From b582b899bede93f52705ab5570052ac0c014a9e9 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Fri, 24 Nov 2017 23:56:54 +0100 Subject: [PATCH 1/7] btm and cdi first commit --- btm-cdi/doc/PfactoryTras.puml | 93 ++++++ btm-cdi/pom.xml | 129 ++++++++ .../tm/integration/cdi/CdiTransactional.java | 16 + .../cdi/EntityManagerDelegate.java | 312 ++++++++++++++++++ .../tm/integration/cdi/EntityManagerInfo.java | 11 + .../cdi/PlatformTransactionManager.java | 54 +++ .../cdi/PoolingDataSourceFactoryBean.java | 50 +++ .../cdi/SqlPersistenceFactory.java | 125 +++++++ .../tm/integration/cdi/TFrameStack.java | 170 ++++++++++ .../tm/integration/cdi/TInterceptor.java | 159 +++++++++ .../tm/integration/cdi/TUserTransaction.java | 222 +++++++++++++ .../tm/integration/cdi/TransactionInfo.java | 41 +++ .../tm/integration/cdi/DataSource1.java | 20 ++ .../tm/integration/cdi/DataSource2.java | 26 ++ .../integration/cdi/H2PersistenceFactory.java | 61 ++++ .../integration/cdi/H2TransactionalTest.java | 160 +++++++++ .../cdi/PlatformTransactionManagerTest.java | 46 +++ .../cdi/PoolingDataSourceFactoryBeanTest.java | 49 +++ .../tm/integration/cdi/Resources.java | 60 ++++ .../tm/integration/cdi/TestEntity1.java | 50 +++ .../tm/integration/cdi/TransactionalBean.java | 81 +++++ .../integration/cdi/TransactionalJPABean.java | 188 +++++++++++ .../cdi/TransactionalJPABean2.java | 89 +++++ .../tm/integration/cdi/TransactionalTest.java | 143 ++++++++ .../test/resources/META-INF/persistence.xml | 21 ++ .../bitronix-default-config.properties | 5 + btm-cdi/src/test/resources/jndi.properties | 1 + btm-cdi/src/test/resources/log4j.xml | 32 ++ btm-cdi/src/test/resources/test-context.xml | 39 +++ 29 files changed, 2453 insertions(+) create mode 100644 btm-cdi/doc/PfactoryTras.puml create mode 100644 btm-cdi/pom.xml create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTransactional.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerDelegate.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerInfo.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBean.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/TUserTransaction.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java create mode 100644 btm-cdi/src/test/resources/META-INF/persistence.xml create mode 100644 btm-cdi/src/test/resources/bitronix-default-config.properties create mode 100644 btm-cdi/src/test/resources/jndi.properties create mode 100644 btm-cdi/src/test/resources/log4j.xml create mode 100644 btm-cdi/src/test/resources/test-context.xml diff --git a/btm-cdi/doc/PfactoryTras.puml b/btm-cdi/doc/PfactoryTras.puml new file mode 100644 index 00000000..2a483133 --- /dev/null +++ b/btm-cdi/doc/PfactoryTras.puml @@ -0,0 +1,93 @@ +@startuml + +actor client as c +participant service as s +participant tinterceptor as ti +participant tstack as ts +participant transactionInfo as tif +participant emdelegate as emd +participant tm +participant em +participant pfactory as pf +participant query1 as q1 +participant query2 as q2 + +c -> s: call +activate s +s -> ti: intercept +activate ti +ti -> ts: new stackentry(attribute) +create tif +ts -> tif: new +ti -> tm: begin +tm --> ti: ok +ti -> s: invoke +activate s +s -> emd: createQuery +activate emd +emd -> pf: getEmTransactional +pf -> ts: searchEm(pf) +activate ts +ts --> pf: not found +deactivate ts +create em +pf -> em: new +pf -> ts: takePart(em) +activate ts +ts -> tif: register(em) +tif -> em: joinTransaction +ts --> pf: ok +deactivate ts +pf --> emd: em +emd -> em: createQuery +create q1 +em -> q1: createQuery +em --> emd: q1 +emd --> s: q1 +deactivate emd +s -> q1: execute +activate q1 +q1 --> s: results +deactivate q1 +destroy q1 +s -> emd: createQuery +activate emd +emd -> pf: getEmTransactional +pf -> ts: searchEm(pf) +activate ts +ts --> pf: em +deactivate ts +pf --> emd: em +emd -> em: createQuery +create q2 +em -> q2: createQuery +em --> emd: q2 +emd --> s: q2 +deactivate emd +s -> q2: execute +activate q2 +q2 --> s: results +deactivate q2 +destroy q2 +s --> ti: return +deactivate s +activate ti +ti -> tm: commit +tm -> em: flush +em --> tm: ok +tm --> ti: ok +ti -> ts: close +activate ts +ts -> em: close +em --> ts: ok +destroy em +ts --> tif: close +destroy tif +ts --> ti: ok +deactivate ts +deactivate ti +ti --> s: return from intercept +deactivate ti +s --> c: return +deactivate s +@enduml diff --git a/btm-cdi/pom.xml b/btm-cdi/pom.xml new file mode 100644 index 00000000..b41d0676 --- /dev/null +++ b/btm-cdi/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + org.codehaus.btm + btm-parent + 3.0.0-SNAPSHOT + + btm-cdi + Bitronix Transaction Manager :: CDI Integration + + + 1.0.1.Final + 4.2.2.Final + 4.3.1.Final + 1.7.21 + + + + org.codehaus.btm + btm + + + + javax.transaction + jta + provided + + + org.slf4j + slf4j-api + provided + + + + org.codehaus.btm + btm + test-jar + test + + + org.jboss.weld.se + weld-se-core + + 1.1.14.Final + + + junit + junit + test + + + org.mockito + mockito-all + test + + + javax.inject + javax.inject + test + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + provided + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + net.oneandone + ejb-cdi-unit + 1.1.4 + + + 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} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-source-plugin + + + + + diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTransactional.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTransactional.java new file mode 100644 index 00000000..3a3e2c8f --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTransactional.java @@ -0,0 +1,16 @@ +package bitronix.tm.integration.cdi; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author aschoerk + */ +@InterceptorBinding +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CdiTransactional { +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerDelegate.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerDelegate.java new file mode 100644 index 00000000..2a85c17c --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerDelegate.java @@ -0,0 +1,312 @@ +package bitronix.tm.integration.cdi; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TransactionRequiredException; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; +import java.util.List; +import java.util.Map; + +/** + * Used to delegate EntityManager actions to the current EntityManager of the Thread, as it is defined according + * to Initialization and Transaction-Context + * Created by aschoerk2 on 3/2/14. + */ +@SuppressWarnings("ClassWithTooManyMethods") +class EntityManagerDelegate implements EntityManager { + + private final SqlPersistenceFactory entityManagerStore; + + EntityManagerDelegate(SqlPersistenceFactory entityManagerStore) { + this.entityManagerStore = entityManagerStore; + } + + private EntityManager getEmbeddedEntityManager() { + /* + * make sure the transaction context is correctly started, if necessary, then return the workable EntityManager + * of the thread. + */ + try { + return entityManagerStore.getTransactional(false); + } catch (TransactionRequiredException e) { + throw new RuntimeException("not expected exception: ", e); + } + } + + + private EntityManager getEmbeddedEntityManager(boolean expectTransaction) { + /* + * make sure the transaction context is correctly started, if necessary, then return the workable EntityManager of the thread. + */ + return entityManagerStore.getTransactional(expectTransaction); + } + + @Override + public void persist(final Object entity) { + getEmbeddedEntityManager(true).persist(entity); + } + + @Override + public T merge(final T entity) { + return getEmbeddedEntityManager(true).merge(entity); + } + + @Override + public void remove(final Object entity) { + getEmbeddedEntityManager(true).remove(entity); + } + + @Override + public T find(final Class entityClass, final Object primaryKey) { + return getEmbeddedEntityManager().find(entityClass, primaryKey); + } + + @Override + public T find(final Class entityClass, final Object primaryKey, final Map properties) { + return getEmbeddedEntityManager().find(entityClass, primaryKey, properties); + } + + @Override + public T find(final Class entityClass, final Object primaryKey, final LockModeType lockMode) { + return getEmbeddedEntityManager().find(entityClass, primaryKey, lockMode); + } + + @Override + public T find(final Class entityClass, final Object primaryKey, final LockModeType lockMode, + final Map properties) { + return getEmbeddedEntityManager().find(entityClass, primaryKey, lockMode, properties); + } + + @Override + public T getReference(final Class entityClass, final Object primaryKey) { + return getEmbeddedEntityManager().getReference(entityClass, primaryKey); + } + + @Override + public void flush() { + getEmbeddedEntityManager(true).flush(); + } + + @Override + public FlushModeType getFlushMode() { + return getEmbeddedEntityManager().getFlushMode(); + } + + @Override + public void setFlushMode(final FlushModeType flushMode) { + getEmbeddedEntityManager().setFlushMode(flushMode); + } + + @Override + public void lock(final Object entity, final LockModeType lockMode) { + getEmbeddedEntityManager(true).lock(entity, lockMode); + } + + @Override + public void lock(final Object entity, final LockModeType lockMode, final Map properties) { + getEmbeddedEntityManager(true).lock(entity, lockMode, properties); + } + + @Override + public void refresh(final Object entity) { + getEmbeddedEntityManager(true).refresh(entity); + } + + @Override + public void refresh(final Object entity, final Map properties) { + getEmbeddedEntityManager(true).refresh(entity, properties); + } + + @Override + public void refresh(final Object entity, final LockModeType lockMode) { + getEmbeddedEntityManager(true).refresh(entity, lockMode); + } + + @Override + public void refresh(final Object entity, final LockModeType lockMode, final Map properties) { + getEmbeddedEntityManager(true).refresh(entity, lockMode, properties); + } + + @Override + public void clear() { + getEmbeddedEntityManager().clear(); + } + + @Override + public void detach(final Object entity) { + getEmbeddedEntityManager().detach(entity); + } + + @Override + public boolean contains(final Object entity) { + return getEmbeddedEntityManager().contains(entity); + } + + @Override + public LockModeType getLockMode(final Object entity) { + return getEmbeddedEntityManager(true).getLockMode(entity); + } + + @Override + public void setProperty(final String propertyName, final Object value) { + getEmbeddedEntityManager().setProperty(propertyName, value); + } + + @Override + public Map getProperties() { + return getEmbeddedEntityManager().getProperties(); + } + + @Override + public Query createQuery(final String qlString) { + return getEmbeddedEntityManager().createQuery(qlString); + } + + @Override + public TypedQuery createQuery(final CriteriaQuery criteriaQuery) { + return getEmbeddedEntityManager().createQuery(criteriaQuery); + } + + @Override + public Query createQuery(CriteriaUpdate criteriaUpdate) { + return getEmbeddedEntityManager().createQuery(criteriaUpdate); + } + + @Override + public Query createQuery(CriteriaDelete criteriaDelete) { + return getEmbeddedEntityManager().createQuery(criteriaDelete); + } + + @Override + public TypedQuery createQuery(final String qlString, final Class resultClass) { + return getEmbeddedEntityManager().createQuery(qlString, resultClass); + } + + @Override + public Query createNamedQuery(final String name) { + return getEmbeddedEntityManager().createNamedQuery(name); + } + + @Override + public TypedQuery createNamedQuery(final String name, final Class resultClass) { + return getEmbeddedEntityManager().createNamedQuery(name, resultClass); + } + + @Override + public Query createNativeQuery(final String sqlString) { + return getEmbeddedEntityManager().createNativeQuery(sqlString); + } + + @SuppressWarnings("rawtypes") + @Override + public Query createNativeQuery(final String sqlString, final Class resultClass) { + return getEmbeddedEntityManager().createNativeQuery(sqlString, resultClass); + } + + @Override + public Query createNativeQuery(final String sqlString, final String resultSetMapping) { + return getEmbeddedEntityManager().createNativeQuery(sqlString, resultSetMapping); + } + + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + return getEmbeddedEntityManager().createNamedStoredProcedureQuery(name); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + return getEmbeddedEntityManager().createStoredProcedureQuery(procedureName); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class[] resultClasses) { + return getEmbeddedEntityManager().createStoredProcedureQuery(procedureName, resultClasses); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + return getEmbeddedEntityManager().createStoredProcedureQuery(procedureName, resultSetMappings); + } + + @Override + public void joinTransaction() { + getEmbeddedEntityManager().joinTransaction(); + } + + @Override + public boolean isJoinedToTransaction() { + return getEmbeddedEntityManager().isJoinedToTransaction(); + } + + @Override + public T unwrap(final Class cls) { + return getEmbeddedEntityManager().unwrap(cls); + } + + @Override + public Object getDelegate() { + return getEmbeddedEntityManager().getDelegate(); + } + + @Override + public void close() { + getEmbeddedEntityManager().close(); + } + + @Override + public boolean isOpen() { + return getEmbeddedEntityManager().isOpen(); + } + + @Override + public EntityTransaction getTransaction() { + return null; + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + return getEmbeddedEntityManager().getEntityManagerFactory(); + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + return getEmbeddedEntityManager().getCriteriaBuilder(); + } + + @Override + public Metamodel getMetamodel() { + return getEmbeddedEntityManager().getMetamodel(); + } + + @Override + public EntityGraph createEntityGraph(Class rootType) { + return getEmbeddedEntityManager().createEntityGraph(rootType); + } + + @Override + public EntityGraph createEntityGraph(String graphName) { + return getEmbeddedEntityManager().createEntityGraph(graphName); + } + + @Override + public EntityGraph getEntityGraph(String graphName) { + return getEmbeddedEntityManager().getEntityGraph(graphName); + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return getEmbeddedEntityManager().getEntityGraphs(entityClass); + } + +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerInfo.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerInfo.java new file mode 100644 index 00000000..9a06c581 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EntityManagerInfo.java @@ -0,0 +1,11 @@ +package bitronix.tm.integration.cdi; + +import javax.persistence.EntityManager; + +/** + * @author aschoerk + */ +public class EntityManagerInfo { + String persistenceUnit; + EntityManager em; +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java new file mode 100644 index 00000000..595c0cef --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java @@ -0,0 +1,54 @@ +package bitronix.tm.integration.cdi; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Produces; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + + +import bitronix.tm.BitronixTransactionManager; +import bitronix.tm.TransactionManagerServices; +import org.jglue.cdiunit.ProducesAlternative; + +/** + * Bitronix-specific Spring PlatformTransactionManager implementation. + * + * @author Marcus Klimstra (CGI) + */ +@ApplicationScoped +public class PlatformTransactionManager { + + private final BitronixTransactionManager transactionManager;; + + public PlatformTransactionManager() { + this.transactionManager = TransactionManagerServices.getTransactionManager(); + } + + @PostConstruct + public void postConstruct() { + // System.clearProperty("java.naming.factory.initial"); + } + + @Produces + @ProducesAlternative + @Alternative + protected UserTransaction retrieveUserTransaction() { + return new TUserTransaction(); + } + + @Produces + protected TransactionManager retrieveTransactionManager() { + return transactionManager; + } + + @Produces + protected Object retrieveTransactionSynchronizationRegistry() { + return transactionManager; + } + + public void destroy() throws Exception { + transactionManager.shutdown(); + } +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBean.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBean.java new file mode 100644 index 00000000..be47cb5e --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBean.java @@ -0,0 +1,50 @@ +package bitronix.tm.integration.cdi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import bitronix.tm.resource.common.ResourceBean; +import bitronix.tm.resource.jdbc.PoolingDataSource; +import bitronix.tm.utils.PropertyUtils; + +/** + * FactoryBean for PoolingDataSource to correctly manage its lifecycle when used + * with Spring. + * + * @author Marcus Klimstra (CGI) + */ +public class PoolingDataSourceFactoryBean extends ResourceBean { + + private static final Logger log = LoggerFactory.getLogger(PoolingDataSourceFactoryBean.class); + private static final long serialVersionUID = 8283399886348754184L; + + private PoolingDataSource ds; + + public Class getObjectType() { + return PoolingDataSource.class; + } + + public boolean isSingleton() { + return true; + } + + public PoolingDataSource getObject() throws Exception { + if (ds == null) { + ds = new PoolingDataSource(); + PropertyUtils.setProperties(ds, PropertyUtils.getProperties(this)); + + + log.debug("Initializing PoolingDataSource with id '{}'", ds.getUniqueName()); + ds.init(); + } + return ds; + } + + public void destroy() throws Exception { + if (ds != null) { + log.debug("Closing PoolingDataSource with id '{}'", ds.getUniqueName()); + ds.close(); + ds = null; + } + } +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java new file mode 100644 index 00000000..5dee56f7 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java @@ -0,0 +1,125 @@ +package bitronix.tm.integration.cdi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +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.HashSet; + +/** + * @author aschoerk + */ +public abstract class SqlPersistenceFactory { + @Inject + TransactionManager tm; + + private final Logger logger = LoggerFactory.getLogger(SqlPersistenceFactory.class); + + private static final HashSet PERSISTENCE_UNIT_NAMES = new HashSet<>(); + private EntityManagerFactory emf = null; + + DataSource ds; + + /** + * allow to reset between Tests. + */ + private static void clearPersistenceUnitNames() { + PERSISTENCE_UNIT_NAMES.clear(); + } + + public abstract String getPersistenceUnitName(); + + protected abstract DataSource createDataSource(); + + + public EntityManagerFactory getEmf() { + return emf; + } + + protected void createEntityManagerFactory() { + if (emf == null) { + ds = createDataSource(); + emf = Persistence.createEntityManagerFactory(getPersistenceUnitName()); + } + } + /** + * prepare EntityManagerFactory + */ + @PostConstruct + public void construct() { + logger.info("creating persistence factory {}", getPersistenceUnitName()); + synchronized (PERSISTENCE_UNIT_NAMES) { + if (PERSISTENCE_UNIT_NAMES.contains(getPersistenceUnitName())) { + throw new RuntimeException("Repeated construction of currently existing PersistenceFactory for " + getPersistenceUnitName()); + } else { + createEntityManagerFactory(); + PERSISTENCE_UNIT_NAMES.add(getPersistenceUnitName()); + } + } + } + + /** + * make sure all connections will be closed + */ + @PreDestroy + public void destroy() { + logger.info("destroying persistence factory {}", getPersistenceUnitName()); + synchronized (PERSISTENCE_UNIT_NAMES) { + if (!PERSISTENCE_UNIT_NAMES.contains(getPersistenceUnitName())) { + throw new RuntimeException("Expected PersistenceFactory for " + getPersistenceUnitName()); + } else { + if (emf != null && emf.isOpen()) { + emf.close(); + emf = null; + } + PERSISTENCE_UNIT_NAMES.remove(getPersistenceUnitName()); + } + clearPersistenceUnitNames(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + SqlPersistenceFactory that = (SqlPersistenceFactory) obj; + + return getPersistenceUnitName() != null ? getPersistenceUnitName().equals(that.getPersistenceUnitName()) + : that.getPersistenceUnitName() == null; + } + + @Override + public int hashCode() { + return getPersistenceUnitName() != null ? getPersistenceUnitName().hashCode() : 0; + } + + /** + * returns EntityManager, to be injected and used so that the current threadSpecific context is correctly handled + * + * @return the EntityManager as it is returnable by producers. + */ + public EntityManager produceEntityManager() { + return new EntityManagerDelegate(this); + } + + + public EntityManager getTransactional(boolean expectTransaction) { + return new TFrameStack().getEntityManager(this, expectTransaction); + } + + public DataSource getDatasource() { + return ds; + } +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java new file mode 100644 index 00000000..44bc8856 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java @@ -0,0 +1,170 @@ +package bitronix.tm.integration.cdi; + +import bitronix.tm.TransactionManagerServices; + +import javax.ejb.TransactionAttributeType; +import javax.persistence.EntityManager; +import javax.persistence.TransactionRequiredException; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.InvalidTransactionException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; + +/** + * The logic necessary to handle stacking of transactions according ejb-transactionattributetypes. + * Later: should be able to handle bean managed transactions as well (UserTransaction). + * + * @author aschoerk + */ +public class TFrameStack { + + private static ThreadLocal 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 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(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/TInterceptor.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java new file mode 100644 index 00000000..95d73d1d --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java @@ -0,0 +1,159 @@ +package bitronix.tm.integration.cdi; + +/** + * @author aschoerk + */ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import javax.ejb.ApplicationException; +import javax.ejb.EJBException; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.ejb.TransactionManagement; +import javax.ejb.TransactionManagementType; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; + +@Interceptor +@CdiTransactional +public class TInterceptor { + + private final Logger logger = + LoggerFactory.getLogger(TInterceptor.class); + + @Inject + TransactionManager tm; + + + + @AroundInvoke + public Object manageTransaction(InvocationContext ctx) throws Exception { + + final Class declaringClass = ctx.getMethod().getDeclaringClass(); + Class targetClass = getTargetClass(ctx); + boolean beanManaged = isBeanManaged(declaringClass) || isBeanManaged(targetClass); + TFrameStack ts = new TFrameStack(); + TransactionInfo lastTransactionInfo = ts.topTransaction(); + + TransactionAttributeType attribute; + if (beanManaged) { + attribute = TransactionAttributeType.NOT_SUPPORTED; + } else { + TransactionAttribute transaction = + declaringClass.getAnnotation( + TransactionAttribute.class); + TransactionAttribute transactionMethod = ctx.getMethod().getAnnotation(TransactionAttribute.class); + + if (transactionMethod != null) { + attribute = transactionMethod.value(); + } else if (transaction != null) { + attribute = transaction.value(); + } else { + attribute = TransactionAttributeType.REQUIRED; + } + } + + + boolean passThroughRollbackException = true; + try { + logger.info("Thread {} L{} changing from {} to {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), + ts.currentType(), + attribute, MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); + ts.pushTransaction(attribute); + return ctx.proceed(); + } catch (Throwable ex) { + logger.info("Thread {} L{} Exception {} in {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), + ex.getClass().getSimpleName(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), + ctx.getMethod().getName()); + if (beanManaged) { + if (ex instanceof RuntimeException) { + throw new EJBException((RuntimeException) ex); + } else { + throw ex; + } + } + ApplicationException applicationException = findApplicationException(ex); + boolean doRollback = + applicationException != null ? applicationException.rollback() : ex instanceof RuntimeException; + + if (doRollback) { + passThroughRollbackException = false; + tm.rollback(); + } + + if (applicationException == null && ex instanceof RuntimeException) { + throw new EJBException((RuntimeException) ex); + } else { + throw ex; + } + } finally { + logger.info("Thread {} L{} finally in {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), + ctx.getMethod().getName()); + try { + ts.popTransaction(); + } catch (RollbackException rbe) { + if (passThroughRollbackException) { + throw rbe; + } + } finally { + logger.info("Thread {} L{} done {} back to {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.topTransaction(), attribute, + lastTransactionInfo == null ? "undefined" : lastTransactionInfo.currentTransactionAttributeType, + MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); + } + } + } + + + private boolean traActive() throws SystemException { + return tm.getStatus() != Status.STATUS_NO_TRANSACTION; + } + + private Class getTargetClass(InvocationContext ctx) { + final Object target = ctx.getTarget(); + if (target == null) + return null; + Class res = target.getClass(); + if (res.getName().endsWith("WeldSubclass")) + return res.getSuperclass(); + else + return res; + + } + + private ApplicationException findApplicationException(Throwable ex) { + // search for applicationexception + Class tmp = ex.getClass(); + ApplicationException applicationException = null; + while (!tmp.equals(Throwable.class)) { + applicationException = tmp.getAnnotation(ApplicationException.class); + if (applicationException != null) { + break; + } + tmp = tmp.getSuperclass(); + } + if (applicationException != null && (tmp.equals(ex.getClass()) || applicationException.inherited())) { + return applicationException; + } + return null; + } + + private boolean isBeanManaged(Class declaringClass) { + return declaringClass != null + && declaringClass.getAnnotation(TransactionManagement.class) != null + && declaringClass.getAnnotation(TransactionManagement.class).value() == TransactionManagementType.BEAN; + } + +} 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..c89e7680 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java @@ -0,0 +1,41 @@ +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; + TransactionInfo previous; + boolean userTransaction; + int level; + + public void setUserTransaction() { + this.userTransaction = true; + } + + public boolean isUserTransaction() { + return userTransaction; + } +} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java new file mode 100644 index 00000000..c995c45c --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java @@ -0,0 +1,20 @@ +package bitronix.tm.integration.cdi; + +import javax.enterprise.inject.Alternative; + +/** + * @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/DataSource2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java new file mode 100644 index 00000000..7ae28c2b --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java @@ -0,0 +1,26 @@ +package bitronix.tm.integration.cdi; + +import javax.enterprise.inject.Alternative; +import java.util.Properties; + +/** + * @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/H2PersistenceFactory.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java new file mode 100644 index 00000000..035a8700 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java @@ -0,0 +1,61 @@ +package bitronix.tm.integration.cdi; + +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(3); + res.init(); + log.info("created datasource"); + return res; + } + + + +} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java new file mode 100644 index 00000000..18101eb5 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java @@ -0,0 +1,160 @@ +package bitronix.tm.integration.cdi; + +import com.oneandone.ejbcdiunit.EjbUnitRunner; +import org.jglue.cdiunit.ActivatedAlternatives; +import org.jglue.cdiunit.AdditionalClasses; +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.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.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * @author aschoerk + */ +@RunWith(EjbUnitRunner.class) +@AdditionalClasses({PlatformTransactionManager.class, H2PersistenceFactory.class, TInterceptor.class}) +public class H2TransactionalTest { + + static Logger log = LoggerFactory.getLogger("testlogger"); + + @BeforeClass + public static void loginit() { + log.info("log"); + } + + + @Inject + UserTransaction tm; + + @Inject + DataSource dataSource; + + @Before + public void initContext() throws Exception { + tm.begin(); + + Connection connection = dataSource.getConnection(); + Statement stmt = connection.createStatement(); + stmt.execute("create table a (a varchar(200))"); + stmt.execute("create table b (a varchar(200))"); + stmt.execute("create table hibernate_sequences (sequence_name varchar(255) not null, sequence_next_hi_value bigint, primary key (sequence_name))"); + // stmt.execute("insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1)"); + stmt.execute("create table test_entity_1 (id bigint not null, intAttribute integer not null, stringAttribute varchar(255), primary key (id))"); + tm.commit(); + } + + @Test + public void test() throws Exception { + tm.begin(); + + Connection connection = dataSource.getConnection(); + 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"); + ResultSet result = stmt.getResultSet(); + assert(result.next()); + assert(result.next()); + assert(!result.next()); + tm.commit(); + } + + @Inject + TransactionalJPABean2 transactionalJPABean; + + @Test + public void testTraMethod() throws Exception { + + transactionalJPABean.insertTestEntityInNewTra(); + Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + tm.rollback(); + Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(2L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + insertTestEntityInNewTraAndRollback(); + tm.commit(); + Assert.assertEquals(4L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + insertTestEntityInNewTraAndRollback(); + tm.commit(); + Assert.assertEquals(5L,transactionalJPABean.countTestEntity()); + tm.begin(); + insertTestEntityInNewTraAndRollback(); + transactionalJPABean.insertTestEntityInRequired(); + tm.commit(); + Assert.assertEquals(6L,transactionalJPABean.countTestEntity()); + tm.begin(); + insertTestEntityInNewTraAndRollback(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(7L,transactionalJPABean.countTestEntity()); + + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + tm.commit(); + Assert.assertEquals(9L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + tm.commit(); + Assert.assertEquals(10L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + transactionalJPABean.insertTestEntityInRequired(); + tm.commit(); + Assert.assertEquals(11L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(12L,transactionalJPABean.countTestEntity()); + + } + + private void insertTestEntityInNewTraAndRollback() throws Exception { + try { + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + Assert.fail("expected rollbackexception"); + } + catch (RollbackException rbe) { + + } + } + +} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java new file mode 100644 index 00000000..e09268b3 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java @@ -0,0 +1,46 @@ +package bitronix.tm.integration.cdi; + +import java.sql.SQLException; + +import javax.inject.Inject; + +import com.oneandone.ejbcdiunit.EjbUnitRunner; +import org.jglue.cdiunit.ActivatedAlternatives; +import org.jglue.cdiunit.AdditionalClasses; +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.mock.events.EventRecorder; + +@RunWith(EjbUnitRunner.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/PoolingDataSourceFactoryBeanTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java new file mode 100644 index 00000000..5a0dc2fe --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java @@ -0,0 +1,49 @@ +package bitronix.tm.integration.cdi; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.inject.Inject; +import javax.inject.Named; + +import com.oneandone.ejbcdiunit.EjbUnitRunner; +import org.jglue.cdiunit.AdditionalClasses; +import org.junit.Test; +import org.junit.runner.RunWith; +import bitronix.tm.resource.jdbc.PoolingDataSource; + +@RunWith(EjbUnitRunner.class) +public class PoolingDataSourceFactoryBeanTest { + + @Inject + private DataSource2 dataSource2; + + @Test + public void validateProperties() { + assertEquals("btm-spring-test-ds2", dataSource2.getUniqueName()); + assertEquals("bitronix.tm.mock.resource.jdbc.MockitoXADataSource", dataSource2.getClassName()); + assertEquals(1, dataSource2.getMinPoolSize()); + assertEquals(2, dataSource2.getMaxPoolSize()); + assertEquals(true, dataSource2.getAutomaticEnlistingEnabled()); + assertEquals(false, dataSource2.getUseTmJoin()); + assertEquals(60, dataSource2.getMaxIdleTime()); // default value not overridden in bean configuration + 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/Resources.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java new file mode 100644 index 00000000..743f5c68 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java @@ -0,0 +1,60 @@ +package bitronix.tm.integration.cdi; + +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 + */ +public class Resources { + + Logger log = LoggerFactory.getLogger("ResourcesLogger"); + + public Resources() { + } + + @Inject + TransactionManager tm; + + @Inject + DataSource ds; + + @Produces + @ApplicationScoped + EntityManagerFactory createEntityManagerFactory() { + return Persistence.createEntityManagerFactory("btm-cdi-test-h2-pu"); + } + + + @Produces + @ApplicationScoped + DataSource createDataSource() { + 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.init(); + log.info("created datasource"); + return res; + } + + + +} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java new file mode 100644 index 00000000..169d68e1 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java @@ -0,0 +1,50 @@ +package bitronix.tm.integration.cdi; + +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/TransactionalBean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java new file mode 100644 index 00000000..5bc8f2d9 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java @@ -0,0 +1,81 @@ +package bitronix.tm.integration.cdi; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Iterator; + +import javax.inject.Inject; +import javax.sql.DataSource; +import javax.transaction.TransactionManager; +import javax.transaction.Transactional; +import javax.transaction.UserTransaction; +import javax.transaction.xa.XAResource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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(); + // platformTransactionManager.retrieveUserTransaction().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/TransactionalJPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java new file mode 100644 index 00000000..d49953ff --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java @@ -0,0 +1,188 @@ +package bitronix.tm.integration.cdi; + +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.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.inject.Inject; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; +import javax.sql.DataSource; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.InvalidTransactionException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAResource; +import java.io.Closeable; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; + +@CdiTransactional +public class TransactionalJPABean { + + private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean.class); + + @Resource(name = "h2DataSource") + private DataSource dataSource; + + @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); + } + + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + 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); + } + } + + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + 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); + } + } + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + 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); + } + } + + @TransactionAttribute(value = TransactionAttributeType.REQUIRED) + 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/TransactionalJPABean2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java new file mode 100644 index 00000000..6969ecef --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java @@ -0,0 +1,89 @@ +package bitronix.tm.integration.cdi; + +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.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; +import javax.transaction.xa.XAResource; +import java.util.Iterator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; + +@CdiTransactional +public class TransactionalJPABean2 { + + private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean2.class); + + @Resource(name = "h2DataSource") + private DataSource dataSource; + + @Inject + UserTransaction 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/TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java new file mode 100644 index 00000000..395022aa --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java @@ -0,0 +1,143 @@ +package bitronix.tm.integration.cdi; + +import com.oneandone.ejbcdiunit.EjbUnitRunner; +import org.jglue.cdiunit.AdditionalClasses; +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.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import javax.transaction.TransactionManager; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + * @author aschoerk + */ +@RunWith(EjbUnitRunner.class) +@AdditionalClasses({PlatformTransactionManager.class, Resources.class, TInterceptor.class}) +public class TransactionalTest { + + static Logger log = LoggerFactory.getLogger("testlogger"); + + @BeforeClass + public static void loginit() { + log.info("log"); + } + + + @Inject + TransactionManager tm; + + @Inject + H2PersistenceFactory entityManagerFactory; + + @Before + public void initContext() throws Exception { + tm.begin(); + + try (Connection connection = entityManagerFactory.getDatasource().getConnection()) { + try (Statement stmt = connection.createStatement()) { + stmt.execute("create table a (a varchar(200))"); + stmt.execute("create table b (a varchar(200))"); + stmt.execute("create table hibernate_sequences (sequence_name varchar(255) not null, sequence_next_hi_value bigint, primary key (sequence_name))"); + // stmt.execute("insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1)"); + stmt.execute("create table test_entity_1 (id bigint not null, intAttribute integer not null, stringAttribute varchar(255), primary key (id))"); + } + } + tm.commit(); + } + + @Test + public void test() throws Exception { + tm.begin(); + + try (Connection connection = entityManagerFactory.getDatasource().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 + TransactionalJPABean transactionalJPABean; + + @Test + public void testTraMethod() throws Exception { + + transactionalJPABean.insertTestEntityInNewTra(); + Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + tm.rollback(); + Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(2L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + transactionalJPABean.insertTestEntityInNewTraAndRollback(); + tm.commit(); + Assert.assertEquals(4L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTraAndRollback(); + tm.commit(); + Assert.assertEquals(5L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndRollback(); + transactionalJPABean.insertTestEntityInRequired(); + tm.commit(); + Assert.assertEquals(6L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndRollback(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(7L,transactionalJPABean.countTestEntity()); + + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + tm.commit(); + Assert.assertEquals(9L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + tm.commit(); + Assert.assertEquals(10L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + transactionalJPABean.insertTestEntityInRequired(); + tm.commit(); + Assert.assertEquals(11L,transactionalJPABean.countTestEntity()); + tm.begin(); + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + transactionalJPABean.insertTestEntityInRequired(); + transactionalJPABean.insertTestEntityInNewTra(); + tm.rollback(); + Assert.assertEquals(12L,transactionalJPABean.countTestEntity()); + + } + +} 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..de66096e --- /dev/null +++ b/btm-cdi/src/test/resources/bitronix-default-config.properties @@ -0,0 +1,5 @@ +bitronix.tm.serverId=btm-cdi-test +# for testing don't use logs +bitronix.tm.journal=null +# 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..5f78f391 --- /dev/null +++ b/btm-cdi/src/test/resources/test-context.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + From 3799fd57b464523ab9df622dc24571dd7866aa40 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Thu, 30 Nov 2017 09:10:23 +0100 Subject: [PATCH 2/7] Fix test --- .../tm/integration/cdi/TransactionInfo.java | 1 + .../tm/integration/cdi/H2TransactionalTest.java | 17 +++++++++++++---- .../integration/cdi/TransactionalJPABean2.java | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) 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 index c89e7680..b83fe67b 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java @@ -27,6 +27,7 @@ public TransactionInfo(TransactionInfo previous) { 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; + TAttribute tAttribute; TransactionInfo previous; boolean userTransaction; int level; diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java index 18101eb5..4f7e9f63 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java @@ -125,21 +125,21 @@ public void testTraMethod() throws Exception { tm.begin(); transactionalJPABean.insertTestEntityInRequired(); transactionalJPABean.insertTestEntityInNewTra(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + insertTestEntityInNewTraAndSetRollbackOnly(); tm.commit(); Assert.assertEquals(9L,transactionalJPABean.countTestEntity()); tm.begin(); transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + insertTestEntityInNewTraAndSetRollbackOnly(); tm.commit(); Assert.assertEquals(10L,transactionalJPABean.countTestEntity()); tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + insertTestEntityInNewTraAndSetRollbackOnly(); transactionalJPABean.insertTestEntityInRequired(); tm.commit(); Assert.assertEquals(11L,transactionalJPABean.countTestEntity()); tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + insertTestEntityInNewTraAndSetRollbackOnly(); transactionalJPABean.insertTestEntityInRequired(); transactionalJPABean.insertTestEntityInNewTra(); tm.rollback(); @@ -147,6 +147,15 @@ public void testTraMethod() throws Exception { } + private void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception { + try { + transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); + Assert.fail("Expected Rollbackexception during commit of new tra"); + } catch (RollbackException ex){ + + } + } + private void insertTestEntityInNewTraAndRollback() throws Exception { try { transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java index 6969ecef..6d9f63e7 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java @@ -35,7 +35,7 @@ public class TransactionalJPABean2 { private DataSource dataSource; @Inject - UserTransaction tm; + TransactionManager tm; @Inject EntityManager em; From 446e3eb1255c33bd1049f9c9dafc0b5d756c5b84 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Mon, 4 Dec 2017 06:56:03 +0100 Subject: [PATCH 3/7] Added CDI-Extension, renamings, CDIInterceptor to support TRansactional first stage --- .../tm/integration/cdi/CdiTInterceptor.java | 92 +++++++++++++ ...TInterceptor.java => EjbTInterceptor.java} | 6 +- .../tm/integration/cdi/EjbTransactional.java | 16 +++ .../cdi/PlatformTransactionManager.java | 11 +- .../cdi/SqlPersistenceFactory.java | 14 ++ .../tm/integration/cdi/TFrameStack.java | 51 ++++++-- .../tm/integration/cdi/TransactionInfo.java | 1 + .../cdi/TransactionalCdiExtension.java | 123 ++++++++++++++++++ btm-cdi/src/main/resources/META-INF/beans.xml | 4 + .../integration/cdi/H2PersistenceFactory.java | 4 +- .../integration/cdi/H2TransactionalTest.java | 26 ++-- .../tm/integration/cdi/Resources.java | 10 +- .../integration/cdi/TransactionalJPABean.java | 25 +--- .../cdi/TransactionalJPABean2.java | 8 +- .../tm/integration/cdi/TransactionalTest.java | 4 +- btm-cdi/src/test/resources/META-INF/beans.xml | 4 + .../bitronix-default-config.properties | 1 + btm-cdi/src/test/resources/test-context.xml | 2 +- 18 files changed, 337 insertions(+), 65 deletions(-) create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java rename btm-cdi/src/main/java/bitronix/tm/integration/cdi/{TInterceptor.java => EjbTInterceptor.java} (98%) create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTransactional.java create mode 100644 btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java create mode 100644 btm-cdi/src/main/resources/META-INF/beans.xml create mode 100644 btm-cdi/src/test/resources/META-INF/beans.xml diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java new file mode 100644 index 00000000..ca88f304 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java @@ -0,0 +1,92 @@ +package bitronix.tm.integration.cdi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + + +import javax.ejb.ApplicationException; +import javax.ejb.EJBException; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.transaction.RollbackException; +import javax.transaction.TransactionManager; +import javax.transaction.Transactional; + +@Interceptor +@CdiTransactional +public class CdiTInterceptor { + + Logger logger = LoggerFactory.getLogger(CdiTInterceptor.class); + + @Inject + TransactionManager tm; + + @AroundInvoke + public Object manageTransaction(InvocationContext ctx) throws Exception { + final Class declaringClass = ctx.getMethod().getDeclaringClass(); + TFrameStack ts = new TFrameStack(); + TransactionInfo lastTransactionInfo = ts.topTransaction(); + Transactional.TxType attribute = null; + Transactional transaction = + declaringClass.getAnnotation( + Transactional.class); + Transactional transactionMethod = ctx.getMethod().getAnnotation(Transactional.class); + + if (transactionMethod != null) { + attribute = transactionMethod.value(); + } else if (transaction != null) { + attribute = transaction.value() == null ? Transactional.TxType.REQUIRED : transaction.value(); + } + if (attribute == null) { + logger.error("CdiTransactionalInterceptor should not be used at this class: {}", declaringClass.getName()); + } else { + boolean passThroughRollbackException = true; + try { + logger.info("Thread {} L{} changing from {} to {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), + ts.currentTxType(), + attribute, MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); + ts.pushTransaction(attribute); + return ctx.proceed(); + } catch (Throwable ex) { + logger.info("Thread {} L{} Exception {} in {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), + ex.getClass().getSimpleName(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), + ctx.getMethod().getName()); + ApplicationException applicationException = null; // TODO + boolean doRollback = + applicationException != null ? applicationException.rollback() : ex instanceof RuntimeException; + if (doRollback) { + passThroughRollbackException = false; + tm.rollback(); + } + + if (applicationException == null && ex instanceof RuntimeException) { + throw new EJBException((RuntimeException) ex); + } else { + throw ex; + } + } finally { + logger.info("Thread {} L{} finally in {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.currentLevel(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), + ctx.getMethod().getName()); + try { + ts.popTransaction(); + } catch (RollbackException rbe) { + if (passThroughRollbackException) { + throw rbe; + } + } finally { + logger.info("Thread {} L{} done {} back to {} xid: {} in {}.{}", + Thread.currentThread().getId(), ts.topTransaction(), attribute, + lastTransactionInfo == null ? "undefined" : lastTransactionInfo.currentTxType, + MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); + } + } + } + } + +} \ No newline at end of file diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTInterceptor.java similarity index 98% rename from btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java rename to btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTInterceptor.java index 95d73d1d..d63e85d6 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TInterceptor.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTInterceptor.java @@ -24,11 +24,11 @@ import javax.transaction.TransactionManager; @Interceptor -@CdiTransactional -public class TInterceptor { +@EjbTransactional +public class EjbTInterceptor { private final Logger logger = - LoggerFactory.getLogger(TInterceptor.class); + LoggerFactory.getLogger(EjbTInterceptor.class); @Inject TransactionManager tm; diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTransactional.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTransactional.java new file mode 100644 index 00000000..66a18699 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/EjbTransactional.java @@ -0,0 +1,16 @@ +package bitronix.tm.integration.cdi; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author aschoerk + */ +@InterceptorBinding +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EjbTransactional { +} diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java index 595c0cef..7d494c96 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/PlatformTransactionManager.java @@ -1,6 +1,7 @@ package bitronix.tm.integration.cdi; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Produces; @@ -20,17 +21,23 @@ @ApplicationScoped public class PlatformTransactionManager { - private final BitronixTransactionManager transactionManager;; + private BitronixTransactionManager transactionManager;; public PlatformTransactionManager() { this.transactionManager = TransactionManagerServices.getTransactionManager(); } @PostConstruct - public void postConstruct() { + public void postConstructPlatformTM() { // System.clearProperty("java.naming.factory.initial"); } + @PreDestroy + public void preDestroyPlatfromTM() { + transactionManager.shutdown(); + transactionManager = null; + } + @Produces @ProducesAlternative @Alternative diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java index 5dee56f7..cc548165 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java @@ -11,6 +11,8 @@ import javax.persistence.Persistence; import javax.sql.DataSource; import javax.transaction.TransactionManager; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.HashSet; /** @@ -83,6 +85,18 @@ public void destroy() { } clearPersistenceUnitNames(); } + if (ds != null) { + try { + Method closeMethod = ds.getClass().getMethod("close"); + closeMethod.invoke(ds); + } catch (NoSuchMethodException e) { + + } catch (IllegalAccessException e) { + + } catch (InvocationTargetException e) { + + } + } } @Override diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java index 44bc8856..2da466fb 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TFrameStack.java @@ -5,14 +5,7 @@ import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.TransactionRequiredException; -import javax.transaction.HeuristicMixedException; -import javax.transaction.HeuristicRollbackException; -import javax.transaction.InvalidTransactionException; -import javax.transaction.NotSupportedException; -import javax.transaction.RollbackException; -import javax.transaction.Status; -import javax.transaction.SystemException; -import javax.transaction.TransactionManager; +import javax.transaction.*; /** * The logic necessary to handle stacking of transactions according ejb-transactionattributetypes. @@ -40,6 +33,11 @@ public TransactionAttributeType currentType() { 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); } @@ -94,6 +92,43 @@ public void pushUserTransaction() throws SystemException, NotSupportedException } + 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(); 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 index b83fe67b..12c912ca 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionInfo.java @@ -27,6 +27,7 @@ public TransactionInfo(TransactionInfo previous) { 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; 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..12671403 --- /dev/null +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java @@ -0,0 +1,123 @@ +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 () { + + }; + + AnnotationLiteral CDITRALITERAL = new AnnotationLiteral () { + + }; + + 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; + } + if (annotatedType.isAnnotationPresent(Transactional.class)) { + interceptForCdiTransactions = true; + } + if (interceptForCdiTransactions && interceptForEjbTransactions) { + log.warn("Transactional-Annotation for Ejb ignored {}", annotatedType.getJavaClass().getName()); + interceptForCdiTransactions = false; + } + if (interceptForEjbTransactions) { + 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 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/H2PersistenceFactory.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java index 035a8700..cf362338 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java @@ -25,6 +25,7 @@ public class H2PersistenceFactory extends SqlPersistenceFactory { public H2PersistenceFactory() { } + @Override public String getPersistenceUnitName() { return "btm-cdi-test-h2-pu"; @@ -50,7 +51,8 @@ protected DataSource createDataSource() { driverProperties.setProperty("password",""); res.setUniqueName("jdbc/btm-cdi-test-h2"); res.setMinPoolSize(1); - res.setMaxPoolSize(3); + 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/H2TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java index 4f7e9f63..fc7a6fe9 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java @@ -1,8 +1,8 @@ package bitronix.tm.integration.cdi; -import com.oneandone.ejbcdiunit.EjbUnitRunner; -import org.jglue.cdiunit.ActivatedAlternatives; 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; @@ -11,30 +11,22 @@ 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.HeuristicMixedException; -import javax.transaction.HeuristicRollbackException; -import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; -import javax.transaction.SystemException; -import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import java.sql.Connection; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; /** * @author aschoerk */ -@RunWith(EjbUnitRunner.class) -@AdditionalClasses({PlatformTransactionManager.class, H2PersistenceFactory.class, TInterceptor.class}) +@RunWith(CdiRunner.class) +@AdditionalClasses({ + H2PersistenceFactory.class, + EjbTInterceptor.class, TransactionalCdiExtension.class}) +@AdditionalPackages(PlatformTransactionManager.class) public class H2TransactionalTest { static Logger log = LoggerFactory.getLogger("testlogger"); @@ -44,6 +36,8 @@ public static void loginit() { log.info("log"); } + @Inject + PlatformTransactionManager platformTransactionManager; @Inject UserTransaction tm; @@ -53,6 +47,7 @@ public static void loginit() { @Before public void initContext() throws Exception { + /* tm.begin(); Connection connection = dataSource.getConnection(); @@ -63,6 +58,7 @@ public void initContext() throws Exception { // stmt.execute("insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1)"); stmt.execute("create table test_entity_1 (id bigint not null, intAttribute integer not null, stringAttribute varchar(255), primary key (id))"); tm.commit(); + */ } @Test diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java index 743f5c68..33043842 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java @@ -4,6 +4,7 @@ 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; @@ -24,11 +25,15 @@ public class Resources { public Resources() { } + @PreDestroy + public void preDestroyResources() { + ds.close(); + } + @Inject TransactionManager tm; - @Inject - DataSource ds; + PoolingDataSource ds; @Produces @ApplicationScoped @@ -52,6 +57,7 @@ DataSource createDataSource() { res.setMaxPoolSize(3); res.init(); log.info("created datasource"); + ds = res; return res; } diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java index d49953ff..485fd80c 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java @@ -12,43 +12,20 @@ import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.inject.Inject; -import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; -import javax.persistence.EntityTransaction; -import javax.persistence.FlushModeType; -import javax.persistence.LockModeType; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; -import javax.persistence.StoredProcedureQuery; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaDelete; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.CriteriaUpdate; -import javax.persistence.metamodel.Metamodel; import javax.sql.DataSource; -import javax.transaction.HeuristicMixedException; -import javax.transaction.HeuristicRollbackException; -import javax.transaction.InvalidTransactionException; -import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; -import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAResource; -import java.io.Closeable; -import java.sql.Connection; -import java.sql.SQLException; import java.util.Iterator; -import java.util.List; -import java.util.Map; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -@CdiTransactional +@EjbTransactional public class TransactionalJPABean { private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean.class); diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java index 6d9f63e7..a8327ada 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java @@ -9,24 +9,20 @@ 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.persistence.EntityManagerFactory; import javax.sql.DataSource; -import javax.transaction.RollbackException; -import javax.transaction.Status; -import javax.transaction.Transaction; import javax.transaction.TransactionManager; -import javax.transaction.UserTransaction; import javax.transaction.xa.XAResource; import java.util.Iterator; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -@CdiTransactional +@Stateless public class TransactionalJPABean2 { private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean2.class); diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java index 395022aa..1658e045 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java @@ -11,8 +11,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; import javax.transaction.TransactionManager; import java.sql.Connection; import java.sql.ResultSet; @@ -22,7 +20,7 @@ * @author aschoerk */ @RunWith(EjbUnitRunner.class) -@AdditionalClasses({PlatformTransactionManager.class, Resources.class, TInterceptor.class}) +@AdditionalClasses({PlatformTransactionManager.class, Resources.class, EjbTInterceptor.class, TransactionalCdiExtension.class}) public class TransactionalTest { static Logger log = LoggerFactory.getLogger("testlogger"); 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/bitronix-default-config.properties b/btm-cdi/src/test/resources/bitronix-default-config.properties index de66096e..9ad95e92 100644 --- a/btm-cdi/src/test/resources/bitronix-default-config.properties +++ b/btm-cdi/src/test/resources/bitronix-default-config.properties @@ -1,5 +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/test-context.xml b/btm-cdi/src/test/resources/test-context.xml index 5f78f391..2b3a3380 100644 --- a/btm-cdi/src/test/resources/test-context.xml +++ b/btm-cdi/src/test/resources/test-context.xml @@ -14,7 +14,7 @@ - + From 8bb7ab0b073ea7ede6fc5fa98a6176b7fc451875 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Tue, 5 Dec 2017 17:11:10 +0100 Subject: [PATCH 4/7] btm and cdi fixed tests some reorgs --- .../tm/integration/cdi/CdiTInterceptor.java | 6 +- .../cdi/SqlPersistenceFactory.java | 2 +- .../cdi/TransactionalCdiExtension.java | 2 + .../integration/cdi/H2TransactionalTest.java | 165 ------------------ .../cdi/PoolingDataSourceFactoryBeanTest.java | 49 ------ .../tm/integration/cdi/TransactionalTest.java | 141 --------------- .../EJBTransactionalJPABean.java} | 7 +- .../H2EjbTransactionalTest.java | 137 +++++++++++++++ .../H2PersistenceFactory.java | 3 +- .../cdi/{ => entities}/TestEntity1.java | 2 +- .../cdi/{ => nonintercepted}/DataSource1.java | 4 +- .../cdi/{ => nonintercepted}/DataSource2.java | 4 +- .../JPABean.java} | 20 +-- .../PlatformTransactionManagerTest.java | 9 +- .../PoolingDataSourceFactoryBeanTest.java | 49 ++++++ .../cdi/{ => nonintercepted}/Resources.java | 33 +++- .../TransactionalBean.java | 7 +- .../cdi/nonintercepted/TransactionalTest.java | 132 ++++++++++++++ btm-cdi/src/test/resources/test-context.xml | 2 +- pom.xml | 6 + 20 files changed, 382 insertions(+), 398 deletions(-) delete mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java delete mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java delete mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{TransactionalJPABean2.java => ejbintercepted/EJBTransactionalJPABean.java} (91%) create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2EjbTransactionalTest.java rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => ejbintercepted}/H2PersistenceFactory.java (93%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => entities}/TestEntity1.java (95%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => nonintercepted}/DataSource1.java (79%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => nonintercepted}/DataSource2.java (85%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{TransactionalJPABean.java => nonintercepted/JPABean.java} (86%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => nonintercepted}/PlatformTransactionManagerTest.java (85%) create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PoolingDataSourceFactoryBeanTest.java rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => nonintercepted}/Resources.java (67%) rename btm-cdi/src/test/java/bitronix/tm/integration/cdi/{ => nonintercepted}/TransactionalBean.java (93%) create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalTest.java diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java index ca88f304..f674a907 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java @@ -16,7 +16,7 @@ import javax.transaction.Transactional; @Interceptor -@CdiTransactional +@Transactional public class CdiTInterceptor { Logger logger = LoggerFactory.getLogger(CdiTInterceptor.class); @@ -37,11 +37,13 @@ public Object manageTransaction(InvocationContext ctx) throws Exception { if (transactionMethod != null) { attribute = transactionMethod.value(); + Class[] rollbackon = transactionMethod.rollbackOn(); } else if (transaction != null) { attribute = transaction.value() == null ? Transactional.TxType.REQUIRED : transaction.value(); } if (attribute == null) { logger.error("CdiTransactionalInterceptor should not be used at this class: {}", declaringClass.getName()); + return ctx.proceed(); } else { boolean passThroughRollbackException = true; try { @@ -50,7 +52,7 @@ public Object manageTransaction(InvocationContext ctx) throws Exception { ts.currentTxType(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); ts.pushTransaction(attribute); - return ctx.proceed(); + return ctx.proceed(); } catch (Throwable ex) { logger.info("Thread {} L{} Exception {} in {} xid: {} in {}.{}", Thread.currentThread().getId(), ts.currentLevel(), diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java index cc548165..bc3a2b0c 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/SqlPersistenceFactory.java @@ -27,7 +27,7 @@ public abstract class SqlPersistenceFactory { private static final HashSet PERSISTENCE_UNIT_NAMES = new HashSet<>(); private EntityManagerFactory emf = null; - DataSource ds; + protected DataSource ds; /** * allow to reset between Tests. 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 index 12671403..12e69c40 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java @@ -24,10 +24,12 @@ public class TransactionalCdiExtension implements Extension { 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) { diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java deleted file mode 100644 index fc7a6fe9..00000000 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2TransactionalTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package bitronix.tm.integration.cdi; - -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 java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; - -/** - * @author aschoerk - */ -@RunWith(CdiRunner.class) -@AdditionalClasses({ - H2PersistenceFactory.class, - EjbTInterceptor.class, TransactionalCdiExtension.class}) -@AdditionalPackages(PlatformTransactionManager.class) -public class H2TransactionalTest { - - 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 { - /* - tm.begin(); - - Connection connection = dataSource.getConnection(); - Statement stmt = connection.createStatement(); - stmt.execute("create table a (a varchar(200))"); - stmt.execute("create table b (a varchar(200))"); - stmt.execute("create table hibernate_sequences (sequence_name varchar(255) not null, sequence_next_hi_value bigint, primary key (sequence_name))"); - // stmt.execute("insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1)"); - stmt.execute("create table test_entity_1 (id bigint not null, intAttribute integer not null, stringAttribute varchar(255), primary key (id))"); - tm.commit(); - */ - } - - @Test - public void test() throws Exception { - tm.begin(); - - Connection connection = dataSource.getConnection(); - 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"); - ResultSet result = stmt.getResultSet(); - assert(result.next()); - assert(result.next()); - assert(!result.next()); - tm.commit(); - } - - @Inject - TransactionalJPABean2 transactionalJPABean; - - @Test - public void testTraMethod() throws Exception { - - transactionalJPABean.insertTestEntityInNewTra(); - Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - tm.rollback(); - Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(2L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - insertTestEntityInNewTraAndRollback(); - tm.commit(); - Assert.assertEquals(4L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - insertTestEntityInNewTraAndRollback(); - tm.commit(); - Assert.assertEquals(5L,transactionalJPABean.countTestEntity()); - tm.begin(); - insertTestEntityInNewTraAndRollback(); - transactionalJPABean.insertTestEntityInRequired(); - tm.commit(); - Assert.assertEquals(6L,transactionalJPABean.countTestEntity()); - tm.begin(); - insertTestEntityInNewTraAndRollback(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(7L,transactionalJPABean.countTestEntity()); - - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - insertTestEntityInNewTraAndSetRollbackOnly(); - tm.commit(); - Assert.assertEquals(9L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - insertTestEntityInNewTraAndSetRollbackOnly(); - tm.commit(); - Assert.assertEquals(10L,transactionalJPABean.countTestEntity()); - tm.begin(); - insertTestEntityInNewTraAndSetRollbackOnly(); - transactionalJPABean.insertTestEntityInRequired(); - tm.commit(); - Assert.assertEquals(11L,transactionalJPABean.countTestEntity()); - tm.begin(); - insertTestEntityInNewTraAndSetRollbackOnly(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(12L,transactionalJPABean.countTestEntity()); - - } - - private void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception { - try { - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - Assert.fail("Expected Rollbackexception during commit of new tra"); - } catch (RollbackException ex){ - - } - } - - private void insertTestEntityInNewTraAndRollback() throws Exception { - try { - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - Assert.fail("expected rollbackexception"); - } - catch (RollbackException rbe) { - - } - } - -} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java deleted file mode 100644 index 5a0dc2fe..00000000 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PoolingDataSourceFactoryBeanTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package bitronix.tm.integration.cdi; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.inject.Inject; -import javax.inject.Named; - -import com.oneandone.ejbcdiunit.EjbUnitRunner; -import org.jglue.cdiunit.AdditionalClasses; -import org.junit.Test; -import org.junit.runner.RunWith; -import bitronix.tm.resource.jdbc.PoolingDataSource; - -@RunWith(EjbUnitRunner.class) -public class PoolingDataSourceFactoryBeanTest { - - @Inject - private DataSource2 dataSource2; - - @Test - public void validateProperties() { - assertEquals("btm-spring-test-ds2", dataSource2.getUniqueName()); - assertEquals("bitronix.tm.mock.resource.jdbc.MockitoXADataSource", dataSource2.getClassName()); - assertEquals(1, dataSource2.getMinPoolSize()); - assertEquals(2, dataSource2.getMaxPoolSize()); - assertEquals(true, dataSource2.getAutomaticEnlistingEnabled()); - assertEquals(false, dataSource2.getUseTmJoin()); - assertEquals(60, dataSource2.getMaxIdleTime()); // default value not overridden in bean configuration - 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/TransactionalTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java deleted file mode 100644 index 1658e045..00000000 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package bitronix.tm.integration.cdi; - -import com.oneandone.ejbcdiunit.EjbUnitRunner; -import org.jglue.cdiunit.AdditionalClasses; -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; - -/** - * @author aschoerk - */ -@RunWith(EjbUnitRunner.class) -@AdditionalClasses({PlatformTransactionManager.class, Resources.class, EjbTInterceptor.class, TransactionalCdiExtension.class}) -public class TransactionalTest { - - static Logger log = LoggerFactory.getLogger("testlogger"); - - @BeforeClass - public static void loginit() { - log.info("log"); - } - - - @Inject - TransactionManager tm; - - @Inject - H2PersistenceFactory entityManagerFactory; - - @Before - public void initContext() throws Exception { - tm.begin(); - - try (Connection connection = entityManagerFactory.getDatasource().getConnection()) { - try (Statement stmt = connection.createStatement()) { - stmt.execute("create table a (a varchar(200))"); - stmt.execute("create table b (a varchar(200))"); - stmt.execute("create table hibernate_sequences (sequence_name varchar(255) not null, sequence_next_hi_value bigint, primary key (sequence_name))"); - // stmt.execute("insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('test_entity_1', 1)"); - stmt.execute("create table test_entity_1 (id bigint not null, intAttribute integer not null, stringAttribute varchar(255), primary key (id))"); - } - } - tm.commit(); - } - - @Test - public void test() throws Exception { - tm.begin(); - - try (Connection connection = entityManagerFactory.getDatasource().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 - TransactionalJPABean transactionalJPABean; - - @Test - public void testTraMethod() throws Exception { - - transactionalJPABean.insertTestEntityInNewTra(); - Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - tm.rollback(); - Assert.assertEquals(1L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(2L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - transactionalJPABean.insertTestEntityInNewTraAndRollback(); - tm.commit(); - Assert.assertEquals(4L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTraAndRollback(); - tm.commit(); - Assert.assertEquals(5L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndRollback(); - transactionalJPABean.insertTestEntityInRequired(); - tm.commit(); - Assert.assertEquals(6L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndRollback(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(7L,transactionalJPABean.countTestEntity()); - - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - tm.commit(); - Assert.assertEquals(9L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - tm.commit(); - Assert.assertEquals(10L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - transactionalJPABean.insertTestEntityInRequired(); - tm.commit(); - Assert.assertEquals(11L,transactionalJPABean.countTestEntity()); - tm.begin(); - transactionalJPABean.insertTestEntityInNewTraAndSetRollbackOnly(); - transactionalJPABean.insertTestEntityInRequired(); - transactionalJPABean.insertTestEntityInNewTra(); - tm.rollback(); - Assert.assertEquals(12L,transactionalJPABean.countTestEntity()); - - } - -} diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java similarity index 91% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java index a8327ada..212c6988 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean2.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/EJBTransactionalJPABean.java @@ -1,5 +1,6 @@ -package bitronix.tm.integration.cdi; +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; @@ -23,9 +24,9 @@ import static junit.framework.Assert.assertFalse; @Stateless -public class TransactionalJPABean2 { +public class EJBTransactionalJPABean { - private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean2.class); + private static final Logger log = LoggerFactory.getLogger(EJBTransactionalJPABean.class); @Resource(name = "h2DataSource") private DataSource dataSource; 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/H2PersistenceFactory.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java similarity index 93% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java index cf362338..2dc8e4c9 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/H2PersistenceFactory.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/ejbintercepted/H2PersistenceFactory.java @@ -1,5 +1,6 @@ -package bitronix.tm.integration.cdi; +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; diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java similarity index 95% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java index 169d68e1..d2b4a0c2 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TestEntity1.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/entities/TestEntity1.java @@ -1,4 +1,4 @@ -package bitronix.tm.integration.cdi; +package bitronix.tm.integration.cdi.entities; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java similarity index 79% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java index c995c45c..d623ef0f 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource1.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource1.java @@ -1,7 +1,9 @@ -package bitronix.tm.integration.cdi; +package bitronix.tm.integration.cdi.nonintercepted; import javax.enterprise.inject.Alternative; +import bitronix.tm.integration.cdi.PoolingDataSourceFactoryBean; + /** * @author aschoerk */ diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java similarity index 85% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java index 7ae28c2b..2bb2f17a 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/DataSource2.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/DataSource2.java @@ -1,8 +1,10 @@ -package bitronix.tm.integration.cdi; +package bitronix.tm.integration.cdi.nonintercepted; import javax.enterprise.inject.Alternative; import java.util.Properties; +import bitronix.tm.integration.cdi.PoolingDataSourceFactoryBean; + /** * @author aschoerk */ diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java similarity index 86% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java index 485fd80c..ca0a250c 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalJPABean.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/JPABean.java @@ -1,5 +1,6 @@ -package bitronix.tm.integration.cdi; +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; @@ -8,13 +9,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Resource; -import javax.ejb.TransactionAttribute; -import javax.ejb.TransactionAttributeType; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Transaction; @@ -25,13 +22,9 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -@EjbTransactional -public class TransactionalJPABean { +class JPABean { - private static final Logger log = LoggerFactory.getLogger(TransactionalJPABean.class); - - @Resource(name = "h2DataSource") - private DataSource dataSource; + private static final Logger log = LoggerFactory.getLogger(JPABean.class); @Inject TransactionManager tm; @@ -62,7 +55,6 @@ CloseableEm getEm() { return new CloseableEm(result); } - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) public void insertTestEntityInNewTra() throws Exception { Transaction suspendedTransaction = tm.suspend(); @@ -76,7 +68,6 @@ public void insertTestEntityInNewTra() throws Exception { } } - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) public void insertTestEntityInNewTraAndRollback() throws Exception { Transaction suspendedTransaction = tm.suspend(); @@ -89,7 +80,7 @@ public void insertTestEntityInNewTraAndRollback() throws Exception { tm.resume(suspendedTransaction); } } - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception { Transaction suspendedTransaction = tm.suspend(); @@ -110,7 +101,6 @@ public void insertTestEntityInNewTraAndSetRollbackOnly() throws Exception { } } - @TransactionAttribute(value = TransactionAttributeType.REQUIRED) public void insertTestEntityInRequired() throws Exception { boolean encloseInTra = tm.getStatus() == Status.STATUS_NO_TRANSACTION ? true : false; if (encloseInTra) { diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java similarity index 85% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java index e09268b3..30589603 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/PlatformTransactionManagerTest.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/PlatformTransactionManagerTest.java @@ -1,12 +1,10 @@ -package bitronix.tm.integration.cdi; - -import java.sql.SQLException; +package bitronix.tm.integration.cdi.nonintercepted; import javax.inject.Inject; -import com.oneandone.ejbcdiunit.EjbUnitRunner; 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; @@ -14,9 +12,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import bitronix.tm.integration.cdi.PlatformTransactionManager; import bitronix.tm.mock.events.EventRecorder; -@RunWith(EjbUnitRunner.class) +@RunWith(CdiRunner.class) @AdditionalClasses({PlatformTransactionManager.class}) @ActivatedAlternatives({DataSource1.class}) public class PlatformTransactionManagerTest { 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/Resources.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java similarity index 67% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java index 33043842..7bb7a18b 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/Resources.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java @@ -1,4 +1,4 @@ -package bitronix.tm.integration.cdi; +package bitronix.tm.integration.cdi.nonintercepted; import bitronix.tm.resource.jdbc.PoolingDataSource; import org.slf4j.Logger; @@ -18,7 +18,8 @@ /** * @author aschoerk */ -public class Resources { +@ApplicationScoped +class Resources { Logger log = LoggerFactory.getLogger("ResourcesLogger"); @@ -33,18 +34,35 @@ public void preDestroyResources() { @Inject TransactionManager tm; - PoolingDataSource ds; + private PoolingDataSource ds; - @Produces - @ApplicationScoped - EntityManagerFactory createEntityManagerFactory() { - return Persistence.createEntityManagerFactory("btm-cdi-test-h2-pu"); + 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"); @@ -55,6 +73,7 @@ DataSource createDataSource() { res.setUniqueName("jdbc/btm-cdi-test-h2"); res.setMinPoolSize(1); res.setMaxPoolSize(3); + res.setAllowLocalTransactions(true); res.init(); log.info("created datasource"); ds = res; diff --git a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java similarity index 93% rename from btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java rename to btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java index 5bc8f2d9..90fde386 100644 --- a/btm-cdi/src/test/java/bitronix/tm/integration/cdi/TransactionalBean.java +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/TransactionalBean.java @@ -1,22 +1,19 @@ -package bitronix.tm.integration.cdi; +package bitronix.tm.integration.cdi.nonintercepted; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import java.sql.Connection; -import java.sql.SQLException; import java.util.Iterator; import javax.inject.Inject; -import javax.sql.DataSource; import javax.transaction.TransactionManager; -import javax.transaction.Transactional; -import javax.transaction.UserTransaction; 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; 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/test-context.xml b/btm-cdi/src/test/resources/test-context.xml index 2b3a3380..7f939027 100644 --- a/btm-cdi/src/test/resources/test-context.xml +++ b/btm-cdi/src/test/resources/test-context.xml @@ -34,6 +34,6 @@ - + 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 From 62afa8c76b8a35257ec21f9fc25180fdf695bf15 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Wed, 6 Dec 2017 08:44:46 +0100 Subject: [PATCH 5/7] btm and cdi --- .../CDITransactionalJPABean.java | 88 +++++++++++ .../H2CdiTransactionalTest.java | 137 ++++++++++++++++++ .../cdiintercepted/H2PersistenceFactory.java | 62 ++++++++ .../cdi/nonintercepted/Resources.java | 5 +- 4 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/CDITransactionalJPABean.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2CdiTransactionalTest.java create mode 100644 btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2PersistenceFactory.java 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..09e3f35b --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/CDITransactionalJPABean.java @@ -0,0 +1,88 @@ +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; + +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..1371be75 --- /dev/null +++ b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/cdiintercepted/H2CdiTransactionalTest.java @@ -0,0 +1,137 @@ +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.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 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/nonintercepted/Resources.java b/btm-cdi/src/test/java/bitronix/tm/integration/cdi/nonintercepted/Resources.java index 7bb7a18b..834a6899 100644 --- 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 @@ -28,7 +28,10 @@ public Resources() { @PreDestroy public void preDestroyResources() { - ds.close(); + if (ds != null) { + ds.close(); + ds = null; + } } @Inject From 2e748014b693ebda4dc9858f94660c24c0334d76 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Wed, 6 Dec 2017 15:02:01 +0100 Subject: [PATCH 6/7] Some documentation, extended Spring to include datasource, fixed tests --- btm-cdi/doc/CdiTransactions.png | Bin 0 -> 62423 bytes ...PfactoryTras.puml => CdiTransactions.puml} | 47 +++++---- btm-cdi/pom.xml | 31 ++++-- .../tm/integration/cdi/CdiTInterceptor.java | 41 +++++--- .../cdi/TransactionalCdiExtension.java | 8 +- .../CDITransactionalJPABean.java | 1 + .../H2CdiTransactionalTest.java | 3 +- .../cdi/nonintercepted/TransactionalBean.java | 1 - btm-spring/pom.xml | 40 ++++++- .../tm/integration/spring/JPATest.java | 32 ++++++ .../tm/integration/spring/LifecycleTest.java | 5 +- .../tm/integration/spring/TestEntity1.java | 50 +++++++++ .../integration/spring/TransactionalBean.java | 3 +- .../spring/TransactionalJPABean.java | 98 ++++++++++++++++++ .../test/resources/META-INF/persistence.xml | 22 ++++ .../src/test/resources/create-schema.sql | 5 + btm-spring/src/test/resources/import.sql | 5 + btm-spring/src/test/resources/jndi.properties | 2 + .../src/test/resources/test-context.xml | 37 +++++++ 19 files changed, 382 insertions(+), 49 deletions(-) create mode 100644 btm-cdi/doc/CdiTransactions.png rename btm-cdi/doc/{PfactoryTras.puml => CdiTransactions.puml} (65%) create mode 100644 btm-spring/src/test/java/bitronix/tm/integration/spring/JPATest.java create mode 100644 btm-spring/src/test/java/bitronix/tm/integration/spring/TestEntity1.java create mode 100644 btm-spring/src/test/java/bitronix/tm/integration/spring/TransactionalJPABean.java create mode 100644 btm-spring/src/test/resources/META-INF/persistence.xml create mode 100644 btm-spring/src/test/resources/create-schema.sql create mode 100644 btm-spring/src/test/resources/import.sql create mode 100644 btm-spring/src/test/resources/jndi.properties diff --git a/btm-cdi/doc/CdiTransactions.png b/btm-cdi/doc/CdiTransactions.png new file mode 100644 index 0000000000000000000000000000000000000000..f15282f672eb6d55132abdcc9f32521117ded92d GIT binary patch literal 62423 zcmb?@cOX^&|Gx$e6%mri%pTb*T4aQ5E|;*E1~ zwK+>}UM_z_ZXQwd&(}n2PxJ68ZVP#S4^&Ajd@?7Fn61@~`O(w0(OQQ60$~~bFov-Z&m~qj%jKot>5r@V?=P_t1{EL67Onr(%a3_u`d(d0WqssA zqlY*P-(#Z)vkSjdH`7PS01 zZP>dGL3fvKms_OwM#f?;mi9?jo%!`c@gc*A-PgD3V{(DL?TR)P$crX|3`H6FodZ|s zTtWvF@1Fg0<^`!yB?VU4=!NlfceT^bpC*1`rb_=f>fF-%(mG-_W}bV#RMU0k=y)aL z*N?4@qjO8|51 zWt%s~T)SzCf3}5@Bq*_lT-9EYB!hYcGu#0_$E=`<3qS+V#@VKNx!HpI6d*1Yt1(j#iSZf(`Ee<+Ri zPTox(-%WO_sU4S^1^;VrE`9+VoW#H@_A1fg19i$#(eJ5iFIH!2Oq>S?3Yl@6=A9HC zKF-S~yJ;xPg3XGYsV)82=ic82zkaJS)Mxc@Fx8E=u-9nLNe%ciE$`tuUxHtMQK6sd z`6-Y%W?KYIxdN+?+8z91D(~%DE5ax*z4N1Z0cjX_D}sq58=OL7Z4VXPI{0EgxS-r( z*-NNBsKfC&kE@x=y8QYUDp<2KWRJdT49*J_!JtOR7VdJS#^)Q0FQJj+FOT3pTZ{Be zOiT<6537h{b^OGt-&S`=S1sZeY{TwiS0h7i`Frhe zE%|IObJP&;=2j+9lrt4>(rJ22URn|fyR0`-wmFLTfWexv;C5K+PNoTRaGuL;+@{^1 zXWK2U{(n{%@BzE8m0MtQ&0*llJV$#9ZF{rB!^7JXC02$?LwAVFmh>L>UTNi5?6Ir& zwoKK&Js|-5+rBfPR3Y^>O}TP~IGw*%i-IW~IP;sVL7e0c`WWZ-@Gy2A<(1)PZ$7k! zvPZ_L%I?i&d?XNu`+SZd?K;);>dd*V#lc>~?S@*imm9YdDzR8ZTx(3|xX{T~y@pk9 z4cUVQpZN2~W&19&7!?*!tDfE8-M#wftd_tSp12}H6gbNXF>&#&A0gskl4JW{EDEZt zp{WcKPE(^B-~^I&E9h;LPFR2`uGHtSyb}DwVmIi|pWTuFNc`E_9NL)i*mU`L71LW= z?%smk8a?ckx#jzHyI^T=@pz9dZRN_%y`u=e*Eo)!#0yG_1)8JRjp{4ZA#s#vz=4mc zmZo@er1Qa~g5NS>VCG3P{oLPaB4JWDrCix>UOC>#k@s6JZz>w|`aV#EHl*9@zCFk@7*xK zyR|b|Lic`Ri3e$0uqI$;+0%6L@%x&@r}GfUZPuE|ces=F29;e3Qv6C=QvIqR0mph% zP^`5%MH9DK(53on(cZ(SK;x$=-g-N~z`uAsUZg}o*9BkP|HN8|KldS}==V~arOP8Y zjCz0U%&M1mRYkT}yK8YyER4gv@#S3_jT!%q(xN$gboJK6`nk&E!b}uR=wo7!N)w1tKO zVb9Fa4_K?oWu};CgYwqCdy&g(v$XOrcC9U;ZijK9xE-BAPK{;jgy~Ed{vep8K^C8J zmvGHxL3f`s+oH($;hw6S&Ja<24AM}2E2ab$VDGD>>&jL5cpY7}_92E)F`KggnRa{`&T+sOS!A)9vz&-;lMgS29S#d&gnV zr6IA+?_v=+SiaiRk`nnrZK*(~B7x@Eg@+SayLb~HwV=)3)FU07m~dUM*tYF_#Ibit zkM;OyFf4iXebI{_+Y|8MirCervGCZ@-TmFAKrv ziQjMUII_@~Cc%Ekf`wQ#@gAgTT~MnCzbR$jlBHpbOM4iyv3qFmG<<*)J1(1?2MhHR z+p+U=&St!;yF0B`X={0u;bQPtT&I}L8~f&m=aw-7r5vMuIVN9cy0Pk|7wkN?IDL4| z39TauVDq9YZY^!~&;F-w}5nb2$8FOqjg^3}haHZ`x?r9wk0^#LVJh&cU$l7 zZ_8V)!{N82T9;;KOmaJ)H~LTAtnc*4Mc3d;Jy#l(;pq>3mMa_ut1)gvcl%iD-G1|^ z4kz2o(zRowct>w5IBQ*^8v$hcM}zKewEgcw6^ zL5J~vn0oiN{OY!NvA4n?$ z=kqZJb;KVWj4Tb;CS>DEEn5$zKjh~VK7`)Y<9-}*bT4jRyT4=5p3oLu=7WcpXJ*5= zg=tyqRj&4aOt)5>INCq%piQH8Hzrs`h3cz+EKxr(XJ?Y zvp!w1-JV5*xNwEa{2$$8HES(Sr0jw#&;)%lKizNe`_bBI&82y@ZTSVQqUasol8jot z>;+Bk{Or9w4c>?ro!@#v-_zGNVMEedyDG}Kk@|q|lFcgpR8c-Dh;F+cGxcFPeid5X z`bSdzvP?b*sojV-Q(V(i#$RqHSDk6rOc?r>tvEK6|M4mR;KWB*)~{;s@fvmAPj;sL z)^zuDQ8Vz|J>4HqANG87bL_s5u8?md#wio98S-ixpVxE{5dEI4dt{ZV(~kHPwQ)n@7n#@pf9b#3z}jDx=G7-9Ip{nkTnAD z<6!0-6sf6$S(uG+@$HU9Yqw&7QHqV6gG^cJtfiRVYPIs4qY8br{fq9yPYbk$6Nc_1 zd4pV~10H^EpEJpYMekz~1&a9FylULi)R`(}O3?xw*uA+Vjj>ymShZpl0#Q!5*N01% ziP!6Ui+P^leq%tlb6y=^-&)*Wn5I)Az=(jd>9FCbmp5SCr-Or6^x0v|`B!C*qwTe& zmcAo-p5}^8&(1@?LQ~9xKaL$VV%8x)-AQDkKW%bMF}V z<(U~=&gWS=>phnyqmS4*G(9j6;eiagUmW1j!Q*- zVhZpt(dudsMC>?)1E*?9;JLh?OKEe^s=Nz5`G*+B5^YzLJ*<^vTU((3>C=#m*IFA% z@;%vG_hcmG3=^DqJ$CfS)lKzXky83fG+A9z)#e_IPoFE*JTW82l#Yyt)_i!|R0ioz z)-dlPf!T-^>(o(1WvEjDmG`JEqDZ<7)sWecuU8BXT3>HM#FwW!t=`y*KvYb*l02i(+fzt7A<$-p?U7!9@5^6@fJ`>tAJ$! zCOskjXNRo z^-wG7hKO8yQaW=`VK45`Y1HTC&aZ2}M*RfATZ-V7eYKuZPcJNkMoP@oW0SO1r7f#R z>|8vul*(RBMR&bSgY`d6>}_xMrhJ}kgl%L$juh?3Zs1=F+=@-N4@stIW{X|)?eZTg z;fJPWL*k+*VzQ&_n0Tb3_4-OXJI*cTK&!HyxZp_po;gbES$Lf^BWKtx*M+7RByM1NZl~?Ox-cHc{h5x`N3Vsn5Y1|Lf>7fW+!P!;=1KX zIB8A(#A+>!AyiYJYifsR-j?|Qy`Wpa=Fqr^Y|cA7+u5yby_`9_<^hocyR?Ay8Y&^p zY8pdGfwk&S7C7Ez#Ebnfv-${=!k^8H(tYCwOMW@2U2R}Jt3z zu(y>W)qWMCu9XTr-*UcpH|nP>Bo0Ns7uuLomehjnlZHRWlR||tN~Wc;HTl&7MrCau zifezk^J-1%*Q*hfF^|C;p{rVBDmb zlJqgR2fV0AttCSq2fI^@g83Z09FkTcCnS{lbv%RiDk|4$dco5l@Fh}b*)j}dHpv8Q z&RTKW5+?iU%Mr9)2R z6n^yn9Z@t4h0=ER{eV20(edXP8qd<=BE0N#R0u`jk_1r7+gA_6uy(pQ49y!@)*DdS z7l{NU>6>8{ky;y{~+t9+tayr)lo>9w?WUVNkae45o z4h=8Cm27n`rpsv{8yWI2CtCgbbhkx7+!K&Ljr3iU94Q?2lSg~67?IVtH&hR`lIw6w z8wBI&8CUwUwcJPXTK&QN{j8*QJgwLontS`CSqt9?luGIETHACUC#-<5m8z&VIDD>iIf`rCTy=h#LG}^ zCv2UQslKaRTPyQ=8(E*4A_9&Q&}I zU|3%5(2bVx9b1S}NK7hEN;*{CDMZnSi&p6Sq;V3ClJ;&3A?$4tyw@+4m6dsTdU6`+ zi{8)`Gf5iekRHPOJ0^ z#>ZYh)|%<%p#!xk0F-pkh?h^B5O}z_uG7=U*I_e_FJHcVSX<8|3}H&kFV6YkR*af_ zd`@@pNl8-pt-;FkIJg;H_@A=F$sU4>wJJlE$d1KJoD?;USlVW6ettg0$jDJ{F!v_G z4$Od`A0})-hu0y11xT!h-AS&)bm8Eu_t}h9nWSB;dlLq+wC>b>i;Ef_2>VC`FV*`d z)YKELg0!@>JRDq!-NH&PRl{49Inq1Y|9F>$Zw+2e$CxXg><=g?3K z=-bU_^uJvOEL0!NurCzs>bWwdJ(>*(<9UP zzN-qlg>5hOKXh+q?R#eNT#O6K?qZa3wO|=)OxJBOCmXyl^x%!XmzP)f(4UF&wqSC` z%m=*?3~kn#?%!=LzqLdXe|uS%q~?r1n$cZ8&@;V3+6Bdx4jbJytxV5{Yll2YJHxFw z%RzHaizZn0eqwJeyGFZ;VH>l|$#b%n+BkZx8qItUzM_?8%MS_dc{Q+fphG7hRze@{ zDZcMLq-J3<-Qm2MrAbnRdh+9Pqtd4fH|}9ETo5_4I;>60J@B!{WW+d>0 z5_$HQCBZMoH%dIt=ng2N~ME@t+F~=+xqwsYOW;p$lgaq1l(t z#(z1*{~=bxpb!=O<0vQF3RuJ*T@|D(;vYu}2j+DUt+cx(?&E+qU^HsytZq(3S3|?DX zbC8Qvl6+tHtS~p1OAQ&y^5O68583%Xx#Gk`MW@zxlhDtfKWB<~9Ms>{^(i$q0(RzbosXOQLdXw(e*TJz3IjSyiy*RpR%_<6lpcr` zYHICsbGf#9;0kMNYd5!&nk!Gf#BXhFMMg$u-Q8hhO9ND@D3MoFQYEOcO+@~v6f8Kp z6|f%x*e_e6;t~ksM|hD6a19olw|9g(znqRYyKxpA&&-8ce$&w9Fp-h-@&go5Vh8uj zgYOi_KE36)?|Mwlehnr0&yy2GW&MAbcJ9chF86wl8{sy6=#;C^D*mxt6ats>dxI7sc_>|J9o0{ z-Xtd!9)kSjW7|+a`$&g|0I}F<7rB21a*aQ=@EJ6RMLT503>tdrzh(g9z5t5u>}4Cx zMNYF*;PQ!=_q#|61(^8+fT?qkB6)9wZ5<0lDp~v%mk<`Fif9zeK#EF8^s*CgD^PBr z&vnf%iJ6zT)#_~s-JfxozMh_4D2BKZ2wo0EL1}3z7K_cy{JFACapg*ET%7&w>zC{z zW13{{J32bDMn3%zD=sF+?FNO8?mW5`6-!G;7YBzMqID&23h?vmrVHmS4Ji#653=efuRh|Sf~~mJPFl#*Y>0U-`B^bu>XaADP@b3DMDj(Z(!gvPFEAy z|IqH@;$nI_D;!;2%!q?P^b2%!bOv~$!-)(%Z5P(~Xy&|aSlhz~Y|1U2opB>g)l&k^ z_RUJ*sYTgZ=jZ0Gr_L#=9Hl-O9UU#EkG?*wW7~5BeltvQeQk~XVGrYlPrd6g4p1Jq z!@bSs#NHGP1~Z_S`6$8G#-{7`-rk-CbWd7ZIzKAM3A*d3P@)KmuT+mmAYt;{ugu}M zqp+4_z0osquj^a#iMx;3Lj`WB2}%er&8@6>TBr&L%XPzbp-`kZFnottC$=o<+n>_= z@p!Nz^l&+~9EARp+nKsLRvsQ@o6n}Frp}&zWs7WmS+>Dl-K!6`@=4dY5bLO(W!u~G zR9Ckjhs#Dj8O2@_9BcsPz!TL^AL)cW*IGl%g)yp(hRvV5$RDOiZjSx3sjs zNT9WA)hN@_`PwkvI&?!yR5X)*_mf`aGDx{YJZmf^1$j725fEp@XQ~SdT8jjFZN)hW;sZ`<&aWmek)$WSK$0=v z*@B&+^MOdnbr-OYw|rD;U<*mDS;Ng5Nk=@6=s^MFAOyt}qPn}gL41sgiu(Tj`xJ}b zy!zs=G)F^yxmAJV2%Z&xF|SWJKxZ4!@fE?|KVmKVvLXjnS(RSNZ?6qd));H_F!Ak8 z`<5~&>j6CC8}^7=zgmj*)B5kN?L7(qUsy*43AxE*+1{k=WQElEz3NbGEE0#=TpEMc zr}|BNx3AqEKWfMxqcCw6k+~?>Y&*YJLSdtc@%QzxoY(T;|J{}^TrPipO0(p zl>0(VE#xPt$2!fpH;j-V-Xw!Q7{S5_;tm5Yv1sTtD|sMH(~ro=FPpy|7oxK0_}5jW z=j*EO{Mg5y7bYQj1`VX7v>-Y$ISX?W_!PhhfJZ+KzHpPuUpwbnM$COL(6BoROy}3M z>V6si+jLk0buPHFQxyg5`!x(4Z^=DZR8&j~$nz$Xkrx+lP@w(l*!!<%w56N^!c9X% zgUDC`Sm=cX5!?R5!^3QQfat6de!^vg&dH&C&!G1VMl7M{Pi20EiAhNKRF9>nrw0X{ zUO6j*Hqq6E<=3EqjU?JSIxa0Qr(i^Hgf*BK4I1Dv1u{E_M;{MNlm03Yc}!DBM@O$- zovP4brgW)lv5H}>Rc5DZ>BZq_uY@?-0R(hd;oy8C4-(j0S8MV z0DecNz@~d|lc1KiA;!zA%AF92g~!Gw3@fLRojYd)fkf?NF+X@p(CE%Vu4a6fNiOJKx*g_6&J^88rk00-yaWHpR6XBL*z}>8gr}Y=;#a$%UFN4 zQUSx5gS?6g!zfW<;dF(irKRBDV5%<$d_cNLa)9&=4jMjv%IwyI2hcgoMo|Go1ACLf zSV2=0ZEd!?9{&U#TiX{eii}nE^8n3ZIg}z(pn4ImJ(f*V3T3#Q&7aav4O$hN3H&#oJIWFrps@iiE?q0z8l?i9vj8H9Ky)MkGY?SdI~?|EXO7#wG9p9VUl5CVE_YgLx*ByQLU>b%DCw^)z*ZBgf=XKs}ZZ? z<>yyz7^mEW!HJ1=OOM?oe0ao~l~pUH<>M=bguUOvz6>@U=|2d0KTtGa^Es5yWjLD%$jDg&>q`V4r_hFQ_`Z0p zBe;9HKydr`>v4br?kv|RYu&s)eoSh1(W&F)f|bB zBgai`jL-i1j!*&fIaj%nBa`C>3gdF*T!ZyNs&#Q5Ub|?wJQul;fv4CUT$am&*eEm zKW3i`s_JU#Ur!;FRZaJ-)U2TWkCII!lKcvFgc4E!CMIY3hBZbS12i^VJ2J3CNUi<( zU$NxWeG{(mk8gl>4MM!r@JWPZIeYKZ?>6P1{_PgjVW&t456)Ab4!T61YYb##k-;}! zr=EeB^5EVlGczP%r@M|Cz2vr@c^-gv1H8lU_N(_k#eKlY?YChw8G%&i34Y?bvSfPP zFw>scKSRMN{V3FR$uEQmt3rrC{^aCBlm%hWb#l~hef(&v+|=tgDI~^zMdrjRz|Gqf zXbBJ(f*rPCwL@mbxZEju{Z_B7<{T-gA_@iG1Twot)?;VJu}luTEu)^}$k3d zhy<+A`BRirI;)=_u~NNn`@}*Us*vG)z?(LARy^Yog4cVmUeKfMxh=Y?cz>?@R%R%u zKXAbyf`6t-d&(s!*g3lnkBJc#73Jc%d1e1L7a!jxvezJEPESv(pb-d6JSb-C>gui) z2@Ey^0VrA4anSX6;xq2mnTVC~B9#= zM0~cLjBgU6eviWN{NnOCVRrQ`EfG&D=d$8FXz6@<2p8B$GO@7Lne)s|s@aCMK3mP$ zDid_OeM5~(Z56eSo*qO{-_z3*AW0A=-n8lPDIi+or|uNf&l2)=4YRW1;^4a`-j>@B zDMs_M6cldp37YyG?0kxCiN*90_}e-=tB;RrOCG;z;e%;2wcGO}(_?~ys*+`f5pA5Q zzE4%Aw$XV7m71nvE2~u+gDGJC>ZW*njUL(^KGOBnk~=|PYOk%;2Vdit3^;cUxK*gf z;tp0TMg%BES@!2*($gK{*yFhEp;c0>(E{f`#pCL2UV^#QXO=kU!$4srDkjE3_1;U? zNj{t~H`mnJ34+BufdJ?kdwY9TKLVu8VQ_hLJSiq8e*p{hHai^0XwkVCLqlhLn?%iO z{r++#RHR3kKBA-rNQ&A9JO1qlYjHjnu4PSfK=o+DruaA|9yI?{kFu6w&eQsxFjzc& z^x=a&r(s<4;8-<-UrnfkTZ^-w*U%jXf$ULnF$>YV+l&BSXttj}bzgll)IsuQSvTZ? z$R5vNlovo_A3l8e{o5?-F>B<=$cO=7wn8xqRBXXx&Dw`&RtAmYDq_?k?&6G%7r6yZ z3p#yPs{0YGPnxdY?oD1;CDS=TxO=#JC24$qeVc4n z1R9!^6;rymG+m5?Un32OJVqOw z=Q~isR*>7{$DV5}4EQV!&XQ?ae_tgIQBj@d zPEoY7_X0t_8Yp$zF_|j3ELmZe96A+9c6yR*?N0p_M^!y*4H_{JWR^N?gnVXl4~Alw zN;3JqC6Z)2W-t`m0nG1VAV2~XI3@aQE_OdrIiD12d3|pYBnfX#j+N|2 zEZ`g}>Jr%knFVQV-M8wmFb#Q(=?-_+?)e0#E^Lp!SAZXGVnMtyKVXH50C4GjhE_pwJt;K za7d#ZkUa#e{+#A-DLwHoGZ4`4&#+ehQWD!*8LxGFZ*v@4oD3wwZ-HP`(1r8ne(vP&1|Viry4a^& zQ=2bmu`0}z%r6oY;fD)BuT3K1aKLNo}Sy@?MKW@z?8~j6|oB)P9q~@ya0n{`3#fuOKr<)rLTLrlTOrnZ9zSk~g=!WMl7}f}j|qDgszA z1mdF)4gZJ3%splrBsmL7P!eks*453;u?DaLpdl+W^N~mxwNkC(PBQ1C{Whlj`Bp?{t(Go_8ay><#QS91AHx4?;Q zQYTLr;J(G{WmkFg*B8be~EVAU4=@oapD>yZDL?pYT2!_~fh`J#C>1!Bvf|L>5>agRY% zYrdz6FLjPQD1V4HRMAAG_~1_JPqvVKbQNq^#;+KKbguOsZ{Mz#qs;=3wOmTvcwrJi zJ#JRbV)>mbHY!*43sM7e;sS{)MRK1kkW>?ugRWUJb#ge^>@;P)@z*?0Jxh}mrjm}A zynmB~u*V3nCDHP4eV|tKd>h2W#s@|Q5%#_b&vjYV>!b7+^FIRgMsz2E;x~~`3sc{a z{sY2DzrO=D@~7p$cl&2YFGT|~Xl`k`|1q)NCpz8Q>E3|>u2t46z!R+hdV8MZxvXK9|rIq&<;8VP1Qvi>!yzJD7H7_#o_O8gHR1ErL@*lrcuMb`9>UyfFsR_gn z#f-th!BsC~T>r?(;`aVIGO~9p+dL->F%b=)Upq+hV`O9m<&(JJ;l#w*$}&Y`5hS1qB@~ZNucM68ppkthBrwxD?QGsBZmJ-k;sQBo!|i`HoRl z-PYEYTR54v5{KxatU1eXo8{uePt>c}%TeE;k*$=J0TJuXiTss;<+s%)y2`*5KdrcK~ zbj|%CPY}&OtMsv2J{5O`bkwLiv8u#srzyo9PF*BY9YpZVF-I6G+1jd^Nc35gtC=1V zlRwxQr=}G!-yrUF(aBz$xPm#{%y(L@+$b47`sP{a$Z(2SB~hMcZX$no`1ZxNW%(Zl z#gla-399<_#E;lSYVqzLhzghy&X3ENh;slu-bqcPBjgn;gcTvKLEXtP#ugXY(w+hS zooIR>4wJvRO5Cea5N86fT@XHQq9^jIZ%Am_nn2b1<{I%Ef}BYBf5UcM8M}E(m1b&r zc^Pc;Y+bsczATfzIXl&hha|iIC+PDjf5M|pP4)C}7A;_D&7@&ALF%Dd>TF7s45$yAr5>g|b#`_JU=!U@xcBPSD-gfF1M>p99cjtswSv0W zpn*-}u-WunhpEp-a4Pj3&=p)tT=vh~%o6_d9cB5)HKl*vg56DR<6rWx-0ly^T4_@_BvYtVRrgDyUxw1Oi+O&{V-aPmGL=R8>QX3RX3>j=sJOj$?dn|M1M2Gb;0oi+p^1 z7>90~80Ito$9Yn-VBWG!s?D99cx!F#=qeQvARcQKJQL!|D858h92t3)AD+JJE7xxH z5^&!$x;jv3vT+#mnb+1AGGre9vrW=rFkL;p+c`iUL*sb|!EN`3#>atISka4$2njJ0 zqCVLI?dzk4tWjG_Oa1}>t9xLv|FeOpUZc1_yS`C8>zvBECMIKl{`>*qSzSH6W{A20 zxFK)`A6o7Uy?8HK0Zy68R7G#TRZ|}ypQWpr7(`myeGwqD0oj5|jD}#pbczCIUl7_f!Rxak>*$5nE)qn_)2 zM&th8KnRn$*pj*$z%>0GOH1v~rT$L_{EEkp2qlvHm7zqgpEH2FqwaOpE-X3p~ zk9)1%#XCa$n`b0U*CrG8sf(Ut@0o{MWVJFI<;4+i}A8nYTbrWSoxo&nHaJEJPK-Yopp3TU}*yBlF z^O6OuH`shY%cy{6f+avUrUML!#wRMVi=sOL=MDUJY|P|+f}>9SW*&{bogLKk&&bkiCEAwqES0O-rt|qpJfPh$`ne{Usg{J1 zj)8&AJQ9giFVaE*7?MZik9dp<0H{uYSHbcUeL-u4mu!m8mH%TddN>ZVDCnRsA8quu zSGCFh!ul>^*w!~))xp9d^Xpeh>ADs;FMMEd(3UnU=HS40);pa@L^Y@KaWF-w)??B@ zT1iN_k(J9;B0vxHMGx`Fs?F8+oS$1#gPwmc6zGrxMHVQUh~$U0FsdLgPq{+}_&g35 zyLh+m+0z-oH_dGC17FR0+QV!KSn%eSmK&*a*Lk_P3SU40O|Mk|CJ#aaHFuT`j2{jG zery;IDR)0zf;u|#M`j6^UU)ymP5_V!Qer8w(=mrURQNaKoM0JTQIaQz|?m#m>UVFl~hl-1MuY?KM9UB5XSL)Na8UV6Cd@1 zq8=n3Waz&Njo!CF2dFd@CgQ&y?1^3dE7Tb1L-Y)JjwvW0!Y5iCG2#HokfjF?evo#L z*~N+!)Vy2U+r3dNml3U(9}uGw2=}&mNF-zGoJ4-ox7qcoWn=0JF2Rp6>3F;h+(WfGE)Y{@Vo<(0cQq z?}?PVn<|EeSC5N>^87VAI<#qR4e)6Qn;Qsiu5^ts&_BKo z3VDcvQ>LtIHmHN@>hANey{z`Htwkc$3R+_=9`NYV+u7O0fMycj+7|t?loSd74E)#_ z>Qaj-kie^ZAU)j3EQg_T2^5Q;-;nOo=VIGP$LgvoV_uc+Ok?fVpIBx`qU_KG*8ZgO zYfvZ=uzd=Uffd#JpCCN6$4=def`^A+v5n4Rj}vOfW?G61mi9nE$qk&f3FZO;d#YXhCP)))mgFLwiVa*u!`Siv*Bk!@~viG}G(54uq|1T1J>~tg@xm zTUd7#)C{2hoXciVRgV%2kYkMb2vN@f&HEV!CT^RZo10rvG04;(Ki%d474Plu=b(;) z0!=<)RdOiP6=ae|P9{scny+66q#H0S({WjbyC8iG3nG*1umDx5r6Gp-ya_y}92)%5 z-5dXGz%-z8wCk}BXW>bCfC$-Lo1UbuHU*2Clk-?a_LafkMryI&*%B23dE|W(UP9?@5-iz! zTF2jy@%r_`q#kA;{n+?ut0@c66SOs;xtok`u?;?$O!YV59!i>NY-kWgD>OTY7jk~q zDpxkr$Ce3~K0(gOlBkIRBBH2Uh@YO|8c9j?UJ@KwvfHb6pcyhZm)Rz?8Gwc5?S5H+ z99}yPtN%3RWG0pk26Xq{8sWjKct9OcRaNDqWwQx=0$#WTWTtqsHfEDi-*=_+cxLw; z)@tw+**{n08{s|mP=h8^H@dX8R;lz{ezY;NHxrQAH56gleIxr!Rxo zLpKyRq2lc>s3p39585&!L`jr*X|q!-G2o#v0(5%uF@#F*TjKzAagH5Eo-rMF6@D=x zpYiI%=bk}Fe27&Sygkl!$3z~W$Cw;?y-6j@rJc$gT zB5O`IGKecc{yUh^W!h{K{g=2-AdvmjBT0HvaM{`bG9yU*4T!*1Yk@-8UJ$(yt<^;B zVu%t~YN2nW*h{Ub524e2`_JRrk9|s-r!lew zz#f3K70yR`JQ-$8-MJMsbguo)0~P>`l4i$(s2E%OV5K1dPn=lh{fmZi3~z|h+BoQJ zR#8#+OxA6HvJR;~qn`jgqDAJG-_M!$z}3|?ioCPE9r}iihmY@L!p*}|jNaYdjbc%LFRGf_ti@r;&CT8Y z0a6XzMZwbA+P;2m&DApc6$hH>{Mi^MUOOunm!}215jc3u#^&ZpJg_|%V>+#)e>~Gs z1k^Kt890Wb?>j23mv!Pwv%i<7O0ojZ`D&%OQ*uV;&>G zB`Lx-HCb8pZ`xXbW0yV`m8XW(XI>_pCguQ;5QC(cQwty4f<4d*bCKlZjwIt&D7zyp zES!ffKp+sVr}?AaH95pEg()J@P4fg-04ZSH-JosC{qcl(Pqu9FP5MOHCy6P2_^h94 zX)Nu0rZpp290or1+Z^HvdYuv?mkhDOHlcvSwepgtbmMbW(vy%|ozt!~E8yHF#=Cgc zcpWL6?$f8wy1SO8?tkt}4S}7)k*eDM+q&HD?z2l<(P-T&`BYUUyG~q&3xn(gEft&7 zdGCce1*MTNq76kSBqplC+pui|>~3Wu#GL}2vPN+vFwhV-uUCVM`eS$;E$8S}InpEy zx+-i#hu-wHgg@}Ma7X}JT8Zq8*<_B_cN;BTqnDtUAY5M#0);MpRCg-_t;w*~Ml7=% z;$JR^Dsz+F&y*>?`97}d5iRTeNZo&vyMhS(5hug)mKgpJhMigXcH#CeESPZ`cq#UTG*T~7qOFyhNH6vD9 zRLW?Ol-$X%kG4?8eGG0hZ{)!+hB5qNcZZE)VoJVU z*?%(e-kv2{@9;41`lnJbS7l>%u04o}2`YZ=0)j%hMp#mE?%+#j${fgM%>|hBn@XvZ z)EXN}{hPYqr#>$CEW|d}y~zfdVLyWrWFw(U*GG?cOZ7m%CTO-}f?LT5fueXk>bk8_ zSqo@&DUB^QtW7}L1lx2v{)zplvRg{Cu0zc*hl zMNU+)MX&E40tk}xQkB%A3RoGj?;A9Ta)7Qcf9&t~D>dbaeaAQ$XjGVdjn~j3YS%cqkOFjLI@k636c2!Wq z^r!FuG5it{E(rl5O~4+|k|#t1ZggWew&Sk7v$Z>2xywJZIgPMCg>3!2Q6un+HXN+L zr!m|#8DNSFQKF>+fgdp-C;Y{T#25ieW-uo4JMj|`kbiL#D4LPK#m)jrK_*syqR<;kFv56CzjGD>)_B=Wz~iSjfk5t3yYsgimK7&Ci?n0r54Y?Ge)uaEzQq^_qS9qYawsn zCecKfa^DC8{iu1L05#P)(}Mhb3nm~WVK99$Nqr#MB{Fu{ilxyPp;x7pUflW+)8hcFt=51}y~-GW}XI9%Ke;BUMw_-L$M6 zq4jo^V8!M@2U92L-iW`UZVy#4u5it|qS9Ef#ADrhHu3@n)l=FDyg`)dzpVVgC=T=E zWm%iP9ufjtxS1t*1O@f6S{9mG0vrjdwvmXY9!m#xDc6MW-ybB#JqZ3`M*q?324`W4 z$Y8|#n2T|)w%MQwldMDr2Tx9R3j&L#SaTvb-u(C-6cjWwW5u6r0iC2C)l|`+QT9_0huP-Ehh;jNAX+^@#bsMQ?YpJ` zk4{cT-qk#cilTaaYSseUBMHwI&PtJXDTn@#tE#XNqFgRrz8rss9*C&e42}I8VTzl& z{?9x2V|ooi+wG*~kFm+subU!<=`|3kw@8BQotyOZMduR4vs?7K-`<`sikW(39jTSX zV(gknEaD_5zhSt%X%h9*MMlQ7l8{C>W9Dw2{^lIR}fRo_+<~(5n#2`(}6I6DBG>I`; zhaN{7Y7cdyBnDpj9`#P-)oO87e2?60PRaKzEVSPTDOo(yY|^z@)HzWW*3pQo7XBO3p> zHUEvm$&Q0O4V!-XJx-+wv=!8xego4XmwJO0`bd2&I$}8S=c#Rd4399^yELej2`qJZ zpCG3co;R9_8r?qP_8cKraWYVn>Zlt^b@%4yzfJwBso4W}fb32~8p5o?kL%Tm?Ok0i znUdhC11b%n*~aYIwbOqENbx5tHJVFxg2ztdz3jfoM#M0)KjuwTwR$aeD_&24cFV^6-& z*l!F^M;;t)@!eV<2w@|QxM$wptbB$?zncU6ynWkeSWM1)>!P@zdszn#ItA58gQ{#wlkJ0LRzSuMa9KI{{BP5!=@ZGY=)|5 z>R2M%$k8aHZm$;huA?az(UdxG7Z(=*rj5As6CpKqb!}@LRds}Ce8~q*7aAt)FA3{T z=w82mU0T|k!K&>(du2rCwcBTM_8_4ay%}n;v;|yyy~RlRSVkdfW8<{F3hL_N!@;RF zim~cp;#aSxpHyYmu6G7Be9k7g+Udd@Y45HVmXZ#JSuZ1mh=zw*bFSqaJLFwh!xMw4 z^$n9Au1|S?>32)|+u4&L*}xwx%I0C%fM)5j1+A^W5{VRUp_W#N z#bUV{NhDG?tXx4t7prAW=j?$}19u~pE6y=qN;Ik4SPkk-PXgOnP)GPwa14z(Qh#sj zC~nKx z2U0aHL-Kw7&2*RQpZ4pc+oG42ToRg%4}XPijC(uU_NuL|EiaG9)#V~xs~en~>6llb zAzobd;0(_*k)&5Rc3@Yb3@$mA_~`Wk&|w%!7MZwx(|kQmf}F!n%h64LkK5u7fj{Ir z4ZMncd9;~!Q>LPEauHU$lM3a>>}iFCyZ?0E#ct7@#*~^<16C<3y&+kJ_!y~8b<}`w zRF3<6%@`lGctP70#3#n2WNXb}@ETJR;576TYNhl2Q{klQB&t(I9Bt#iToOezI9AWV z7LGg=$bg0BqsiuAsmdA^BVj5hZuWDm5IkomKK%>%XY-c~Wf6dhODA5Il1Gf*_;YXwoT;bu5N0CJrS&0D?Z z$%4N`YL}@`Ooh1bac#OK76wbAiu=#$@~SgaPXH22{>G#{#lT1{r*DPs#u6C}+53<8 zAbJK%#7^6y<;$NNzQHu2!lN*^Un<7;c+Q8`eZbxOu*uj!1hXE!$5*Z`jpz2ltm0So zI?J5%zZH3|du%vtxCnx+!TtYOY>Qjyu?xC!0V(QHZ;JX6^Tce*hlt!B=g~N*gLS{T zzn!>q#`@n6BzpWKPQZP%wREz=^-8n!@8v=+qIeUyEvT*nNeGH4Xr_|&^sKD7tEL8Q zFU1_3iMX_s)QU%?5eSuHVqyT0Z)814@QseaIG6Wud9e?V?i5F_lKostJ@&Vwf z^UGPe8C#x|Zh2Q%5#q4fzazih4(q^|LrSHNp7}8TDs|*&GmJmc({na-ME3?D!!I~E zB&ItaG|Fb@lPs6zIP!Qg~V;lJXl#~?N z&o~{+P4ZL&XKwM&_m8UiLn9Zk$u6}hglyk$~v zW;{P;5+`?|m($o9ZKRx+1q~(f@l2Kd%+aS$95_$o^YH5U%wpJypz67?DW_DQUB9;< z37COIXd#I!%gmc*iMF-n>L_(g&gKC5u6E{UX(rP){{s!9r6yQp0?;?8(x?$x8v!_T zv$17lWVHGZLv_0NtxlZ|8Xd5i22hWw;d4xQ_~MVs^+^LQ0Y;H|7I&Ea^Id}kQeJ{T zO?ltPPHocE_f`-zE@YRBF$s;IlRA5Z{uZ-eRZ?p@YhXYCo0w2ggcdGzH-btw;xn>! z95XM8I3}0xO(7w2fy~TpD~Lq)gD^eC1x!U+P(VC*@UYIEkZhfqyY9bz#GlARri}Hu zT8BHqU7b3WIXg#J*`cg6{#FuZ+WhD(ZKbKNCp8(M4~sqovF4~`f(KH*6;4YZ4U#DO ztL~pBYHB7o^K;}mx0_AxZdrlKMVb_ou9b*yXl9&bafoxmGpmxr6x+y6tPlJOKQM`c z(wAzMaZFBMzY1;_b7t%fN-F)Z?B z7KpUparF}&GwAc#(p)EFOvkL#Knf!=S!UOZ!_W&@yvN;J`@i1dLh_UHQP7Tx^Djj7 zyDg005n{KT8EM=kU7^eb51}9>&93C~li;@4yn49lPDVus9V*H4FuY1fgvJa)jmu%y~*{ldpgp6x7IjRz{v5 zFu9-+3Edi7NFMQNV##Cpk~k!B+hqKy2p(wSf7#CKu6*-x%{{Uy2H!ph2fvu%r}2SWvFsa=JWX8DhgK|36_}Pl1lcO(>(|pZU#O8u zrFApX$X=%iGl`vAQR&8`+Cdk&H(G>S|D**XDEucUNeRP9dfI*+GdC+PAM|~{lk`f* z3S^pZkOn-e`9Ke48}3x4{iuZJv;!!hx(z`wHwlU)P=p}H5c`$7mn2bbQXs$j;q_Ma zpz=kU#fVg>YKX6VGG5ryKk26@OVgz0^9!#$KHvB~i>6TKq)zn4fi(QA z&Wm7o^ff)?p8g~jh7pJT5#RN&-?%cn!^-o*H{^(I{2@KojI{YT&4rvsgTQ~$2=Xp^ zbnmE87BamzT$_bs4OE>L-Md8u;Z|9n_#N{$T?7*PkiTv)VQsy*p3^(1ry7{)-}bmg z>TKNPEh3C?ydo+p%5Myel9iQW+$W)Sk8RZ3=DfPU;RhUJynh-NPnObxq1? z2ld{s2pUj!z_1C3;f=Lw&*y-yp(!i?_9u7;|5uaIUXUmlORx@XBu(^M*^`wTQ@i z6HLWUjf{fwCx$VN9=<(2 zI%LM*AtRfkJx7k0CK=R_K%BW=b=pM)!P#9D>H>3JZEX}0r=|wVbxX?}H#wiKSRRTw z6mA4w(~d>)eC!rg^YRJic1UdSILLya zRy*b7=9VN~CzUIG_6Q}Jrm2)300XB&lWE5j$&`!GxteA2MV2UUyLRug$*25+Gsc3k zg<&T1M#-f{9ZL5HDSuM5c7Wc}mDsli;!6an@BhaS@l*2dPX?+z+HgPnwG692a)aJ- z8TgE8ZbAO?Q=%`ICUzjB zw-TrAu;a0Lyj86x&h6^vDl1woUkhMG795Z)WAAg3fQvYqBQ=TVEp0|?t5gEB5S{t;SAmT)5_Wqa(nCtr5 z_g|5zo>itK-fcm;w0}M^j;?>Uj@R_3(v9DK0YDXJxOsC|_d@^FF^^AsemeuwwvWp= zq+yjRC!o^ejYpu5D+eOfFc;ESv5_To3!PACHd(R<1K;p!8*qv0JN`jV#GPX%|AO_@ zs7N(}CL+>O&}rz2Fa(Gm;$TxBd%V(YBC1(Snw9B0X!gIQiTY4 z_pz+q@!E(2oXTt`%oZXL!phLbpu&}*2rs`1IKQs+1M~s$@nP>=eva(vV+bbxXXjrb zvdZJfj=}HA_=D|&iO*(yp7Kd^1>PAhr<+SsftWkTCJ5q>kTb(!X9|mo$mS4IWr4cp z;^M;QpeR^i5P>pT+H3+5~ zv$YCkoeN~xjnH>h1OrnB!=%I@^1%fW`4i4vy!p#Xy;_ezF70zaEE)rA~^6X zQ0&W}Oicx@GxZqq%%FkYWc_uC$AYS%ImcaG5A-5)rX@RkNTHK!-y@23x|WRv8CCnPsbH1TFkL_*fhGrbYkJeNAvD} zNMx#}NrW+`Ly3da_HR;^Z`Dc!Z6ixCjSh?UO zwb$#1LG3YGIc;LWCkkyg$miN>enW&55on@zS{P2~E*u%zzFshPYX!3{gt&>25o=Ak z2zN#yA|M0#1Jy+;ngQfO8iP^b_If7*f1cJSz z<54jbwT1y7L#AbIcsSW3l%EAgu--!Oqt9oOT{|jzWStEr~i@W&Z<0J8)L- z2A%=4CI>PJYEufd%|mJB+m=>@x?znmm|mM|M%`SL-cg^=nd({kZ(q*ASTFG!USChzMIJrn;B^8P+y*tjtY5}xVN`&C#_b37E&J#oM<1z zb)+`ihvV8R&0ceR_q^l-u4i#%j}K`9<>`VKlI-pq>sJ0*yS^Bw>pK4YpKRthzX_ad z)xH>hu{F>2Mq*E8&vX+9|4e0PDf6!)@^asLXN{~$qMV$D*eR~Qk#3i&UW_*P^H+aw z+ofjqzM$*Qhm@puUytAH>HpMS=J)5#SYq*9x+Y#^U903Jfz$T^&2o1K_0s{}7n&ZV_R6LBv{(J{@o9V)OYc6G+!{ebP$12VuhfCz|KQM`0H}25Y zvXnTKl2#4enJYg%hcB+?BcI)Y3Ep{N5PnX03jcB@-H-Ntw>Qorg-a^%>xmoa#eFxK z?nt?2*2l9*zv3c9w zjEpHC)BPG^a3_QwKiGpA<~ZPd?c_`rS1=IbxU{r1XfXsSIifCSI3x{}UOY|*3!OJ0 z_U7b#RO}zjB6B_6uWy#&W;%y8O4|jm`lT!r5{l<{S399D`+3kN!~EX8Ykm+dz}KFK z=+a~5xVOx3HuVDBdLq}mYZo8%5m+rW$to%tVT5D*o*L!p@tbKKg>g!j)Z5?u)F7Mt zCk<8Ae4D{9_bzu3W?fDy!^7>*`n~p%wF9%(MjznY-cK%6586E8yi$L!{-@kQC>F8t zz&aoox>2K@CD*o;pyZWMO2!%!dm;iSll75hgVow|W+nq`-IsrVIW8`-e)0tN*HDC` z?(}D8(lxBUGHHPWJ3*oB_x?mkBM_=;jv$Y$cfV$P-Mee#rMX#-em#e&2ub82lgRR?Mc zc2Zai>V}418-Zw=VtVN~-iZH9=vWH>#GDInl>ZzVic@vj-7x28&kiTS+df%-!7Nvw zfxUm^_Er5NsT%p(@Q>GfRBV+D4O5&mk#P*S+VS`Yme)N(%8hJ)U?h{37aK0M6TR54 zb1n#I*xAjzfB*gx#zzwr7+>rFQhm7hv$ZH!Be$=m<+6Z4s(`+xa9q)h&BtJi{g~F6 zc*MphK7};phuOR(3@8abZ!3l)sx05EYt*tWG!wN-KaRSVmP9 z4`Sp(&0JDeR0Ixem|y`pIH-GxTKb(&+DTfuT(+jdf9=_0ARwW5H3TutbZ9LzytEUu z_a@Z%Xw=V#w%TKH42Qh0tBW!v#KwjtL7i((O_YZW;lJPq8jztWi;SE-E?T zJ1ZZMV+y--fmq0Y3QUFxpIOmqui?!4@xK?%XfQUy$<)I|r&*^lOXl&l6^?`WT@f6S z9X}cJ!U)&<0|3GYjyOfXTP|H7+Fs=C)J7P!FG_$IfsAuu_ucz&mo}8~^h2*1RY(zz zLsK&$8&oA0*N0T-b5a)by1oSICUj4F41Nh9(nY4dTy5aVan19qHu zVx*uLgO~^8&&Edf%2Y2*RFJWrrtaVGO#iWa*L5_wog+77@1W~uXJ>D_$$g)55n~D} zu@unz+2yf@xjBimCpdRkjIM`&da~(+Qc|%WeZI~$M0Xd(4)nN15Bkhteb$O6nJDJc zH;!wg{TV)7@xo0X^H7fE;flpoIF3Y1bG`G(8Z<)xv47%7rCz6n>EguZSZkMeUqp66~=zSl?w?BZq;N)<`utI zKZ#1gcgr#(m&?cYBBr~a>#w@!xREHGph)!UXHVR`baA$)<@E4b;eYSsOygD6=^h2g3tlLvP+BE-pHI!~&-bV?=<*)2j<^&g0L*^l82!!eQ9N&#P5cf3i2kpNQUyzP6Cs01;(SQ$>mp)D$B6^G8r^2dP=i}C)rBB`@Nly+o$Dq9@Op(6y>$ZQ5U-I+QLFNR zDG+8OL|}uSSiBYjDTK*)i8YI&GS(Bdw|RvYa6|c-nd~`Swm$kFLw_0OBz@2LPD}_m z6J0;t=4Fs7~IZFfX65yEpX z5B+IakcQi5Ftq2s%(*4YCmPvUY?FMh#@i$o`l8 zIf9fiB~RR3cYg)xQX9a>Z(lz!d#qA&%!&-_N!gMjjvo0r6A_l(OawRn%QM~G^m^`? zg&)r`rGG2qpdBaV{$+dc6>)JG}kw_9&aeeLh~6ouyQXC zT)ntSPKu?kdG(mEKoEK9i(r>%d{@Hi_*ClopzCe<%^vlmrNm3^aQ{g~^Ea-T>wjxq zS}N>cwMx@eQ=LgV32s1;pdTuZXI1$srHHauoDdQ4vF$Eqd7Gt#n8m$ zA`0!oP7l5gG~OUl&yaCyQw^$qKwy0Aawh5ero$wy4(5@W$Mrr@>a73f4ty2eyp#c zRV&rCf;gc!TzEHOZerBuJY#=xclnQ$ACGRTkj*FHR_o08Tn zu`VN7+6Z@Dw??dH#gS84ExrRS|oqAqLq0t+5gQvh{ZBaV5$*kBFGsUhEEQjVH6e2xgJ3xU@S>&v~UssYXz{U%ND)j9*_X4^Hl* zMAKm`yXRlwH7yUaKf(pNKIT`K4m#xIf`H-jl}9rcPVtHK8;>r1~@ zakk7b>Zd7Gm6VnHqv#8gU~S-f1jXmo2ge3Zy3V(yCS*^#id258(c3}&z=xeTke>_J z#Yv+Cs7InFgtmc0 zFHA>Zx``6zitR0?g*!iag18-fJp23mdY$#EZ}xgR@&wK43Dq9!cY3Y^J+u99?%&kY zO9&uHTDyoB+(Gq;UxJkEq-6$|3!d&H6Zxmp*GId5lrQuxE3s3`i+ermTC>arVP3%o z@Co`8u|!nukHo-(HZVwLGBLC+QbKE?g-tpa+xzn6%Z<>tnai=3dc#}FjyQfC_=yem zU4IadPGcy?IXsx`D;H4tC$9tOjKm$l)DSWec=Z=7LlieW(GDb-qfSi!&C{g{*g$!k zbe?6ihzlM7l6t|GGovdXllv-DG~w)xT;}oqjnG%smqg8bA~}&erUOSZ|0fm1!?*l{ z*a5?AMCvmJ75~Y5G)%}~!{p}k;j-Z!+Fxpp#5(Rwgf*H;e2SZ%zkl1>rB34oITI$> zROz$MM*#SoIkbh|8`5wP2pJDaySuMKNhdpsD{?>Pt#_>B=DD~ySe9W9D(MLaW1c98 zTC8#ruebsYno4*6lkA#XxwyK*8_62%H8nevGGIxld;eLNTt_P3TBjE@C{l34fIRu$ zk66uB92Aaf3f&fM8w;$S9Jw)~@Ec7%6Mg0Hmx=`s0?=g~_uVd^V3-2Mvx0se$kWR> z_8{Q-Fef3r_^=$33+=ZuYA(? zYT3mcbicX}%;?3NINy;2j)alItXRE}VP9LrB*vGR7M&q!_R45~*K}=NrN#=1CM_B-XnoM)e})ustU*Ht!R$ z&IdMlB-kF=uQEnM&qKw~mOU{QgYmA8J?jh2*(DRThkM@)9~f0`P7+cTYOv)3IwXpHTh@%dJMxJcY1&(#w@l@CIT1=!40<6C<&r!=bHY=+&^Jz+U2 z>j#78Tqiox{XS{9v)WLvL(QRTgX3WCNcSt3MDMi8r97%E5pima3tC+>R3RN5MbGra zxY{52K|M2)VF&32dPb*vA_GzdExTUZJp_8L%n*K;^E9UO&mGpCxs?9xN)n5S^w0V* zwSm4$e{vHfWv-lOI5g?P5*<+tZ*wd4T=!tVxrfYt2@}Q8vz+C*_R+Jukr6((=M3QG zo$8Ub*;7a;@%rn=i1dHl#Qde5EYe}`gBxm&TsR5j9LfxkZp*<~p00e_-$x7abmA;* zEGhRSl<8dksNNenJ1OA(>qoZo2)H=a7T~{#!%aJ0*`FYD4h+u=9KP;h7U`iT`SYPu zPCfGGXX6yjZ%E`sMaRd-H=!l5vMDOjElQN;2U!7HE~4=duUZtM>S>mjJJbF0gLqVY z^*fb*JFdQHd|%%WdtnNN;x3*L9|+Zct)qbZoqo-I0>bPSr1UWEqtPH+{=6zAB-+|p zLS`-f{r#=2_XS|o2$NqGAR@?JAnPaOiPPtF^!5heWK0v>!-7?4zj{FA2hV=&1wswL z^iKWGcfCKRrfeF+lDsmDiUjbf+1VDhwkih=GN9&uM!t{7&+CmQxlL(IA%Oo1jPQ;y z8q_-HmB_V?4s0vwD!7DBH8hm}n5N5vp^-Ccd;)7&9|2}Km`@nwh&{W+Op25LE9z3KL_h^HvB~w5GzRIU) zdzIm1#3_Hnpnj(9^2$KbZB|KS`G(EF#i~=JKiQ!H1MXiihZoYwQamn?;#|h&;U$Fb`wS z9*6|sD3oA)O_5P*;SmLs44V_k@lyG_lR1E~mLc^Jkksu6W9HQ%I`*q#l&f;EN~FT& zqVwNo4VC|8%{2E(1z!9qMq3u|SF3g@C+`Z z9zQO4v@OPI>WRR%?I-$VaQuv~Utvz~Cco2^bB^tKP%pwAC1OVrLS?=Q(s?LW=w4r4 zl%M-*Pgv`-l$v^v;~qBqEDuYIyCv5g)5tUXxp3E7bA$oCx~QjANrM~rYu3SxW>=2{ zxt{_H0DH~#Tc1*ulKtRen@jNuz0K%0U`G*L|MB#uG0tJFQ=v829TE}uvSw9;EJW7B zHp!WF_FF!d+aFtjQEE>1##cra&ZZ}%xldMp6eqb+clM0e3ul@0m=;_Ph5#Ov%B*|6 zsM-TTVh{X3Uw(wfo}eyMU-4mx-AtH7tp35?_iV{xa@VbwzPT?`4KLlVS*A{At?IdAQ3o-{fZ zG5S~7kp7#&9B&NTE5*J_1maduf|&tNd12)hRnWjh8r|qP#k3aGT{*4F9?sKG;{e>7 zqoa8eG@@S0$%nTJoB# z>>9X~EVwF~MUff#xn^Pkv;bQgeyr9AstQW5N^ar_MyJ$nqoM~LN}&OD!c7_E0S=0j{nJ{$3N1v_@k860>>)uZzM4k za$D82*0vcw%8^^k3KTn>i|p}Vuc{wMo*^j_7p6q~FZ?AE#L#NfT>i@xzw(Kid!^hPdJac(*4gaM+_<40-%y+UI&=0$*&&^h^PsCWg_l?_AG>g}Guiw6tQv2j%t3;GgbQvWmSeQ2$L7Pq z{!E-Ad7nq#Q+CG6lvJSz#q#0rWbKR))5TwP!t*m4oKHL-9+RHVD0Um~#l|&h@9v*y z8y$)0*DLKE==K;eUNzPe1_8&FE05sRX!lUMJgT>M*XPm$q|KSkIf(Yyy?}d1DttK* zIY~XVlb4UrbsIx%&5c-TXPKjJx zdC1}7n*Vuq$R%$|cfDFP$aCIWZ$0OW_T-hJu1Ze{^0pn;a_I9&1!7{&x*0Gj6cx^R zH4ND&B=BEYC_bf3clv6|X5{9H)S369BS#xU5(sg9XyQfvv!DOhLtw@DJiTp>ml#dt zJfidTp31pt?Sdr$7nDeU)v&;r?Yn}Wz3bITRl5b^p&@E!I(72)Noql2oD;#deh&f3 zhHXQV8$*tb6cqsxDtABwBSYl~;WYqIGAr)x?dcH}6AM6Y`7eT@BgT*?3dC^tE~opb zv9roSX%40YnYMzY^z>gXN_Z9Cs_Z)#4dMj^1c)$LKakjlhxYAVK$GKrZoyC^eK$89 zJlJM5(arJ#jgLkQ&ruiPYo4c?Qrh{Ymmi!mQ6+LDAu;RqFC=v%GQb@`fxWxC)Mij> zY_x&5nV^~>GYg11ddyxpz||k;l>_J$g0sDyogD;Oa0P!u{Q(b1p!}A(c~((!1hD}o z3akPDwG=F4KQW_caAZiZwDBna^oj`J_Es$ioNBdIf=QJ2SbO1|G~nf-2;gGI#ts9LloKsZojO$zp%Ed+8jQ`#GRNX( zwV=&KOKyfWzXwH<;^Pgi1z9T@ql{vEYV{RZik1ZWbklVRHk85GM5xpy$?1PI)StR1g(7EDs%fmUEv z7Jja7;GutDpsb=?B$s>d$>e14uAl}8t$l6<(I6z$Ls3!DEbx^1ej|8=>l0lWE4;ZW z=k>aDcukG^RXjs^fkwnKSr$Y*8b+9%H_>iVG}I*BRXdE#-X43a5^KQI+1=fZ$A>nY zUx>MW8=8IXOiW~afmDm6OXO1xP}rUW@{7bm!nLcagYYV>d>K0f{v!&ufJcrUk<~uI z%v@Y_8Qzk{`8};O6c#lV#@*~ItATp)PTBLA%l!NbjF6wOreL2oWyjB1^TLUPWo=n% z1rT(cH`hz|0NO)3eLk>*MdVPL=gsTz;dJxs-xiju`0auYVvgu{8>sBo`_;45Sm`=I z%+GamY3FoZ^WYVHzlTj6)9Enu{JksJjOQyWs9bOk+t>ziPL>(`Xvi7W$@nO4`(chS z&&kZvevtMNPke{O{3k&x@rk7OA?iV#l@|3UBmH$(sEFgz@;he>v_}a)qgz7hz1(Jm z0b#E8XRrxL6cn=^)kRg@5PVqYbmMmQg4n3;0u*y)&`T1{hpm5Hy}If<;%yc*y}t=2 zL`M{7zY9D{rA+{b03)Jtai~jqDg*N6&b4Y%Wl!mx!m)?_2+;Tk%@3|bG;kb*@sGD| z-@2~$!hAv9arJw|ZWY(HYGw9^d>BiY^>FCz9(ee&_yFc@@y^#6n9V58M(M<;b0zdNh(k`^2^{$%+U&R0%j8Meh~r1G*x`9LED>J z%A8p~Q_zKQSyLmlfLQ`47uZwFQ?g9>GSkfZ2?Sc7TeC@rypvN>BrV$5&0ffyE3h#Z zFggaznQj)0vVSR-P^dtV^p*Ng;^vpiWtvg3Hh1*yd#${oxx&{JOxa;BL;md-7*_IWI2q8C$rb;5*OcJqW8?-1_1P)rCFh|W{$nugV8f|GsHAbL?RM>uj_}FkXU(lZu?WgWaQVccjqBH z3ryzXZSCvhS}?hh&Lab<)1%uz^Ue{V#$0e={$%@J^#!-@`xN9sBw`FGHR=rk6yd1> z=K6p$cRAz}_#I=YIXWBwWo<6)u(x*zhVKDz!MhN~Nbu@FuVWcigHtbaCvKe#u33`2 zVV7V^EbxL3i8Q0>I}kudOrDICeuOD5&N2u2q`NK5%pj2kg*LX09boK8U>C*l&E1vY zJr~hqrk76;zRa~jd^36iA&;{#OG`Oxv;tR@i;5~$$VqSCKYvwdjhR~-J^v3=THkT; zQ~-z$RST-*g)6L!U3-JpDV`ExqSyG5^a8va5W}#s;DgoGk5&GNq;s;ASjScOmCpkd zahgj=rbXx{02OS8nBcNclqmjUOQBCfPD@n)b(TB(tvy8aL_$Kx$a zN5y#)tLJBO2g^7V{)b@PsY>U$ifaiBwRh_W#^xPh#iOHo%d_nLvk%VLQft4-w73H$NKQC7>Y+c*yHGB$I343KZbDx2s44_cRC4p<^m!`jdxy@L8L4to za%u~^%ej`?6{J@gza(c;TI(qLSJ(lO1F!)MbM{VTukFed&Q}dxbdZX55SrC@ega`P zsjv!J)Q0^3=BHFu$4=K++6*KUGU&Hnz?+tjOfOi3C1hNP~YeEnV zv$EvuK=B?AViFipW+OWz8LFd}aUFmg*^E2zyD4<-$^!wI`NgAB!f_7DuKWTVhYlQo zA@)9L6%~CG4(3g>vjung$Zb~X+vGWyxvFzD2XK!q*`ezY%^K_ zsM7t8?uK2Wk|6`|KDg)HG|!yQCBMd-X51M0%ZD#m{`}5Eg~4(|ysfuv_ZcQjE|ma0 zvymkt?~KERgbJ|U$SCJao&ebtr%Iqrd=JniXiISFI~mJoLEx<%S(H8BrKq3~aCu>g z)wGtT)fg~o>Ff4F>?xUhJfUxc9kO9sly=LSiE)7%XW@gq=uZPLYx}(L5_hy)Dopc@ z1PdN4jegG4#Mx&YiRrvO5fmTI8D#iiuaaE>fOgOa(EcuRD9(-_{rh!nlwid^T5i5H z7W%PUfl+e@lV9sc$g=B)el|L;`R4kCK-B5 zTeNIL-YP=7f9GS1G%?zje0m4Lb$Qg;jm(E9t+sN^&(4}WF_I{>Ivd=wKPs1}5R1uY zU!a$|d1c0$c20|I%}0h{2D?P}wIIp#RYa;0tVAzz zQP88P>`U3K{Cug*L{M;&O(y4|b5O0C@n$R^qn4$zPIGBfS!qv>3!N};v4%PC`9Sd3 zv&n#sUsmYQsK6F7gvzFzqZ!DSpvPY^kH8I`u|w6Wpni~D$PT4BrLqyY>=S69#lQ+= z)qquln?G>w1x&t*7E7q@`$p4y>q;Sk-Gws^pApriot&bBF%2IWAg#;gOzbfpp|6EH zar@M^Pgv{dHN4%aYU1j6=kCe)=Y^Kx0EU;-_@2C zq|R~Y=}NBf%qY_V8NDBK%y&~^=6f+$p+dBeP&l|f{_Aqj`sBLP(NjAvIcI@J5@xxD zX}0w4+4Tf0?wQ_9%(c^}8(_9Qcgy@ljU#Ns+;2~!HN;TM&`001+H!rq=WHkv;J%|H#i!PUzZ#ChhqC~Tu)YGSO7$udDb&#{=GKCe z`6CRnOwU*J^gMsmi9Di1dBV?}j#7`N`Je5EJmzIwAKRHB@-~%zySw4TQNfv@T~(uF z`xDbC0DFOmgQ#iOMtulTG9rIFx}l>5GyGI@1mHiRdM}G~huo$XC=vTjV0Zc0n1WtI z_1-O8xJ5YTSEa*+vI*Z10~S|yQ?F0frOQ#YV#t$|)~2RDhRO5M$rC2|T~45sQ!V5>>Pu|juW8xv zrO_)7NtY8RV(8kX8hp)IP%zSi-R#xg&U!7$gCIY#(>BA*rfr$UM{j@JZ4mtq!Gn#A9W$0rxg z(r8t^$%gJU2y2`(xGzd2SuaiTZ7~6Bk}g^a0rRTA@Z@Ns#HiN~52_Gkqr6;(#?*2F zIS=Z*>r2qQA|xPiJ{Z*h`wdbRfPHI#PQ(58c{_90q5Pn*J)~hOT7O<(xluNmb z+4xyZR0O=adT?<jGVgudd2v_jyMOW->jm@l~|EQrrpbrxBWn33C zeu|<&ZWFK@)yNfxfs%8pZIz_)7 z_IuPYo-t(soen#B_+MBXWyo3bNgssiu{AX{-_q;TFaEU$*b5jS1s!;n#-Qmk%P2oq z)8L+W6v7HLMVXl|d23|mH%n%FwM$wr(cFRjkc|P(YDzNKjGQOTsZ3QZA+&XNvYA4L z5yjm##A$ay{xq-35rqa^t4RUW-9Z+#3tk)u%(YPkaEgsO)}P@O%S{HJ4_aq)nQOmf z>pPq-4rp)gjTQ=>c#`|2i#0##{m_&XurIT@m-WKgyPSTo8)@<8=%w)4Wup>m*V_Ir z%J6J@Yh_P82UODm+lvdq`HGb|zInFTz1^q_2*idqB zq|4Sf(b~=BUF(l4EjyVz`s(xbd$*DLC{%+`0=fbLkcPSQhx+# z8>A!DP=Vm~&0lzoJB~twn}Ny7;tAX|J|-Se#Bp>iM)3TXL7PGh*CV1r9}Y~0**m9M!f`*U|eO_P^DxYj_2i_Wm%)BiEcsvNAyk9WzSd5%!v zJEf3@S3#;xSR5^(Y9lmmKDaO|Cc^|8tF4MlVd^SSCF107siiFI#4qO|JrSu2G3h7huEg|E@v?-j-=OHvDHp(IW&arJ2n9=L13WZ=|Jjn zhc3$achY0AoJ9(29YLhbci;R6+6+&a$$`eoL=H1W%9XqJJDsK~K@r2)NYOWL1}YPF zn`Uz4A1k>b(?Py}gr3Y^{Amza$3>!os$RpKGY$lFphQbIyO2sJ}LkBE$@gS5v z2uQs@B_@ElKN{Xyd{<8ovTdbcWx6Ql-#3m((ne5dNVKhrMZy$dAk^957sbGX9fz4& zAkS9-3xXaa8mD?_J(|Iu{w=q|79rg&&gzkW8~(6}Iv09-HSB$Xj0C?}=xS5zuxWCzVH{(f(omXa4lVY&k z&o(^tr7yKo(o*?@WzNBk!18}9i(%ecBz-#!7{$wX`O6tWy`plkc^hf;83PYO;pCzx zBc^Y@L({ytqIf>M#V-uuLJ(Je#vs0ZxN?5}gB`ZREt&-?q!KP&x)y7v3fYfi+}DH1 zpcA}s{(QO;bo2aMNcD&?M;Sv^RE~d>jzo*jCQt%!MUi?7EdAS$avnAto9Qc6F92)J z$EF$>1TjUos5TPL^54ndX(`nA?*5eCD!AZFUY^T65#4KFGpi%?=F9QiWbFa+@{{`6 zUrj>hl{0;En7zIE#)n7I@YQ{@|E4Da5G!T93kX=w^mofLjV3Mu0WA+Zq-Z2fV=OWQD-X#>!I~ag>BjPUjG$IYIs=Gg4L`^H1Xr6vC(r2MfFrDiM)mjoW{g2UMxZ`TtdY z+lH|g$o;%D4x}@oj^xd{!6+f{=4uGYG?6g(|DkWxh$X|@nx~vrt{yyp;R5h~LH)as zgu;++Q&B2B1_BVT8;QwWvT1lI)j+JE7qo(o5SYq3FtNdRgS@M^o%Mte!=a#RUU6}8 zK&zZ3y+5Fp2&`srWId~>AVl~*{m8}Fpg+mF)`>3;!ljUGkkDLJGZZ^%k0!eqSlu7O){J7dK%OY!)0rw^zUAz zg^^E6X?U%3YNmnbZzVymx%vKqhwl;-@1KWa2fU9Ys~|!kDW_`CX28_mo}iTi@9w^& z>YC437+Jsv?{Yiy$n*w14EL;c$5`G6QO?n>Jcl61(6D#^A@JxFK>e+EA}^v&OiHaT z&#=BB;3C+&KvK}pa|fcLO32X8uM3l2qqxuAwgt%oZ%(6GOtg?3dZ`4&mH83b#P058 zlWtTtitgjAZI=vW7md^a|8z6JBkd=!bA$RBauzf$4Rp`-U&KdSA27}njDoU*QC?gD zOooPudC}3j+!>2nE$PrxXma*cfUm;(R3g1!tuc=(46-OOPe}nH!o>$(r-DoX3@*=h zMFUlQbSX;a98-1$(FG&fO^}+kg}$UMSOOxf2W;tb@VY13H=xr2Dg=wDLM?_IO6gCX zI(OlNjHSFZ*Msx?53e#9X}zEcDS$WXbl#Ku5)xHV0DR8MsboGQoTYQQZhnNWqIQeX z9E};T92$1E>#Z|e)tbpTw9Sz9t*Oz{3ycG#uE+U8;JuM4V~Lm~Ye<|(Ujpby4F%h# z+)H~YNEw@1mgyRL-11Cj{h>`cdST0v5|9M@VB6c;zS#>mTIMf4CLQIV6jc;EEL^CV z?Ha0QVA{xo_ptF};&l4fIx;Wp$W24_<^faOqKQRB>z->rf4;47IJfyhA$5%VWCK5_ zdRFgNH{$;2`bq5*5_+J3C{1@o>hh_$<9j9<_SQc6?`cp(KXuDBPwdaz;G!>D?`$q# z$_Vlpp?lH{e1_4`o~F{rNOrN!m`3YJ%o(w&@n(b5~0IH3z!)gsZ z77PAjvKv4g$c89fMTIPEe{cNfy7QT!!xQfxQ{}3km4A!d!Hs}Yxhk(%3T+E4^igLw zlidaMYu0O@zJ7hxlnmj$Dd@jv6!_}!$YTG#S@@-$DOiw>=gyemQx!nAO-&T2Pl7la+NGB%Q6E)-H|(E`Y~u@-O)03+4{ht{NS9pgd^uqf6Agod;02@fCr=(} zEg0@WISR*pd({OqJpJ6$&HI=um)i3=IR*b8Uta|Izw3Xl>s+Uk@y`2x z-p}V*@B4lTy*P7oUiYO=Yf8oi@H+Fva|irxb=)gSD=O-c2WtO56+0EVE3xNKjAy7` z&}uNS_BbhZM=B}qMx-#S6byp?`Llo1Gr>d<1moo}0Eq%-qETuWU>AU+q^tGjSS6GX zEPqunsbTigt`8PhdQ8J$@vSE;?w_emyNmXEZO%V|qxD*<{F>r+lwz19b-5ORrF`=_~KV`fo-iW5k_fD+r`0t3l_?#q`W zptlVzE;MG>^B5E_Z*+oWCj>h%^3q25ai>Wrql2R(qf}5xNWFBT&^b;^sQX@TxW#2w z|2TT2$)Hjof&c|1K*dAi-=LLeajkJnDOx_&(qG?*t~A~W%C6nztLA!ZEm1sr+9h)y z%e_veBfs21O4QMMe%I+ooeFF3$Sf!_W!{sv(X67Wql#=!5No;gc=mAdUr{^te|&*M ziDlv>#vF65IF$w8g!CR^!cub_!G;dP$bf#@-O|+B2n9S2%=xe204MkN^VUG~J(!!k zL=H=T*+>FDnV=vGA8Bo-|v;3{dQ&$;Hbd~`@pNWtfLOv-VgE1E9J zE%B9CxL24je%WZG@FfvkepT} zb?M;O+$+7>q2@BxX@L74Z_Ku~6DCkpBp#M+vz%T4%u0lFUg`jVx2fSv!>Cs9j|vy%VuTfdn% z2xo!fAddjX{VZG9(_d0@(!mJ%iE9^ho~z#=^-u5fCG1O%XGIgX5xPkm|HVbHQ{KVX zz2C`c|D9e_=s-{J{r@;paT#1O@-_OGGi7DZ1Tq0?k%40a!144s1Yiz9vYYhN6(mL)? zq65Wh;!7Cm>F>W^?+WA~tiL0^%SaN7l;~mvi-A4=Y2h^}#zMDl!M{AzfbWjw9i;q* z#7-$*OUgCCTLw~7NdHn~n#59PcBPl*L@4v;uD_Mh`zrtU0-Yg|EZ1?L#plLDL)?wJ z{ke)Nmi_bMrbHU0A$XTt<4G1OKe$X!3D-b5LdS-3FAEFpAr<5>sML()y{%jOFj{%7 zdG2+T_;mTz4hi4^-Wv}K6>~L_ybH1lZPy#<3x>lZz4CtL;2DN*LtzW4?SW{>;5D3i z4S}e(7=71s>>1Cr&V1A|L3Wd8P?rIL-~5iFPi5AVh>OYER{^g+_$>xMxIjiYbg+4; z&!)XW&9|-74&npEg_fO8?Au0NNq#BIs5j_v#`r`yBJ4q^2(AKBf?EUIxx({si2BqN zU>G2_rz{vyj~-c{EFZUgS4_Dv3W4FDqT@d_B9Uo&#ov^y|4FSgSA^-?(4P4`2=WxS zQ&f(hMABetj<1*OZp|Xw7kU^VY(eW=odxJSm?^RX9vCY3h%usYpN=Ekr|&k_(BU#5 z)fwzoEIT=u$B?XBIxZZz%eEWWJU)rFT3rHE`RKQJhBP=J=K%{Q6jSE56 z8Kp6wiUQ7XYBhHpB~tS2$(Y;NL$D znjB1@(sA!|sKJQQRX`q-^jEL}Sen-!ocpmqI!f20mpuGQs zpne5e8K)v&zRV$ou?!=#P{#C=Jc|y{OKr3)8GL-I0tA{-%T;%alu`zZ+XOzhWE3-D zJa7%Tg2_V?n?3D3$qYi`%d^-fk22OZjpFeQG}IDcY2)1Kqux`R@o2w(a+l4ho+W0! zq^dPuh1kfM6FYviCVuxexvBca|4nX!1;;;DiXJCoFW5UbJ?ZxvC^LW7=rH-~z1|x)nc$n>T%h=VTfJp2DiIKVj}Y@x)&NAlNxd}PcZK%FZp<^_a}l@t^~vZKviW#Ku8F!rZ#RW%%r{srOCG$ zb8~Z$_$Vv?oOK4okb<0L2~-vC-~R&}+9JLN6c6_I8=y%*Im|R@3;>+ZMaKa90_unS zopEYVTVFNRE zsFV(k&}apODPMBYiMhO>T71q9_?7Td;ZR= z4~zyqDXdS2b{y&cTN2L z&d*!5Zp+|mSPBnPLK`XBEift(Nq;Pd12W9onkpyTSnr5NKBx0k`(2L({>l^XMjgap zVAq|&QuW!#Z0*KsGYgq>goL|UxQw#e8(WEu%F-=`(%{pItXIA}F3|yOxGVR#OJ~yu zUPY)GQsoz)S4)T@EPNYn1~!xXQcJhfdhKE|gIC|RSDW?;G2b;8P1~x=fw9ynwm!#< z!2W+R*Fa5?=7IIr=wPW3Z@J0mz1Ny_?AD;B4~%`NCx`6M3auZp+%EOFORE4CK`9zW zKSnk)6qPR{vH{O#Q}IyskWnMfkhHH@eE&uyMtl9|ByhyM8C-MJ!xH6sciRK;Af42e zm)h)pX(to*d4BAR?G9NUS?YjLNi+gG3`B%b{vied7MNiIsgywLERqovy#8VO0OcF? z9r%>U^20WOqeyubJTDHw2x#`>5WNCDSr!S4NSw1nFqv>;1924yhY12V^U$`GgcR3S z%DcOPG3aZ9nJk&GN1EsZU_wyZIrxfvw&$fiKY)2yCeW;?ns8KnSt&(dD2 zgr+npP!QIV>ZTB4{+>iQ2-eE|ta3w{4x+sxfz;pP_;1N8@CZ&5QqrUBIuyr z`o~u5=anoI04W5J9r`<%S!d|!{+m~zW8$`kW4YBAOvo~Fxd*XsNM$?>8+qXeTNplP&~vxEeK!&b%JJ+mz;3aXGq*G!_?U}E|9OQfR8vGlTO z?~&4Os+0}@^3|(V+_yN-zBXt*{{S6~9YT}QltP9oXG$8Fw~kf_Q8x-0zaUuKGeByL zeO{imG9p)Fz?Y%+RbiX<`t@qrS9H)@BhQDE3hT5F6Vu7i_49JJ{|tr4g)cj_CVzIM z!=U55@%doxe9nWRzt?fN9gcA+s?2bWMXwSHkkApNEmh)Q0kV6*D-Zt3 zP>z2}`wwXlu?FteK=f>lmt(mDsoD1e)7CO~&P04TI=VfFYqRb97a8$+uyD+D3A=!a z3f12?$I}DExckZlhg?4_>j}lXRg;TL#n+1e(G&!_b0R9d|3cVS%j!3)DTD;2y-kA3 z6<+Qj)km&5bXW_H+=BqJDkBhSr^BKqec`s_`Kt^_?Kni6f%p|zGszOF{n`6I?1Wl!IHTD8STVHIYr`R%znAK>!M|XYE92~XFPV(u43;rw+&Gs4e*EYsUWoY@7+>eci_KD3<;qTp%6@0_o1Z66e^WS z`+YxF-G44iAsqdMZFoX}vokR3EWj94tse_4S~|a5d@+Hk;7TYo0%)|vns%tv8vyuU zOe{N1%YgAkA(D$AISH*7 zc!k3Q$jCgj0Eoy39t#3sN?5a#5qg}>lp_GEW+03p+V^E>$N*1xshAc6warVU zSepDz3%F$@B&K+;zJ)72+HGp@NPubwCk;y3aQI@6g?#q)^##3I(fC)jp54NtqMR{M zc-kosZ}UxUt3)4Qd%d#w zR<3m}P0!PLh8dTFe?OhY2=u`;@ug1S4=3&S6|b=BIRCEev#Iq*3XT0gF{ii$iO*$o zWwdgNpu93+EU}it>tSkRV?)GaK%{V3c4G-)7egk{Tegq{v>lhZUDFSzGoX>DT@c@W z8rwPl-!CchF1BH2n3ypP?c2nzkx@K>oObH8_{+_P1M#DfufoaQPJ*CV4OcF(GG{IX zb`)|~2)kTXd?8f^L#1qh#LRzT|D?zeB1=qZX8Ih?|KkCLihO(gH%*?;&gbBARNn$z zHnc@zCklP@UpX>Gb_}+K)c(7eL?JfDpt+NGDE)&8%L=5#qE<&DeMyBb6pR1_4Q&-i zB$`GtgJN|wJSg}N$PR%y@+!4;JSvJlt+n&px5FWy1wWG)Wzn(8Mp|YaM@u9VpT}4= z)J!_USh06HK%LLY38~gEWB$!$uFPHv0~gI^D?8J23j$SO0$vR7d}o{dBW2Iy2qUv# zfmFW|QcJ=0)IA+n3U8|qPM+~O9Ciw+?Kq-|P`Cbi3mOm*-o7yu21W=>Q3R}W5LW|3 zZg3{T`&FRU1<1Ztu{I7Ue0EYlKFA{Yp8!B_&YwognWZ@M+A!8VYI86P*23KUSmlE1 za~Ps2MBY$FV2 z0jSm&j_OCfg0LH)a>*!7S$jCbQKw}vz->% z?({wtR;3sRcfGNE-ikIc8+MmJP&SKc$nJ=gr*sw3XGaRX;$0`Q3h%C`nR_|ZuM#in zjhJk2hA&B(=B}=*zq$nmWg-rgJ!`Z(Sn+T%_|RtcSam`_Ab}mBK}WH8h+M(Hg4+x+ z79{lbX2pFS%yY`|7zyhxNilvlg2mVXL7VV(&G($BxR1NLpA5-?mn;VWWBMUiLq@mPwHA z=(7rzr{x5M3k`M_p^AXT*oWx$A-fV`J#zLTtY;w8??((Z>~;!KWJkvWF(qI8d?+O) zSU()~&KHHAw|G~KoEOSwJ@+nG#lNNdu4`+z&35pd&XqtSjgNaX!q1iIDMtumRT?~s zJbfR!4CzRWYWTBE+hQknrb{M3;}#@aKxos2@xY!%2jCF_b1c8I(#yd?)&8j8Edj>% zd*nmD%L@z8DqGpu9Jdev98{K+(LLa&LzoNtF;2gCe35#zu$s2^DeAu#Mavz(fw$fi-KBm%JtEWXwok<|>I z^t>;Irh$k}g(av@C58Eawd4s0F-^uR0O>3)E|!yf+?FK;`Wrx%IEFw~LHW-2QHmdc ziUI1LAqRBaG6tXLEwq%}2$7`S1T=<16!D&h%E?hjwcaoG#iukz(xK*)enXhaPH z6m3G!?>9++R|G;L>3}zzb`S+!8*_8A{E|^5fOfuSW^&FS9OTFZ;;X*1^E-ix2XBNt znj^RU{ruHHom}MoGGjRaE2kf|;y<%^<@|X_3Tp183YEuLAKU|)oDC}G!CmPo7qd`0 z9Z9z@3vvkd=N^w~tAdH9+c^g87Bwcw2XX0gv$A8=#=L9#aC|l;`e{!qZUOQF z-$G3(qd^VgxIR?)K@MqSq`%yT>}Hm*Ip0*Vu=w8A>d_7$Bp?x=J*qpob-njIgH8_L z+z(~_kWdOG3M7&;6W{7+H)`;^&W?JCZS0TUwGATaA)hl42M6c|%0=3ZHnJE10u%1Z+=p|5f>*EPE0AcK_ez=LFk7>wp}uNyMa}XP;4wKgbYoTkY*S zBvPtH?)j`wgxPk*5%#}7SVaz5Yl)L0AD+1Z%4_>ba6YEtO3_%~m>`ktN_0RVh`gwH zw{nq%7S0YLP?9o0Ih&Nrff>vDu@+Nn09*5G!2|%_`|QrcHiGy0o4^Mzsk%Ku<>h-% z81n_FmM1)NkZARA!|X%-PUUt%Cvm_rFbM3N+aoGHrhsSX;+kJ^2lMZvnYj{sY;~*q z>Zvi|y0xCGXNA0+CX9qqYwaKG}sr<}oI>^H>F zY1k+4gsT-Jo|xS_Z^fklxd zN0yg!lYu%H`60S1q~CsIA|~F&ctoOU?Q*5dXm2MT|KY<2n05oPTr$do2Zp@lLwt7) z3h-@I*c%un2FcOr*L9cGxsj|8pdZ)Pq*-31%gG5qbOwW%ynU%jj|s0)=`Rv^Or;y5D&(5o_-D@< z7|ngs9rg6<&xRwmFe^3@S@p*RmDdld{f`ID_gJ3@u<}f;fTupnLwR|~tI0j*Z97+i zN_tAU#o|O1UQHwKldUACt@O=ny!;=#DA%CWEp`?fiOFtYkl#Hl$u0;(nJCY;^F2d> zZ_CR)+}*EX8vc*FfS;C?xf>WnKCl=)X zwpa2v3t;#`1;^CL&W?|Zt3Dm!2}8p20wjUA*AeA2kg9O62PPzJ?{V~gfh>)@3%0NPUcNfPXsh>B9fq6(I%@T}MZCvD9U?TF&K!Qh<@|r| zNEfs9A^ydNUZSTVpTmbzcRM>fh^>LOQsG}*Ff^e$9UYYxBNCYE3(thydBLVe=zC$k zlkWJ+e5-zz&FU#9`ptxo614{Q+q(!JO^O^sHybi9KeRBrH1#=WWp4A(#$w;HXTju$ z$I)(k_IqNybv2JT<)lO9ocPOMNap(Xt4O*25t!^uqj0cfg!-M>XQ=wE zPw>|<^)VfB`u#@Jxu_W7`y2h+SzC(8+qrL2Yi0-kezpTX)j6kwl=8(${tIkQbu1o@wL7ll*^%@ z491L9zp=%IVCy=4caF_6H*^}*kOu<>d!jMw@2Gxxn5)3ahw zV)^}~=Cp0YYy3ZMUjW>YhNkBD2|1?JyWY`8l8J{8auH7q$YboC?Qiu|?>xJa6ee6k zV)jXsh5I^*SK*oBeJ!Ia`lc3CFeC8w1qg;2x#1RN_mV^_(#Xv{cMHoG{bf+)r-vxE zEdZb*>7Yz+n~Klb@R%$&mw3A-lw+k?c=I2jk*D}7hN8}!I!MZR;7{EBBTbC(@gpQXad9wTO zlsvk?)Voi@zFMC!&80lgF2UT#!im3)=M9VwE*J4(DmvSzIh_VcRJPq@+fyWvZvzU7 zY9b!$B!L_D=01mG6Mh?+`9vs-&Or{>Q=cx)9h41&WK5O9V0#{|-Z5v9EeE;O-{`#z zOl^8kS#J0ycx%uT$;#qBOby~Q&KbD3Z|~92?B8Ex*=c5f{%`Nu~qw55OGTeZeE#w_5RR~U%#BKtnlwLD=I2L2x4Dg-U1ei zi?QR=)4ZIVy98gO;4uDur6ir}*rPv?2M#8{Cal(&!YM~n!@YbDgU|??=0NLcfP(ej zCpR7z!hpv)j*Opp8*eEN?&kaLNS)=(5bF-aUd3mM4h+5P;oZdvj1n<+ zBQVeJ9gNGQY>*omB)pI!)o{#ol@27k^)3En6IY+F`(e*vK)dZ}k)4t)q5ekW<*uC1 z>Rv^QkJmBvIeku@2hU$!iuZf@>>ulex9{3`MK0R7F}ty~(PVhORVU59oblIl6?JaO zmwR*8*Bb*m3b?hiGbVXhNMF|$svhqbepVWvlD6ZHlX7Shzcw3;eM+9LR4ywVEe;T! zuhTWT`sJ|OzdWDUkn1ley!lqPdBW-tx_poTrN5n(KkUzDht*q)OvWzDY&|<-`1fOw@<7~H0^X*FEm4greF^&K1qHS~g8l|+G>oqDr#5G!&RQJ~z zioyNQ5g@K-;Bs;f$w9vbd52(&e{S1i>azmb|KkokZRh?S-SPZQ5ET^#OfgIxDQ|-Y zC**|-F)>kmvEsv7_XRxQw0eHL{RFumQ$@@`HpHv2_ov88~tJ_10I!|C3yQM_`6+}az1 zUf-|G--x_k8@9LXH9q3-wiUaV&c(}XZD4SV9~3sEF_3Vw!~dI@yw~ta@~hjc+q}=y zZq2a_IrzxEwWdRLG&ff%0m6r+M(R^UHE=6vv~MwW!_l(q6z3106g-H1>MH{lte3dC zxruauX2YPB7`N}K$!`bxgl^Z{uA|8k?S%6?y83xrEnPCX`VY1yT)-&PUa# za~r*+Et_~<8>j=$ZVKi+QSRY#1{o}-guQY7viPs)(mq=?avZOOd+TJME-Jz_NP|&3 z?~5&?so2~akn~kO+?q>_x2{w{KkcBIOXwz$Z!gj-XmxA5 z23T>i-DFeM{=7FBMk-gBC8~4Wc$VhwEOi;BS=U>ibFR_!9-}qu$`h&Wu%xZP)KG=38`eIxu>*}uKz zPNdL27&tXN$1Jd~YTHmeg)J4(!NR4&t+=*q-v2~ubAxevuTx@n*z#w82*y3Rc*yPs zCgYrR>rEs!0>|+CK%8zQ-!_NN`WW%{G5v{zB}9nt zU~14kG-&qLd(_H<@f66?dH6T~@P7sWv2@sj2M!Dk4Mo5|QBkfLpu%;d9-XbFrm1OK z3q$kj>*~N#2Nv)0zHV!43vDy+-FsVF-m5aP#J=5yx(I&STsQ$uQTqU zWYQ*h?Hc#@d{&5Hfcy#u9~uy`Ioa9mA3)3>62A8lS>fCp$2aWiP5V8^-O$I{q+Tka zFUrK-YKz=={SSD24jBxXeaIn?szT>Ngw#TW>0?z@RZ8p^xLd=JGyigUbd(H6U%T)D z02k_lgA14^1wLw5^PeoCxMAcp&L^Tm??F!ks2hMMsOV^K_bU)+?eDL&>Lq}^04r7o6$UNoUC8IM^yeICKl1q1QtBD)?1sI)-|4QrN#Ra z{yz4fUFs>dfNfBL$bMnQO+0@s{%O3|dNc;@k+EE(HLL!4T2JC*4rAhW0O0pK0Cmi9@|9C1-pa|xlzqxT| zq~XY&aQ&wr|ceK)3nuK{6S2hbgKr6>+QTJNZ+hR0J3 z{#&@C8i|=tAX&k|xO>hY4-Q z)Bnm)9U(&npO&yxn3L0F1oB+1zeLXej$e;n$4sw%FP8Y_7hid=U=&02Nw^yP5V;sn zEb`_ekF;Sg+O0afEhk<#tnc1js4B)ZFP-}HIV2C+&*->XcoqK%EM$#It9|WyV`Z6h zdMMuhl~djwG~#Xf7KD9_t22ET*KYm+@=roXe?!% zss#N7kLd#iWgD{I=(QA<`Jge$F#iEd@38LF3qxIb5BFAwNgsfN!47@Mwx>qsb07|| z0hx{G#%i2(#Y*tisWEf6p=KPX)5b_UgoF>?*Rw@_%<;-!Vhwt<4V#qzRbBNDqaKlV>>2pWqrGM7a1_kWnli^y}S>+ z(}H@sSC0cr6Mz7UykAIBt7DMHGZbQc1Y?_#=mg~4+%9SW_s!yHr_aX+dxf-4#fG&U zqNU|52j>sK74dSnJ{bCNg6-8yS+wsdzK`e81l z=83LvH(q?W_nh`%c~!v5@x&A}!jj2qL8YbbH6Ho~ zkYteV4Jm7BasGXT2h`Y(_(c;6KD9(gM>{E)Lahgmpgu#kN>Psjo`sad}wR#<(ms6VA+9D-+c3sh@vQ16ju}~g$O3@CEnXPgc3~s4uXt2LL z>hhiQ;dvHpNt&;g;d2lG=?CiBQkH=1L@qE1Rr#S|4e_I+aC4+3>f0>-=u1;{t4g#| zQ`Fz1AcIf$>MXIKiYB{J5u1&`+$PPsBF>3qANBSlm9f!aSzQ7i1!>%P$zg^hBKWT< zsNK{xe5cS#6A zN55f=Z1I?(rPEu!HPvBviNk9U^G*+trazUpi*yc&mASmzSi8P7G+3INo@8Qz)+WoT zQ^!t6Mnq79qSN0-faQ~s`OF%vlYf}hq8On&c78p7)Pp7wm68QBd*mHv`r?O1N6i!e z>?q)Xfj2;8_|v0sBS*Vf(e{}n>@A)5(pirP?6pt^Zb%@wdwuNzxj@j)n}^uuTnHdH zzuRD|)Pd&(>R;b(Q75Ubc76XYEWm1Mq756gQ@$yp=T)RpcM%K#yLWFeil*0rIy=8= z!peT~+bdNwHmlLT2D)c*StmW9q!@d-Xr5}i*H4|p?#8QwKbKPqD)3MQ%zS&V_n{U! z7;0I_x%}4i=>FU&o99eSP>9|}O6|9O^7j5yTH3;0(4GUf`7;&btQaO_#4~d;8x0lM z#WT@&n=$6dSxIa=Y8|ZMN?PseC-#*t@nH18$U_zP`#ucS z{>#CiN#PxSlV(D=AHU)+uxbS3sm`UYhK#+^iZXO?|Vnv9?T8 z63ikI%nt@epNisiPby%nzBG^55@nwF682X**jNtBVeYmXG-*T7)V=w}U@JnvYvTLm zvqXSFG(Mt%=LNGGsi-fov7S+no0(x3(*<(`>wZ)bgZyE~0aGWI_#suq<5oVV-(QTY zLs|~@9AW`K{l^48r9hJ!0kDMz!M|-Gwz=oL{732EKA%SU`K5!~dpO<6FB$(7N+icf zh^d7-vkh1bN|6TMrh5SxJ}9<9xMw4*1L3Wb5|~TjYu9=}ZzeJl#QJs7Xoi zRej6uwzfA-5GI1sZE$e)ub;aqalB!^%CCkaFd_pK9AY!Fv+3@~$FCkc;oYWrR}bA9 zVbMoVvWKuVcAP{)2FJgA$;86KkauI};qw0TaCw&@g6)>KwapvZZ9P_o#LO0La1T>| z(2`^&E+?IA%{{^uCZZ_aYJ9(dLmoq$4T?x09_QspIDfu~lZiGVp0oj>z35({y|lul zd;ukf2C)yYQu`?jlNvG(wc}6UZVO=d<;u#*Ef&6c^GVvIok(OjejF+`(96%}trROFV3-YFcT_+ z;whof^MQ3%;saTuFXP6ioE*!ho4QdtZ|9R9KQZq}6Sb>O!f&9xZ0AQBx{Ix+$Hx=L z0Pi4anq}Z_|H*#q>bvRuQ(FBm`;l_Bhe*Q2+CsKWX0eHy^*E2e#$S%?N+SOX2qL&m z?-dziiboHLpLSpNb=qh?SSqeWAcsh#WGv?2?ZFZ;fZ&h`W=a&k==HNkz$eJmlnN#^ zLp?9<{0K`c+@hCtXaCCPspYb@E(p6>Ao>; ztM~5m!3Phuw0;K|muWBW)W1uY`U#HHZW6Iqen5iscJi+5(%n1r1KES0L+;AUf4go! z{E_k|kfujeAVEH@=I-h2Qi*o2kGGQpYhOG=%sCRzF<`61raiU~!%4iM#v{lSA*4pm z1~rd&*lfd*n3-gAPmN&8?}Yx`E}C3tGeFl9EV5od{}8reRuIgpQd{Fg{IC3_U9aoLs{wO;M!nD{e|QES;zdb zB!m8OA}xP|T^J_dI>t1L!;hb5vfk!gR`NhoP4|Mm>5)dKrE0;7rN)fd3zm(kx4h-Y z&lpQ#;4G~Isea#IyWU!>f!Q-*HPS#dh&*?mH6vhc2OVtc|fzx_7 zQw>vZ#XZBwnK0A!lPB->-UTM_7i~Q$MTzLL(Nn!P)x++_;_6Yz*Xu@m`Wm~n2b~B820;oDe)^xsR*T+B0;#_3 z?;UD0j1=p7|7KUL29Jsg)k#cYS|Tc&3x(1xwu%?8Hfi?2JkAEFY(tJ#Aq@NIC?X7n z$?d113GnxaO0i_35EKE$-ijxiNEz+O?fn@Ep+kt*OVG&FhW?N1%30n1}aqOK0hyq?YF&&3v&`jCD~ zhF~>k!AU8MZB|K|;-b0r)-20fxDTN%HN!BD%$aUpn?dg1%(k?(+e^}7gn=~N7S`4Y z-UKj}Jx`?c7B<$fkk2@jy|ZMVm2t}-3eM|g7I0m`bN(XaXrtK=kb!*#h~Th zWx>J^9J0`PNtiYCSDY;E-_W~z&33xrh?$yX+&*TfrdK?*cL2ZIdturA^u-S^(Rv%3 zz=y7Xb}%7wB{soVPTeHhpP;`|L>*2{*SPqpOUdf9R;gLLp;OPSP?PwYO_)-2SAj;u z_yL@eNbP%#>?+B0xs(rr^)Yv#*x`luzjVr4uog#E#8Z0{L(malQ0gtH=5t9WhssQ; zgyYOk_bTx3B^|W`x4|N80~)8A3C(ROF@TNP^W?^jNb0Y;Y)2F@5GlpNZ0c9$gKMn` zjwVM_P4ZoM+vTDJk(z`n03@Y5QYn>(Y!RzbG zWL)o($T7j>L09cRF)a(@Ru2~29jAhayR3M0@&<|*U536ue1>+8J2ZWI;;+cBr59GO z%iYG%zp7Keb0?8sZ}A4ZsrjTq!=F#H4Kgn^YA5*i^*EF~eEoy3EiTgT`gnOs#_cTU z@9KGTU$l?W2NANAOvCp1-{Yr`^Y@8W)J~bvNyJXA)TP1ri|^PpDf!QER~BQ1Xj;AtR(-n|7+nsysK8+5B|?9BaUQq`f^XvX&<091$mixAH_NQC``4d*na4u}Da&|%3 zNf&_g)#_ z#mGzIOa6LC@4oI{2Z#O?7p>L^>B5o5tL6^2=-Q?RjEEO04AOgO?tD^PDojkQs&O7a z(927I3NkD*nORkn(G)r;gm(b*k9bdQar%o_w`Brx=;?{bw7R;Z@rjCRIETHjweQq( z7J2|18Lg4LM97`7<8N$kKsbdktZytmT2?=tr7*;g4 zxl(;f6}7d*3#iu>CSDwlP>KC$cSo+k>tWT{wU(l;#Nv;3vk8|(y>voninz5>Sp9qW zgb40t*(>*}RcPy_i5tfz!@e$Kan2Zj)cxj0ZB37b=F-B*;$F=mwm|XzDBdk>e1@V< z433cXR!Or(Q(LnI-OwGA>GR!1v%F}&MO`*|O*!&Tv35zKfT$DQsT}354mFMJFpOvv zDT61^E`7$%Ea*p14t2pM&K+X6T5z=+7Bz2ye<7Tp!b$vWs}p`vW6I1 zWC-Kb~_wh;ng*D15Yp z1bgG%_`d`CyI5RIg#MU%5S+uNTWiEl8coAjdkJk=q}Tr$G^j4PKT5hYdZO=4BByG9gpyr4Pr|vUt2AYY6)aFL9AE_~v5$OO~ho?nk-nparnw`+uH}HIL-g z_87i5zC7Je!pBBkANk%oV-jspp`{|2LFgQx4` zjER3|+>gyQ!|CP-hVHyj@cMxnEB=yG)59`Np*NkSuK^xpkLyxug_|K_6xQWr!M2xqRRPF zxL3+NwWR(HXDBIFJ%c?He{x(@C0eBK>PZm}@m$vZHFgd7D*D8w)VQRlO3`m^M_L2V zV>4#hP&o0 z!awSIBIZ~y<*1!r*in{#>HF8R47pO3xQ8k1^jMCNg1WD&p}D2Z);F`o(kJQz(@=*e89bsx(lJi)3evpED4$q`h4( zaG&VBf_vFYB^_fH?=NXuI+DR*D_AF#UnPU#eBEJhR74nQk5=RSR(Ce?8ae7kQI6bf zdgk}j6H`40b@R-AD9SJ>a#c0&l{F&GiKW?8;h1nMHf-i-UTZ7Pt7SxI%;NAPIqQg# z*rTAFc=6$IC_doMZA?W4${1eILMmF8cqOL2m0%DQpgt~X5;5}BZ*13yy>9r_;kGLc zGh+sK((7~IV~$`BCB(;y_l}oVMK~67y5_RkI!a_`J4_a+kS=`~ei{OGCe#5(>W{+a z+GXrNQc!x9SW_lnsb#LOPY4qqM(SOi9C(v83OeRTt-kSSO;#PSUH$_FJ1I&-dJq!|AYuNiv4BW=x^<=A? z4)-&Qu6{_ni>tS?Q%K4GD0bFXb*Ma89mdh!# z-V-Jgt8E-7tZG6D#~;bRb?J?)PD1hIHCjxMX-}V5>`Y%{;&H;(;cxg~>$v_}7#d3J zBxr9y8qW|zaCp`sGc0@&Ld8*UJM=Hsw-c|f$7IR{1R2^E^IHAko#R^iif;K2ldde4 zE9~bdo29u)`8C9m$nV6t%rArrQH*NVtgI<1th?jNzFmQTxF;83PG__)(H?JSenVbe zy23h=YxQE{!#FB@v>`d-HGXVvJQ>AuOr9L(bU0+HN%dFnvsU^`3G;WrUGuD;yF@PH z;3{oYj+UEH$S}zK)iG_TP$vJJ$1!f`6n7FiCd7R5GOpC5^X!NW#w-{^j1`oD@)J#; zP`>dVro80tqnKtMt;j!F4wS^=!V3u_8-{^g>74ws86JcIw(GOX(VP|JIlW)+@YQK3 z!{<2?Ei#zq%b!9C4)sHWY))fiGzkL^5hHt?y3{36PTj93a;Gf&vA32#R*)yw)8(!| zoEgGDBD^4+I>p7GU%yx)9jzVbGnaV=p8}D8##HGByVqod;KM_)I^XA7SG)D8?wgF^ ze?8O5q-ouJeD+lv7AXv05r7<)yxDK7r&v4ReOgz5r1GG}wDfAGX@=a>>($B({DS8= zlR2|EADC%lE$z{QJVeVN6hB{=G;^J}p!5>=SJ@|5iZ@K`Kdy|8sEMSd%9yG)Le3%o zdO_6xR7A2|?Ea`={L?Gzov)ZOu&o?iWdg5J?Jnf1j#{mdWJUEf>+<}*o`|1UcBzh9 znMZQe9Z4aok35=FH7)prrNw2L7uOjMcYSqzW+fe6#{|qz6y8#Z+8@IFWsi@kpYZqf;?9^ZT_+=dRT6OF_9ENNTn97!9}s69=ocsqUT}l4krwn8|p@`Sy4wy;f7y z0ra!3V`B9(!=VAKPRz=JF7?`|7aipy%F(?^>>;Xulm(_AQ-1%L$6nq$;}%Cuuz9a@ z#WSt!=L5~^sPhFKsMoC>u_8g_qak%nrxeT!bq~IdmC<~oMqjkT!AAEc^95FKMZQ%t zonGU~>$;@3;amPn;UOG@(wM}?3c!wZ6<)J>ajM>`akED*vRfnBdL=e7R!Au=6>~8)l7#Y)@Q|q+R9qK=U|M!B(uIjjo!_A zyA*p8BaU%RaTsxyd)&l2e?zW}C^D+z@MnNy-=vOdS!dptHoD2UoPn`74juG~;u!tt z7TEwU>yG1NI488-9h%-jRc2Ln6ErG_xjxPuWxil4)oiHm0M@mqsBg96eg7q`j?!y( zGRvpiq|76myYFkcgiC2nd4`Xvo9g1q*ig~O%V;xk5zMN%i0i&Kn{K$WPBPC+8W%mZ z!`o|lqxVZEo$%n&t$S9S(}7PS&7-RE z#%)f_d6y?_UF>~^LmBR>y)SrEyzFV+{teAjFssshuyCLt(WlR&Yls|~uotVj(l+$! zQtWQd@BURH=er|o+yrN1Mm%Dk7A9E5CNec95D5} zJz7sE=SzLjVJ)-LFJK{rTlCTaF5q48?QEAE+`T@+YM>@uiyg>*gXbymBX&ZVBy zx2wNVY;7D8kW`PSbGzW%!;(eHKcK0;5-|~=bnthr=+*vX^PecCz8}@*2_^F^519vz z5A`NLoG_)SdmI`X>gTtQQ7=8f>#q-@oSvEY7`CVj3gBPW$at^6wBC6A<)5;}v~p;2 z05-cm^G#eM^B`IKG)x4C5n!5je+2RFUnVKHKPIeXIN=x0Af~H|b_J^8AwPyJB zNkJF8KEk9m*L+`g@_<`;d<>zbmUViiCJ9gVYWl11?U&{F*sre$VV8`{BgN>kJTBW6 zqan!*a&<6UVRS;Dlaqj5`DOr{6VIW7ex=^7F445q&I9_fpCe32vTnbxoEDS8g=rF^ z$FeRQ_A7EyY>OO1t_Pi*2-O=kKRqzMTvzt;>JIMay)k@`HPW)OqG+*iZM2JMc-qu8 zbIq^3bea8DYNz78u2Ls!pz1(hvhy9MC4copt#cEuHlKILDD@4P#9oZdb6B!;+%5oJ zA)HGyZjKf0H$8=aN9Xt5_qN4Z_w!s$+6q01;g{{x1hT#Fs9HXR3Yf!&e`c3`D*pDb z_M0iEZrOzBt#u~2Z;+cTGKixU606Q+DXMI}4I_{xqdi0FdTZRI6E^~L*^c0@RI)T^ z<;$hk5*YN{XDxO`M8yoq%lTk9S4?%D9W#hu;^^lqJ6Heb%3<8OG|lGx`S`@2`RDSU z&x=i;J&LC0;WE}!2;;YBI2&@b_Z;iTMJ|F_(^<##Uom++{*}j4Clumn_H}>DVl6d@&AOL*;W43w@7_yOzg)ROuAq9G z$62l7!`jc#1|nxz^wow0xU?KA_yu&-FVNdnjEdbF*q>-PJb}AoXEb$gNI6OSjlnxl zDfHxu5)n@2MYCh7RMy?+NL{nVaXy;B2wYaU(^hCs#Jen1$ji&KXTb(HZr=XtxQ&EA zpK~-8-1?rPpHO2ce51oOVUFh`S3c8=pTjMt>ID<-#wPEwZgUU0H@W@&iB7V_0?-V1>FjLe|6CZAkqjvfH8fX=2FQb->rWc`TuX4 ztAPB6KZHFcyu5hvB0#JYyf#;ot#1dil3I*?Wn{e6WOK4lOi->5?FQtt(AJ^+4qp%< z$#D1N;&?UpHAE5R_um7YANl$J`1H=-?v%WNbbyq;m533W*^WSe0zwvYX{Drea{=eu zU)p2PsZABnn0NkmJ%gNrPt)$7ORUK67TEf9P|vdD%$Cw%FjR{X1`Fle{SRS-4l&W_ zmKBwbjnrb3f5k z8oZV3bMs9STWwXJoiOV8^O&b_FiriU2Dy>j`nH~7&gY=7*T^PlI#Ni~J;lsSXU8n& z>HzfS+|jj3s63<^S8NqTwf0gLO5iTZ-$$=E!Rww|N5WIzrW@^_KKY!D7JsVwIfR8} zYP^4O%6!T5$GwkFWLz34cO<|<5?H@m*Z|>Ey`iu{^q2g?;is0D02a_<578QD4dQ zhX;9Loet4Yb`BKWGBoILBlmWZWU|)+zg}Y8esL?TGkh=Y%5|13E~~Rer5v}57h*-O zaJX$adc&ldom9pg?u_p{b;OXk;vd$@?2>^Dd BQ$+v( literal 0 HcmV?d00001 diff --git a/btm-cdi/doc/PfactoryTras.puml b/btm-cdi/doc/CdiTransactions.puml similarity index 65% rename from btm-cdi/doc/PfactoryTras.puml rename to btm-cdi/doc/CdiTransactions.puml index 2a483133..b78465de 100644 --- a/btm-cdi/doc/PfactoryTras.puml +++ b/btm-cdi/doc/CdiTransactions.puml @@ -1,10 +1,10 @@ @startuml actor client as c -participant service as s -participant tinterceptor as ti -participant tstack as ts -participant transactionInfo as tif +participant TransactionalBean as s +participant TInterceptor as ti +participant TFrameStack as ts +participant TransactionInfo as tif participant emdelegate as emd participant tm participant em @@ -14,31 +14,33 @@ participant query2 as q2 c -> s: call activate s -s -> ti: intercept +s -> ti: manageTransaction activate ti -ti -> ts: new stackentry(attribute) +ti -> ts: pushTra(attribute) create tif ts -> tif: new -ti -> tm: begin -tm --> ti: ok +ts -> tm: begin +tm --> ts: ok +ts --> ti: ok ti -> s: invoke activate s s -> emd: createQuery activate emd -emd -> pf: getEmTransactional -pf -> ts: searchEm(pf) +emd -> pf: getTransactional +activate pf +pf -> ts: getEntityManager(this) activate ts -ts --> pf: not found -deactivate ts +ts -> ts: searchEmInTopTInfo +ts -> pf: createEntityManager create em pf -> em: new -pf -> ts: takePart(em) -activate ts +pf --> ts: em +ts -> em: joinTransaction ts -> tif: register(em) -tif -> em: joinTransaction ts --> pf: ok deactivate ts pf --> emd: em +deactivate pf emd -> em: createQuery create q1 em -> q1: createQuery @@ -52,12 +54,15 @@ deactivate q1 destroy q1 s -> emd: createQuery activate emd -emd -> pf: getEmTransactional -pf -> ts: searchEm(pf) +emd -> pf: getTransactional +activate pf +pf -> ts: getEntityManager(this) activate ts +ts -> ts: searchEmInTopTInfo ts --> pf: em deactivate ts pf --> emd: em +deactivate pf emd -> em: createQuery create q2 em -> q2: createQuery @@ -72,12 +77,12 @@ destroy q2 s --> ti: return deactivate s activate ti -ti -> tm: commit +ti -> ts: popTra +activate ts +ts -> tm: commit tm -> em: flush em --> tm: ok -tm --> ti: ok -ti -> ts: close -activate ts +tm --> ts: ok ts -> em: close em --> ts: ok destroy em diff --git a/btm-cdi/pom.xml b/btm-cdi/pom.xml index b41d0676..d3006a6d 100644 --- a/btm-cdi/pom.xml +++ b/btm-cdi/pom.xml @@ -26,11 +26,6 @@ jta provided - - org.slf4j - slf4j-api - provided - org.codehaus.btm @@ -42,7 +37,8 @@ org.jboss.weld.se weld-se-core - 1.1.14.Final + + 2.3.5.Final junit @@ -68,7 +64,6 @@ org.slf4j slf4j-log4j12 ${slf4j.version} - provided org.slf4j @@ -81,10 +76,30 @@ logback-classic test - + + + javax + javaee-api + 7.0 + + + javax.enterprise + cdi-api + 1.2 + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.1_spec + RELEASE + + + org.jglue.cdi-unit + cdi-unit + 3.1.4 com.h2database diff --git a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java index f674a907..04b49504 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/CdiTInterceptor.java @@ -16,7 +16,7 @@ import javax.transaction.Transactional; @Interceptor -@Transactional +@CdiTransactional public class CdiTInterceptor { Logger logger = LoggerFactory.getLogger(CdiTInterceptor.class); @@ -30,16 +30,27 @@ public Object manageTransaction(InvocationContext ctx) throws Exception { TFrameStack ts = new TFrameStack(); TransactionInfo lastTransactionInfo = ts.topTransaction(); Transactional.TxType attribute = null; - Transactional transaction = + Transactional classTransactional = declaringClass.getAnnotation( Transactional.class); Transactional transactionMethod = ctx.getMethod().getAnnotation(Transactional.class); + Class[] rollbackon = null; + Class[] dontRollBackOn = null; + if (transactionMethod != null) { attribute = transactionMethod.value(); - Class[] rollbackon = transactionMethod.rollbackOn(); - } else if (transaction != null) { - attribute = transaction.value() == null ? Transactional.TxType.REQUIRED : transaction.value(); + rollbackon = transactionMethod.rollbackOn(); + dontRollBackOn = transactionMethod.dontRollbackOn(); + + } else if (classTransactional != null) { + if (classTransactional != null) { + attribute = classTransactional.value(); + rollbackon = classTransactional.rollbackOn(); + dontRollBackOn = classTransactional.dontRollbackOn(); + } else { + attribute = Transactional.TxType.REQUIRED; + } } if (attribute == null) { logger.error("CdiTransactionalInterceptor should not be used at this class: {}", declaringClass.getName()); @@ -58,19 +69,14 @@ public Object manageTransaction(InvocationContext ctx) throws Exception { Thread.currentThread().getId(), ts.currentLevel(), ex.getClass().getSimpleName(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), ctx.getMethod().getName()); - ApplicationException applicationException = null; // TODO - boolean doRollback = - applicationException != null ? applicationException.rollback() : ex instanceof RuntimeException; + boolean doRollback = !isSubOrClassOfAny(ex.getClass(), dontRollBackOn) + && (isSubOrClassOfAny(ex.getClass(), rollbackon) || ex instanceof RuntimeException); if (doRollback) { passThroughRollbackException = false; tm.rollback(); } - if (applicationException == null && ex instanceof RuntimeException) { - throw new EJBException((RuntimeException) ex); - } else { - throw ex; - } + throw ex; } finally { logger.info("Thread {} L{} finally in {} xid: {} in {}.{}", Thread.currentThread().getId(), ts.currentLevel(), attribute, MDC.get("XID"), declaringClass.getSimpleName(), @@ -91,4 +97,13 @@ public Object manageTransaction(InvocationContext ctx) throws Exception { } } + public boolean isSubOrClassOfAny(Class c, Class[] classes) { + for (Class clazz: classes) { + if (clazz.isAssignableFrom(c)) { + return true; + } + } + return false; + } + } \ No newline at end of file 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 index 12e69c40..63f30071 100644 --- a/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java +++ b/btm-cdi/src/main/java/bitronix/tm/integration/cdi/TransactionalCdiExtension.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import javax.ejb.MessageDriven; import javax.ejb.Singleton; import javax.ejb.Stateful; @@ -43,6 +44,11 @@ void processAnnotatedType(@Observes ProcessAnnotatedType pat) { ) { interceptForEjbTransactions = true; } + for (AnnotatedMethod m: annotatedType.getMethods()) { + if (m.isAnnotationPresent(Transactional.class)) { + interceptForCdiTransactions = true; + } + } if (annotatedType.isAnnotationPresent(Transactional.class)) { interceptForCdiTransactions = true; } @@ -50,7 +56,7 @@ void processAnnotatedType(@Observes ProcessAnnotatedType pat) { log.warn("Transactional-Annotation for Ejb ignored {}", annotatedType.getJavaClass().getName()); interceptForCdiTransactions = false; } - if (interceptForEjbTransactions) { + if (interceptForEjbTransactions || interceptForCdiTransactions) { final boolean finalInterceptForCdiTransactions = interceptForCdiTransactions; pat.setAnnotatedType(new AnnotatedType() { @Override 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 index 09e3f35b..f924619d 100644 --- 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 @@ -26,6 +26,7 @@ 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); 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 index 1371be75..68a70d95 100644 --- 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 @@ -16,6 +16,7 @@ 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; @@ -26,7 +27,7 @@ @RunWith(CdiRunner.class) @AdditionalClasses({ H2PersistenceFactory.class, - EjbTInterceptor.class, TransactionalCdiExtension.class}) + CdiTInterceptor.class, TransactionalCdiExtension.class}) @AdditionalPackages(PlatformTransactionManager.class) public class H2CdiTransactionalTest { 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 index 90fde386..f1105a93 100644 --- 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 @@ -48,7 +48,6 @@ public void doSomethingTransactional(int count) throws Exception { } } transactionManager.commit(); - // platformTransactionManager.retrieveUserTransaction().commit(); } public void verifyEvents(int count) { 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 + + + + + + + + + + + + From 1f937dbc145060e9564e2dcdfc8b7434d9e4a922 Mon Sep 17 00:00:00 2001 From: aschoerk Date: Wed, 6 Dec 2017 15:34:47 +0100 Subject: [PATCH 7/7] remove compile with jdk7 and jdk6, seems not to be supported anymore --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24f65e32..821c91f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,4 @@ addons: language: java jdk: - oraclejdk8 - - oraclejdk7 - - openjdk6 + \ No newline at end of file