You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by ti...@apache.org on 2018/01/24 18:39:48 UTC

[aries-tx-control] 03/03: Tx Control spec compliance - support passed in JDBC resource providers in the local JPA implementation

This is an automated email from the ASF dual-hosted git repository.

timothyjward pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/aries-tx-control.git

commit f6cb1acfc2a529ef8538f4273ad353bd2f086de4
Author: Tim Ward <ti...@apache.org>
AuthorDate: Wed Jan 24 13:38:25 2018 -0500

    Tx Control spec compliance - support passed in JDBC resource providers in the local JPA implementation
---
 .../impl/DelayedJPAEntityManagerProvider.java      |   3 +-
 .../jpa/common/impl/JPAResourceActivator.java      |   4 -
 .../common}/impl/ScopedConnectionDataSource.java   |   2 +-
 .../impl/JPAEntityManagerProviderFactoryImpl.java  |  42 ++-
 .../local/impl/JPAEntityManagerProviderImpl.java   |  10 +-
 .../jpa/local/impl/ScopedConnectionIsolator.java   | 291 +++++++++++++++++++++
 ...xContextBindingJDBCDelegatingEntityManager.java | 111 ++++++++
 .../impl/TxContextBindingEntityManagerTest.java    |   2 +-
 ...extBindingJDBCDelegatingEntityManagerTest.java} |  51 ++--
 .../impl/JPAEntityManagerProviderFactoryImpl.java  |   2 +
 .../tx/control/jpa/xa/impl/XAJPAEMFLocator.java    |   1 +
 11 files changed, 482 insertions(+), 37 deletions(-)

diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/DelayedJPAEntityManagerProvider.java b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/DelayedJPAEntityManagerProvider.java
similarity index 94%
rename from tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/DelayedJPAEntityManagerProvider.java
rename to tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/DelayedJPAEntityManagerProvider.java
index 3c3d516..71a70f5 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/DelayedJPAEntityManagerProvider.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/DelayedJPAEntityManagerProvider.java
@@ -16,13 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.aries.tx.control.jpa.xa.impl;
+package org.apache.aries.tx.control.jpa.common.impl;
 
 import java.util.function.Function;
 
 import javax.persistence.EntityManager;
 
-import org.apache.aries.tx.control.jpa.common.impl.AbstractJPAEntityManagerProvider;
 import org.osgi.service.transaction.control.TransactionControl;
 import org.osgi.service.transaction.control.TransactionException;
 
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/JPAResourceActivator.java b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/JPAResourceActivator.java
index 1b3ef75..600923a 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/JPAResourceActivator.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/JPAResourceActivator.java
@@ -20,14 +20,10 @@ package org.apache.aries.tx.control.jpa.common.impl;
 
 import org.apache.aries.tx.control.resource.common.impl.ResourceActivator;
 import org.osgi.service.transaction.control.jpa.JPAEntityManagerProviderFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public abstract class JPAResourceActivator extends
 	ResourceActivator<AbstractJPAEntityManagerProvider, ResourceTrackingJPAEntityManagerProviderFactory>{
 
-	private static final Logger LOG = LoggerFactory.getLogger(JPAResourceActivator.class);
-	
 	@Override
 	protected Class<JPAEntityManagerProviderFactory> getAdvertisedInterface() {
 		return JPAEntityManagerProviderFactory.class;
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/ScopedConnectionDataSource.java b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/ScopedConnectionDataSource.java
similarity index 96%
rename from tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/ScopedConnectionDataSource.java
rename to tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/ScopedConnectionDataSource.java
index 11fcfa5..d52fbc2 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/ScopedConnectionDataSource.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-common/src/main/java/org/apache/aries/tx/control/jpa/common/impl/ScopedConnectionDataSource.java
@@ -1,4 +1,4 @@
-package org.apache.aries.tx.control.jpa.xa.impl;
+package org.apache.aries.tx.control.jpa.common.impl;
 
 import java.io.PrintWriter;
 import java.sql.Connection;
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderFactoryImpl.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderFactoryImpl.java
index 9882e23..d02a3b3 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderFactoryImpl.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderFactoryImpl.java
@@ -21,20 +21,26 @@ package org.apache.aries.tx.control.jpa.local.impl;
 import static java.util.Optional.ofNullable;
 import static javax.persistence.spi.PersistenceUnitTransactionType.RESOURCE_LOCAL;
 import static org.osgi.service.transaction.control.jpa.JPAEntityManagerProviderFactory.LOCAL_ENLISTMENT_ENABLED;
+import static org.osgi.service.transaction.control.jpa.JPAEntityManagerProviderFactory.TRANSACTIONAL_DB_CONNECTION;
 import static org.osgi.service.transaction.control.jpa.JPAEntityManagerProviderFactory.XA_ENLISTMENT_ENABLED;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
 
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.spi.PersistenceUnitTransactionType;
 import javax.sql.DataSource;
 
 import org.apache.aries.tx.control.jpa.common.impl.AbstractJPAEntityManagerProvider;
+import org.apache.aries.tx.control.jpa.common.impl.DelayedJPAEntityManagerProvider;
 import org.apache.aries.tx.control.jpa.common.impl.InternalJPAEntityManagerProviderFactory;
 import org.apache.aries.tx.control.jpa.common.impl.JPADataSourceHelper;
+import org.apache.aries.tx.control.jpa.common.impl.ScopedConnectionDataSource;
 import org.osgi.service.jpa.EntityManagerFactoryBuilder;
+import org.osgi.service.transaction.control.TransactionControl;
 import org.osgi.service.transaction.control.TransactionException;
+import org.osgi.service.transaction.control.jdbc.JDBCConnectionProvider;
 
 import com.zaxxer.hikari.HikariDataSource;
 
@@ -44,7 +50,12 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 	public AbstractJPAEntityManagerProvider getProviderFor(EntityManagerFactoryBuilder emfb, Map<String, Object> jpaProperties,
 			Map<String, Object> resourceProviderProperties) {
 		Map<String, Object> jpaPropsToUse = jpaProperties == null ? new HashMap<>() : new HashMap<>(jpaProperties);
-		checkEnlistment(resourceProviderProperties);
+		Map<String, Object> resourceProviderPropertiesToUse = resourceProviderProperties == null ? new HashMap<>() : new HashMap<>(resourceProviderProperties);
+		checkEnlistment(resourceProviderPropertiesToUse);
+		
+		if(resourceProviderPropertiesToUse.containsKey(TRANSACTIONAL_DB_CONNECTION)) {
+			return delegateTransactionality(emfb, jpaPropsToUse, resourceProviderPropertiesToUse);
+		}
 		
 		Object found = jpaPropsToUse.get("javax.persistence.dataSource");
 		
@@ -63,7 +74,7 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 			throw new IllegalArgumentException("The object found when checking the javax.persistence.dataSource and javax.persistence.nonJtaDataSource properties was not a DataSource.");
 		}
 		
-		DataSource toUse = JPADataSourceHelper.poolIfNecessary(resourceProviderProperties, unpooled);
+		DataSource toUse = JPADataSourceHelper.poolIfNecessary(resourceProviderPropertiesToUse, unpooled);
 		jpaPropsToUse.put("javax.persistence.dataSource", toUse);
 		jpaPropsToUse.put("javax.persistence.nonJtaDataSource", toUse);
 		
@@ -71,7 +82,7 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 		
 		validateEMF(emf);
 		
-		return new JPAEntityManagerProviderImpl(emf, () -> {
+		return new JPAEntityManagerProviderImpl(emf, false, () -> {
 				emf.close();
 				if (toUse instanceof HikariDataSource) {
 					((HikariDataSource)toUse).close();
@@ -79,6 +90,27 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 			});
 	}
 
+	private AbstractJPAEntityManagerProvider delegateTransactionality(EntityManagerFactoryBuilder emfb, Map<String, Object> jpaPropsToUse, Map<String, Object> resourceProviderProperties) {
+		Function<ThreadLocal<TransactionControl>, AbstractJPAEntityManagerProvider> create;
+		JDBCConnectionProvider provider = (JDBCConnectionProvider) resourceProviderProperties.get(TRANSACTIONAL_DB_CONNECTION);
+		
+		create = tx -> {
+				DataSource ds = new ScopedConnectionDataSource(
+						new ScopedConnectionIsolator(provider.getResource(tx.get())));
+				jpaPropsToUse.put("javax.persistence.dataSource", ds);
+				jpaPropsToUse.put("javax.persistence.nonJtaDataSource", ds);
+				
+				EntityManagerFactory emf = emfb.createEntityManagerFactory(jpaPropsToUse);
+				
+				validateEMF(emf);
+				
+				return new JPAEntityManagerProviderImpl(emf, true, () -> {
+						emf.close();
+					});
+			};
+		return new DelayedJPAEntityManagerProvider(create);
+	}
+
 	public AbstractJPAEntityManagerProvider getProviderFor(EntityManagerFactoryBuilder emfb, 
 			Map<String, Object> jpaProperties, Map<String, Object> resourceProviderProperties, 
 			Runnable onClose) {
@@ -88,7 +120,7 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 		
 		validateEMF(emf);
 		
-		return new JPAEntityManagerProviderImpl(emf, () -> {
+		return new JPAEntityManagerProviderImpl(emf, false, () -> {
 			try {
 				emf.close();
 			} catch (Exception e) {
@@ -123,7 +155,7 @@ public class JPAEntityManagerProviderFactoryImpl implements InternalJPAEntityMan
 		checkEnlistment(resourceProviderProperties);
 		validateEMF(emf);
 		
-		return new JPAEntityManagerProviderImpl(emf, null);
+		return new JPAEntityManagerProviderImpl(emf, false, null);
 	}
 
 	private void checkEnlistment(Map<String, Object> resourceProviderProperties) {
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderImpl.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderImpl.java
index d0affaa..115a5ee 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderImpl.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/JPAEntityManagerProviderImpl.java
@@ -30,13 +30,19 @@ import org.osgi.service.transaction.control.TransactionException;
 public class JPAEntityManagerProviderImpl extends AbstractJPAEntityManagerProvider {
 
 	private final UUID					uuid	= UUID.randomUUID();
+	
+	private final boolean				delegateEnlistment;
 
-	public JPAEntityManagerProviderImpl(EntityManagerFactory emf, Runnable onClose) {
+	public JPAEntityManagerProviderImpl(EntityManagerFactory emf, 
+			boolean delegateEnlistment, Runnable onClose) {
 		super(emf, onClose);
+		this.delegateEnlistment = delegateEnlistment;
 	}
 
 	@Override
 	public EntityManager getResource(TransactionControl txControl) throws TransactionException {
-		return new TxContextBindingEntityManager(txControl, this, uuid);
+		return delegateEnlistment ? 
+				new TxContextBindingJDBCDelegatingEntityManager(txControl, this, uuid) :
+				new TxContextBindingEntityManager(txControl, this, uuid);
 	}
 }
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/ScopedConnectionIsolator.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/ScopedConnectionIsolator.java
new file mode 100644
index 0000000..d1f9e0a
--- /dev/null
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/ScopedConnectionIsolator.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIESOR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.aries.tx.control.jpa.local.impl;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+public class ScopedConnectionIsolator implements Connection {
+
+	private final Connection connection;
+	
+	public ScopedConnectionIsolator(Connection connection) {
+		this.connection = connection;
+	}
+
+	public <T> T unwrap(Class<T> iface) throws SQLException {
+		return getDelegate().unwrap(iface);
+	}
+
+	public boolean isWrapperFor(Class< ? > iface) throws SQLException {
+		return getDelegate().isWrapperFor(iface);
+	}
+
+	public Statement createStatement() throws SQLException {
+		return getDelegate().createStatement();
+	}
+
+	public PreparedStatement prepareStatement(String sql) throws SQLException {
+		return getDelegate().prepareStatement(sql);
+	}
+
+	public CallableStatement prepareCall(String sql) throws SQLException {
+		return getDelegate().prepareCall(sql);
+	}
+
+	public String nativeSQL(String sql) throws SQLException {
+		return getDelegate().nativeSQL(sql);
+	}
+
+	public void setAutoCommit(boolean autoCommit) throws SQLException {
+		// This is a no-op
+	}
+
+	public boolean getAutoCommit() throws SQLException {
+		// This is a no-op
+		return false;
+	}
+
+	public void commit() throws SQLException {
+		// This is a no-op
+	}
+
+	public void rollback() throws SQLException {
+		// This is a no-op
+	}
+
+	public void close() throws SQLException {
+		getDelegate().close();
+	}
+
+	public boolean isClosed() throws SQLException {
+		return getDelegate().isClosed();
+	}
+
+	public DatabaseMetaData getMetaData() throws SQLException {
+		return getDelegate().getMetaData();
+	}
+
+	public void setReadOnly(boolean readOnly) throws SQLException {
+		getDelegate().setReadOnly(readOnly);
+	}
+
+	public boolean isReadOnly() throws SQLException {
+		return getDelegate().isReadOnly();
+	}
+
+	public void setCatalog(String catalog) throws SQLException {
+		getDelegate().setCatalog(catalog);
+	}
+
+	public String getCatalog() throws SQLException {
+		return getDelegate().getCatalog();
+	}
+
+	public void setTransactionIsolation(int level) throws SQLException {
+		getDelegate().setTransactionIsolation(level);
+	}
+
+	public int getTransactionIsolation() throws SQLException {
+		return getDelegate().getTransactionIsolation();
+	}
+
+	public SQLWarning getWarnings() throws SQLException {
+		return getDelegate().getWarnings();
+	}
+
+	public void clearWarnings() throws SQLException {
+		getDelegate().clearWarnings();
+	}
+
+	public Statement createStatement(int resultSetType,
+			int resultSetConcurrency) throws SQLException {
+		return getDelegate().createStatement(resultSetType, resultSetConcurrency);
+	}
+
+	public PreparedStatement prepareStatement(String sql, int resultSetType,
+			int resultSetConcurrency) throws SQLException {
+		return getDelegate().prepareStatement(sql, resultSetType,
+				resultSetConcurrency);
+	}
+
+	public CallableStatement prepareCall(String sql, int resultSetType,
+			int resultSetConcurrency) throws SQLException {
+		return getDelegate().prepareCall(sql, resultSetType, resultSetConcurrency);
+	}
+
+	public Map<String,Class< ? >> getTypeMap() throws SQLException {
+		return getDelegate().getTypeMap();
+	}
+
+	public void setTypeMap(Map<String,Class< ? >> map) throws SQLException {
+		getDelegate().setTypeMap(map);
+	}
+
+	public void setHoldability(int holdability) throws SQLException {
+		getDelegate().setHoldability(holdability);
+	}
+
+	public int getHoldability() throws SQLException {
+		return getDelegate().getHoldability();
+	}
+
+	public Savepoint setSavepoint() throws SQLException {
+		// This is a no-op
+		return null;
+	}
+
+	public Savepoint setSavepoint(String name) throws SQLException {
+		// This is a no-op
+		return null;
+	}
+
+	public void rollback(Savepoint savepoint) throws SQLException {
+		// This is a no-op
+	}
+
+	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+		// This is a no-op
+	}
+
+	public Statement createStatement(int resultSetType,
+			int resultSetConcurrency, int resultSetHoldability)
+					throws SQLException {
+		return getDelegate().createStatement(resultSetType, resultSetConcurrency,
+				resultSetHoldability);
+	}
+
+	public PreparedStatement prepareStatement(String sql, int resultSetType,
+			int resultSetConcurrency, int resultSetHoldability)
+					throws SQLException {
+		return getDelegate().prepareStatement(sql, resultSetType,
+				resultSetConcurrency, resultSetHoldability);
+	}
+
+	public CallableStatement prepareCall(String sql, int resultSetType,
+			int resultSetConcurrency, int resultSetHoldability)
+					throws SQLException {
+		return getDelegate().prepareCall(sql, resultSetType, resultSetConcurrency,
+				resultSetHoldability);
+	}
+
+	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
+			throws SQLException {
+		return getDelegate().prepareStatement(sql, autoGeneratedKeys);
+	}
+
+	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
+			throws SQLException {
+		return getDelegate().prepareStatement(sql, columnIndexes);
+	}
+
+	public PreparedStatement prepareStatement(String sql, String[] columnNames)
+			throws SQLException {
+		return getDelegate().prepareStatement(sql, columnNames);
+	}
+
+	public Clob createClob() throws SQLException {
+		return getDelegate().createClob();
+	}
+
+	public Blob createBlob() throws SQLException {
+		return getDelegate().createBlob();
+	}
+
+	public NClob createNClob() throws SQLException {
+		return getDelegate().createNClob();
+	}
+
+	public SQLXML createSQLXML() throws SQLException {
+		return getDelegate().createSQLXML();
+	}
+
+	public boolean isValid(int timeout) throws SQLException {
+		return getDelegate().isValid(timeout);
+	}
+
+	public void setClientInfo(String name, String value)
+			throws SQLClientInfoException {
+		getDelegate().setClientInfo(name, value);
+	}
+
+	public void setClientInfo(Properties properties)
+			throws SQLClientInfoException {
+		getDelegate().setClientInfo(properties);
+	}
+
+	public String getClientInfo(String name) throws SQLException {
+		return getDelegate().getClientInfo(name);
+	}
+
+	public Properties getClientInfo() throws SQLException {
+		return getDelegate().getClientInfo();
+	}
+
+	public Array createArrayOf(String typeName, Object[] elements)
+			throws SQLException {
+		return getDelegate().createArrayOf(typeName, elements);
+	}
+
+	public Struct createStruct(String typeName, Object[] attributes)
+			throws SQLException {
+		return getDelegate().createStruct(typeName, attributes);
+	}
+
+	public void setSchema(String schema) throws SQLException {
+		getDelegate().setSchema(schema);
+	}
+
+	public String getSchema() throws SQLException {
+		return getDelegate().getSchema();
+	}
+
+	public void abort(Executor executor) throws SQLException {
+		getDelegate().abort(executor);
+	}
+
+	public void setNetworkTimeout(Executor executor, int milliseconds)
+			throws SQLException {
+		getDelegate().setNetworkTimeout(executor, milliseconds);
+	}
+
+	public int getNetworkTimeout() throws SQLException {
+		return getDelegate().getNetworkTimeout();
+	}
+
+	protected Connection getDelegate() {
+		return connection;
+	}
+}
\ No newline at end of file
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManager.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManager.java
new file mode 100644
index 0000000..aac306d
--- /dev/null
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/main/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManager.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIESOR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.aries.tx.control.jpa.local.impl;
+
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
+
+import java.util.UUID;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+import org.apache.aries.tx.control.jpa.common.impl.AbstractJPAEntityManagerProvider;
+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.TransactionContext;
+import org.osgi.service.transaction.control.TransactionControl;
+import org.osgi.service.transaction.control.TransactionException;
+
+public class TxContextBindingJDBCDelegatingEntityManager extends EntityManagerWrapper {
+
+	private final TransactionControl				txControl;
+	private final UUID								resourceId;
+	private final AbstractJPAEntityManagerProvider	provider;
+
+	public TxContextBindingJDBCDelegatingEntityManager(TransactionControl txControl,
+			AbstractJPAEntityManagerProvider provider, UUID resourceId) {
+		this.txControl = txControl;
+		this.provider = provider;
+		this.resourceId = resourceId;
+	}
+
+	@Override
+	protected final EntityManager getRealEntityManager() {
+
+		TransactionContext txContext = txControl.getCurrentContext();
+
+		if (txContext == null) {
+			throw new TransactionException("The resource " + provider
+					+ " 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 = provider.createEntityManager();
+				toReturn = new ScopedEntityManagerWrapper(toClose);
+			} else {
+				toClose = provider.createEntityManager();
+				toReturn = new TxEntityManagerWrapper(toClose);
+				
+				txContext.preCompletion(toClose::flush);
+				toClose.getTransaction().begin();
+			}
+		} catch (Exception sqle) {
+			throw new TransactionException(
+					"There was a problem getting hold of a database connection",
+					sqle);
+		}
+		
+		txContext.postCompletion(s -> {
+				try {
+					// Make sure that the transaction ends,
+					// and that the EntityManager gets the
+					// right cache invalidation based on
+					// commit/rollback
+					if(s == ROLLED_BACK) {
+						toClose.getTransaction().rollback();
+					} else {
+						toClose.getTransaction().commit();
+					}
+				} catch (PersistenceException sqle) {
+					// TODO log this
+				}
+				try {
+					toClose.close();
+				} catch (PersistenceException sqle) {
+					// TODO log this
+				}
+			});
+		
+		txContext.putScopedValue(resourceId, toReturn);
+		
+		return toReturn;
+	}
+}
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java
index e99edb4..406cd96 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java
@@ -82,7 +82,7 @@ public class TxContextBindingEntityManagerTest {
 		Mockito.when(context.getScopedValue(Mockito.any()))
 			.thenAnswer(i -> variables.get(i.getArguments()[0]));
 		
-		provider = new JPAEntityManagerProviderImpl(emf, null);
+		provider = new JPAEntityManagerProviderImpl(emf, false, null);
 		
 		em = new TxContextBindingEntityManager(control, provider, id);
 	}
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManagerTest.java
similarity index 74%
copy from tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java
copy to tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManagerTest.java
index e99edb4..1570af2 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingEntityManagerTest.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-local/src/test/java/org/apache/aries/tx/control/jpa/local/impl/TxContextBindingJDBCDelegatingEntityManagerTest.java
@@ -21,12 +21,15 @@ 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.COMMITTED;
 import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
 
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
+import java.util.function.Consumer;
 
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
@@ -40,13 +43,13 @@ 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;
+import org.osgi.service.transaction.control.TransactionStatus;
 
 @RunWith(MockitoJUnitRunner.class)
-public class TxContextBindingEntityManagerTest {
+public class TxContextBindingJDBCDelegatingEntityManagerTest {
 
 	@Mock
 	TransactionControl control;
@@ -69,7 +72,7 @@ public class TxContextBindingEntityManagerTest {
 	
 	AbstractJPAEntityManagerProvider provider;
 	
-	TxContextBindingEntityManager em;
+	TxContextBindingJDBCDelegatingEntityManager em;
 	
 	@Before
 	public void setUp() throws SQLException {
@@ -82,9 +85,9 @@ public class TxContextBindingEntityManagerTest {
 		Mockito.when(context.getScopedValue(Mockito.any()))
 			.thenAnswer(i -> variables.get(i.getArguments()[0]));
 		
-		provider = new JPAEntityManagerProviderImpl(emf, null);
+		provider = new JPAEntityManagerProviderImpl(emf, true, null);
 		
-		em = new TxContextBindingEntityManager(control, provider, id);
+		em = new TxContextBindingJDBCDelegatingEntityManager(control, provider, id);
 	}
 	
 	private void setupNoTransaction() {
@@ -118,6 +121,7 @@ public class TxContextBindingEntityManagerTest {
 		Mockito.verify(context).postCompletion(Mockito.any());
 	}
 
+	@SuppressWarnings("unchecked")
 	@Test
 	public void testActiveTransactionCommit() throws SQLException {
 		setupActiveTransaction();
@@ -125,22 +129,28 @@ public class TxContextBindingEntityManagerTest {
 		em.isOpen();
 		em.isOpen();
 		
-		ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class);
-
+		ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+		@SuppressWarnings("rawtypes")
+		ArgumentCaptor captor2 = ArgumentCaptor.forClass(Consumer.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).preCompletion(captor.capture());
 		
-		Mockito.verify(context).postCompletion(Mockito.any());
+		Mockito.verify(context).postCompletion((Consumer<TransactionStatus>) captor2.capture());
 		
-		captor.getValue().commit();
+		captor.getValue().run();
+		Mockito.verify(rawEm).flush();
+		
+		((Consumer<TransactionStatus>)captor2.getValue()).accept(COMMITTED);
 		
 		Mockito.verify(et).commit();
 		Mockito.verify(et, times(0)).rollback();
 	}
 
+	@SuppressWarnings("unchecked")
 	@Test
 	public void testActiveTransactionRollback() throws SQLException {
 		setupActiveTransaction();
@@ -148,31 +158,28 @@ public class TxContextBindingEntityManagerTest {
 		em.isOpen();
 		em.isOpen();
 		
-		ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class);
+		ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+		@SuppressWarnings("rawtypes")
+		ArgumentCaptor captor2 = ArgumentCaptor.forClass(Consumer.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).preCompletion(captor.capture());
 		
-		Mockito.verify(context).postCompletion(Mockito.any());
+		Mockito.verify(context).postCompletion((Consumer<TransactionStatus>) captor2.capture());
+		
+		captor.getValue().run();
+		Mockito.verify(rawEm).flush();
 		
-		captor.getValue().rollback();
+		((Consumer<TransactionStatus>)captor2.getValue()).accept(ROLLED_BACK);
 		
 		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();
-	}
-
-	@Test(expected=TransactionException.class)
 	public void testClosedProvider() throws SQLException {
 		setupActiveTransaction();
 		
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java b/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java
index 218e66f..51c112c 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java
@@ -55,8 +55,10 @@ import org.apache.aries.tx.control.jdbc.common.impl.TxConnectionWrapper;
 import org.apache.aries.tx.control.jdbc.xa.connection.impl.XAConnectionWrapper;
 import org.apache.aries.tx.control.jdbc.xa.connection.impl.XADataSourceMapper;
 import org.apache.aries.tx.control.jpa.common.impl.AbstractJPAEntityManagerProvider;
+import org.apache.aries.tx.control.jpa.common.impl.DelayedJPAEntityManagerProvider;
 import org.apache.aries.tx.control.jpa.common.impl.InternalJPAEntityManagerProviderFactory;
 import org.apache.aries.tx.control.jpa.common.impl.JPADataSourceHelper;
+import org.apache.aries.tx.control.jpa.common.impl.ScopedConnectionDataSource;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.wiring.BundleWire;
diff --git a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/XAJPAEMFLocator.java b/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/XAJPAEMFLocator.java
index 4b1a1b5..20686d2 100644
--- a/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/XAJPAEMFLocator.java
+++ b/tx-control-providers/jpa/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/XAJPAEMFLocator.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import org.apache.aries.tx.control.jpa.common.impl.AbstractJPAEntityManagerProvider;
 import org.apache.aries.tx.control.jpa.common.impl.AbstractManagedJPAEMFLocator;
+import org.apache.aries.tx.control.jpa.common.impl.DelayedJPAEntityManagerProvider;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;

-- 
To stop receiving notification emails like this one, please contact
timothyjward@apache.org.