Added: aries/trunk/tx-control/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManager.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManager.java?rev=1737958&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManager.java (added) +++ aries/trunk/tx-control/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManager.java Wed Apr 6 10:06:08 2016 @@ -0,0 +1,109 @@ +package org.apache.aries.tx.control.jpa.local.impl; + +import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION; + +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; + +import org.apache.aries.tx.control.jpa.common.impl.EntityManagerWrapper; +import org.apache.aries.tx.control.jpa.common.impl.ScopedEntityManagerWrapper; +import org.apache.aries.tx.control.jpa.common.impl.TxEntityManagerWrapper; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.TransactionContext; +import org.osgi.service.transaction.control.TransactionControl; +import org.osgi.service.transaction.control.TransactionException; + +public class TxContextBindingEntityManager extends EntityManagerWrapper { + + private final TransactionControl txControl; + private final UUID resourceId; + private final EntityManagerFactory emf; + + public TxContextBindingEntityManager(TransactionControl txControl, + EntityManagerFactory emf, UUID resourceId) { + this.txControl = txControl; + this.emf = emf; + this.resourceId = resourceId; + } + + @Override + protected final EntityManager getRealEntityManager() { + + TransactionContext txContext = txControl.getCurrentContext(); + + if (txContext == null) { + throw new TransactionException("The resource " + emf + + " cannot be accessed outside of an active Transaction Context"); + } + + EntityManager existing = (EntityManager) txContext.getScopedValue(resourceId); + + if (existing != null) { + return existing; + } + + EntityManager toReturn; + EntityManager toClose; + + try { + if (txContext.getTransactionStatus() == NO_TRANSACTION) { + toClose = emf.createEntityManager(); + toReturn = new ScopedEntityManagerWrapper(toClose); + } else if (txContext.supportsLocal()) { + toClose = emf.createEntityManager(); + toReturn = new TxEntityManagerWrapper(toClose); + txContext.registerLocalResource(getLocalResource(toClose)); + toClose.getTransaction().begin(); + } else { + throw new TransactionException( + "There is a transaction active, but it does not support local participants"); + } + } catch (Exception sqle) { + throw new TransactionException( + "There was a problem getting hold of a database connection", + sqle); + } + + + txContext.postCompletion(x -> { + try { + toClose.close(); + } catch (PersistenceException sqle) { + // TODO log this + } + }); + + txContext.putScopedValue(resourceId, toReturn); + + return toReturn; + } + + + private LocalResource getLocalResource(EntityManager em) { + return new LocalResource() { + @Override + public void commit() throws TransactionException { + try { + em.getTransaction().commit(); + } catch (PersistenceException e) { + throw new TransactionException( + "An error occurred when committing the connection", e); + } + } + + @Override + public void rollback() throws TransactionException { + try { + em.getTransaction().rollback(); + } catch (PersistenceException e) { + throw new TransactionException( + "An error occurred when rolling back the connection", e); + } + } + + }; + } +}
Added: aries/trunk/tx-control/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java?rev=1737958&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java (added) +++ aries/trunk/tx-control/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java Wed Apr 6 10:06:08 2016 @@ -0,0 +1,152 @@ +package org.apache.aries.tx.control.jpa.local.impl; + + +import static org.mockito.Mockito.times; +import static org.osgi.service.transaction.control.TransactionStatus.ACTIVE; +import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.TransactionContext; +import org.osgi.service.transaction.control.TransactionControl; +import org.osgi.service.transaction.control.TransactionException; + +@RunWith(MockitoJUnitRunner.class) +public class TxContextBindingEntityManagerTest { + + @Mock + TransactionControl control; + + @Mock + TransactionContext context; + + @Mock + EntityManagerFactory emf; + + @Mock + EntityManager rawEm; + + @Mock + EntityTransaction et; + + Map<Object, Object> variables = new HashMap<>(); + + UUID id = UUID.randomUUID(); + + TxContextBindingEntityManager em; + + @Before + public void setUp() throws SQLException { + Mockito.when(emf.createEntityManager()).thenReturn(rawEm).thenReturn(null); + + Mockito.when(rawEm.getTransaction()).thenReturn(et); + + Mockito.doAnswer(i -> variables.put(i.getArguments()[0], i.getArguments()[1])) + .when(context).putScopedValue(Mockito.any(), Mockito.any()); + Mockito.when(context.getScopedValue(Mockito.any())) + .thenAnswer(i -> variables.get(i.getArguments()[0])); + + em = new TxContextBindingEntityManager(control, emf, id); + } + + private void setupNoTransaction() { + Mockito.when(control.getCurrentContext()).thenReturn(context); + Mockito.when(context.getTransactionStatus()).thenReturn(NO_TRANSACTION); + } + + private void setupActiveTransaction() { + Mockito.when(control.getCurrentContext()).thenReturn(context); + Mockito.when(context.supportsLocal()).thenReturn(true); + Mockito.when(context.getTransactionStatus()).thenReturn(ACTIVE); + } + + + @Test(expected=TransactionException.class) + public void testUnscoped() throws SQLException { + em.isOpen(); + } + + @Test + public void testNoTransaction() throws SQLException { + setupNoTransaction(); + + em.isOpen(); + em.isOpen(); + + Mockito.verify(rawEm, times(2)).isOpen(); + Mockito.verify(rawEm, times(0)).getTransaction(); + Mockito.verify(context, times(0)).registerLocalResource(Mockito.any()); + + Mockito.verify(context).postCompletion(Mockito.any()); + } + + @Test + public void testActiveTransactionCommit() throws SQLException { + setupActiveTransaction(); + + em.isOpen(); + em.isOpen(); + + ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class); + + Mockito.verify(rawEm, times(2)).isOpen(); + Mockito.verify(et).begin(); + Mockito.verify(et, times(0)).commit(); + Mockito.verify(et, times(0)).rollback(); + Mockito.verify(context).registerLocalResource(captor.capture()); + + Mockito.verify(context).postCompletion(Mockito.any()); + + captor.getValue().commit(); + + Mockito.verify(et).commit(); + Mockito.verify(et, times(0)).rollback(); + } + + @Test + public void testActiveTransactionRollback() throws SQLException { + setupActiveTransaction(); + + em.isOpen(); + em.isOpen(); + + ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class); + + Mockito.verify(rawEm, times(2)).isOpen(); + Mockito.verify(et).begin(); + Mockito.verify(et, times(0)).commit(); + Mockito.verify(et, times(0)).rollback(); + Mockito.verify(context).registerLocalResource(captor.capture()); + + Mockito.verify(context).postCompletion(Mockito.any()); + + captor.getValue().rollback(); + + Mockito.verify(et).rollback(); + Mockito.verify(et, times(0)).commit(); + } + + @Test(expected=TransactionException.class) + public void testActiveTransactionNoLocal() throws SQLException { + setupActiveTransaction(); + + Mockito.when(context.supportsLocal()).thenReturn(false); + em.isOpen(); + } + +}
