You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jg...@apache.org on 2018/10/10 15:18:40 UTC
[1/2] tomee git commit: TOMEE-2256 update to Tomcat 7.0.91
Repository: tomee
Updated Branches:
refs/heads/tomee-1.7.x 01e7b05b6 -> d2907b27a
TOMEE-2256 update to Tomcat 7.0.91
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/229b280d
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/229b280d
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/229b280d
Branch: refs/heads/tomee-1.7.x
Commit: 229b280df6dc99785c11058a9664ae8845012ddb
Parents: 01e7b05
Author: Jonathan Gallimore <jo...@jrg.me.uk>
Authored: Mon Oct 8 15:52:17 2018 +0100
Committer: Jonathan Gallimore <jo...@jrg.me.uk>
Committed: Mon Oct 8 15:52:17 2018 +0100
----------------------------------------------------------------------
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tomee/blob/229b280d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 218fcc3..8fa059b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -128,7 +128,7 @@
<jaxb.version>2.2.7</jaxb.version>
- <tomcat.version>7.0.90</tomcat.version>
+ <tomcat.version>7.0.91</tomcat.version>
<cxf.version>2.6.17-TomEE</cxf.version>
<!--2.6.4 requires wss4j 1.6.8-->
[2/2] tomee git commit: TOMEE-2257 improvements to tracking
connection proxies
Posted by jg...@apache.org.
TOMEE-2257 improvements to tracking connection proxies
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/d2907b27
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/d2907b27
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/d2907b27
Branch: refs/heads/tomee-1.7.x
Commit: d2907b27a0574bce824246f441f5140a0c59657e
Parents: 229b280
Author: Jonathan Gallimore <jo...@jrg.me.uk>
Authored: Wed Oct 10 16:05:45 2018 +0100
Committer: Jonathan Gallimore <jo...@jrg.me.uk>
Committed: Wed Oct 10 16:17:22 2018 +0100
----------------------------------------------------------------------
.../openejb/resource/AutoConnectionTracker.java | 128 ++++-
.../GeronimoConnectionManagerFactory.java | 13 +-
.../resource/AutoConnectionTrackerTest.java | 546 +++++++++++++++++++
3 files changed, 681 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tomee/blob/d2907b27/container/openejb-core/src/main/java/org/apache/openejb/resource/AutoConnectionTracker.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/AutoConnectionTracker.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/AutoConnectionTracker.java
index 968db3a..7be3337 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/AutoConnectionTracker.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/AutoConnectionTracker.java
@@ -23,25 +23,47 @@ import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker;
import org.apache.openejb.dyni.DynamicSubclass;
+import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.proxy.LocalBeanProxyFactory;
import javax.resource.ResourceException;
import javax.resource.spi.DissociatableManagedConnection;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class AutoConnectionTracker implements ConnectionTracker {
+
+ private static final String KEY = "AutoConnectionTracker_Connections";
+ private final TransactionSynchronizationRegistry registry;
+ private final TransactionManager txMgr;
+ private final Logger logger = Logger.getInstance(LogCategory.OPENEJB_CONNECTOR, "org.apache.openejb.resource");
private final ConcurrentMap<ManagedConnectionInfo, ProxyPhantomReference> references = new ConcurrentHashMap<ManagedConnectionInfo, ProxyPhantomReference>();
private final ReferenceQueue referenceQueue = new ReferenceQueue();
private final ConcurrentMap<Class<?>, Class<?>> proxies = new ConcurrentHashMap<Class<?>, Class<?>>();
+ private final boolean cleanupLeakedConnections;
+
+ public AutoConnectionTracker(boolean cleanupLeakedConnections) {
+ this.cleanupLeakedConnections = cleanupLeakedConnections;
+ registry = SystemInstance.get().getComponent(TransactionSynchronizationRegistry.class);
+ txMgr = SystemInstance.get().getComponent(TransactionManager.class);
+ }
+
public Set<ManagedConnectionInfo> connections() {
return references.keySet();
}
@@ -58,8 +80,12 @@ public class AutoConnectionTracker implements ConnectionTracker {
reference.clear();
references.remove(reference.managedConnectionInfo);
- final ConnectionInfo released = new ConnectionInfo(reference.managedConnectionInfo);
- reference.interceptor.returnConnection(released, ConnectionReturnAction.DESTROY);
+ if (cleanupLeakedConnections) {
+ final ConnectionInfo released = new ConnectionInfo(reference.managedConnectionInfo);
+ reference.interceptor.returnConnection(released, ConnectionReturnAction.DESTROY);
+ }
+
+ logger.warning("Detected abandoned connection " + reference.managedConnectionInfo + " opened at " + stackTraceToString(reference.stackTrace));
reference = (ProxyPhantomReference) referenceQueue.poll();
}
}
@@ -72,7 +98,54 @@ public class AutoConnectionTracker implements ConnectionTracker {
* @param reassociate should always be false
*/
public void handleObtained(final ConnectionTrackingInterceptor interceptor, final ConnectionInfo connectionInfo, final boolean reassociate) throws ResourceException {
- if (!reassociate) {
+ Transaction currentTx = null;
+ try {
+ currentTx = txMgr.getTransaction();
+ } catch (SystemException e) {
+ //ignore
+ }
+
+ if (currentTx != null) {
+ Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>> txConnections = (Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>>) registry.getResource(KEY);
+ if (txConnections == null) {
+ txConnections = new HashMap<ManagedConnectionInfo, Map<ConnectionInfo, Object>>();
+ registry.putResource(KEY, txConnections);
+ }
+
+ Map<ConnectionInfo, Object> connectionObjects = txConnections.get(connectionInfo.getManagedConnectionInfo());
+ if (connectionObjects == null) {
+ connectionObjects = new HashMap<ConnectionInfo, Object>();
+ txConnections.put(connectionInfo.getManagedConnectionInfo(), connectionObjects);
+ }
+
+ connectionObjects.put(connectionInfo, connectionInfo.getConnectionProxy());
+
+ registry.registerInterposedSynchronization(new Synchronization() {
+ @Override
+ public void beforeCompletion() {
+ final Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>> txConnections = (Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>>) registry.getResource(KEY);
+ if (txConnections != null && txConnections.size() > 0) {
+
+ for (final ManagedConnectionInfo managedConnectionInfo : txConnections.keySet()) {
+ final StringBuilder sb = new StringBuilder();
+ final Collection<ConnectionInfo> connectionInfos = txConnections.get(managedConnectionInfo).keySet();
+ for (final ConnectionInfo connectionInfo : connectionInfos) {
+ sb.append("\n ").append("Connection handle opened at ").append(stackTraceToString(connectionInfo.getTrace().getStackTrace()));
+ }
+
+ logger.warning("Transaction complete, but connection still has handles associated: " + managedConnectionInfo + "\nAbandoned connection information: " + sb.toString());
+ }
+ }
+ }
+
+ @Override
+ public void afterCompletion(final int status) {
+
+ }
+ });
+ }
+
+ if (! reassociate) {
proxyConnection(interceptor, connectionInfo);
}
}
@@ -86,6 +159,32 @@ public class AutoConnectionTracker implements ConnectionTracker {
* @param action ignored
*/
public void handleReleased(final ConnectionTrackingInterceptor interceptor, final ConnectionInfo connectionInfo, final ConnectionReturnAction action) {
+ Transaction currentTx = null;
+ try {
+ currentTx = txMgr.getTransaction();
+ } catch (SystemException e) {
+ //ignore
+ }
+
+ if (currentTx != null) {
+ Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>> txConnections = (Map<ManagedConnectionInfo, Map<ConnectionInfo, Object>>) registry.getResource(KEY);
+ if (txConnections == null) {
+ txConnections = new HashMap<ManagedConnectionInfo, Map<ConnectionInfo, Object>>();
+ registry.putResource(KEY, txConnections);
+ }
+
+ Map<ConnectionInfo, Object> connectionObjects = txConnections.get(connectionInfo.getManagedConnectionInfo());
+ if (connectionObjects == null) {
+ connectionObjects = new HashMap<ConnectionInfo, Object>();
+ txConnections.put(connectionInfo.getManagedConnectionInfo(), connectionObjects);
+ }
+
+ connectionObjects.remove(connectionInfo);
+ if (connectionObjects.size() == 0) {
+ txConnections.remove(connectionInfo.getManagedConnectionInfo());
+ }
+ }
+
final PhantomReference phantomReference = references.remove(connectionInfo.getManagedConnectionInfo());
if (phantomReference != null) {
phantomReference.clear();
@@ -93,7 +192,7 @@ public class AutoConnectionTracker implements ConnectionTracker {
}
private void proxyConnection(final ConnectionTrackingInterceptor interceptor, final ConnectionInfo connectionInfo) throws ResourceException {
- // if this connection already has a proxy no need to create another
+ // no-op if we have opted to not use proxies
if (connectionInfo.getConnectionProxy() != null) {
return;
}
@@ -142,6 +241,25 @@ public class AutoConnectionTracker implements ConnectionTracker {
return found;
}
+ public static String stackTraceToString(final StackTraceElement[] stackTrace) {
+ if (stackTrace == null) {
+ return "";
+ }
+
+ final StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < stackTrace.length; i++) {
+ final StackTraceElement element = stackTrace[i];
+ if (i > 0) {
+ sb.append(", ");
+ }
+
+ sb.append(element.toString());
+ }
+
+ return sb.toString();
+ }
+
public static class ConnectionInvocationHandler implements InvocationHandler {
private final Object handle;
@@ -177,6 +295,7 @@ public class AutoConnectionTracker implements ConnectionTracker {
private static class ProxyPhantomReference extends PhantomReference<ConnectionInvocationHandler> {
private ConnectionTrackingInterceptor interceptor;
private ManagedConnectionInfo managedConnectionInfo;
+ private StackTraceElement[] stackTrace;
@SuppressWarnings({"unchecked"})
public ProxyPhantomReference(final ConnectionTrackingInterceptor interceptor,
@@ -186,6 +305,7 @@ public class AutoConnectionTracker implements ConnectionTracker {
super(handler, referenceQueue);
this.interceptor = interceptor;
this.managedConnectionInfo = managedConnectionInfo;
+ this.stackTrace = Thread.currentThread().getStackTrace();
}
}
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/d2907b27/container/openejb-core/src/main/java/org/apache/openejb/resource/GeronimoConnectionManagerFactory.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/GeronimoConnectionManagerFactory.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/GeronimoConnectionManagerFactory.java
index dd9cbfa..3a99637 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/GeronimoConnectionManagerFactory.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/GeronimoConnectionManagerFactory.java
@@ -92,6 +92,7 @@ public class GeronimoConnectionManagerFactory {
private int connectionMaxIdleMinutes = 15;
private ManagedConnectionFactory mcf;
private int validationIntervalMs = -1;
+ private boolean cleanupLeakedConnections = true;
public boolean isAssumeOneMatch() {
return assumeOneMatch;
@@ -228,6 +229,14 @@ public class GeronimoConnectionManagerFactory {
validationIntervalMs = (int) validationInterval.getUnit().toMillis(validationInterval.getTime());
}
+ public boolean isCleanupLeakedConnections() {
+ return cleanupLeakedConnections;
+ }
+
+ public void setCleanupLeakedConnections(boolean cleanupLeakedConnections) {
+ this.cleanupLeakedConnections = cleanupLeakedConnections;
+ }
+
public GenericConnectionManager create() {
final PoolingSupport poolingSupport = createPoolingSupport();
@@ -260,11 +269,11 @@ public class GeronimoConnectionManagerFactory {
name = getClass().getSimpleName();
}
mgr = new ValidatingGenericConnectionManager(txSupport, poolingSupport,
- null, new AutoConnectionTracker(), tm,
+ null, new AutoConnectionTracker(cleanupLeakedConnections), tm,
mcf, name, classLoader, validationIntervalMs);
} else {
mgr = new GenericConnectionManager(txSupport, poolingSupport,
- null, new AutoConnectionTracker(), tm,
+ null, new AutoConnectionTracker(cleanupLeakedConnections), tm,
mcf, name, classLoader);
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/d2907b27/container/openejb-core/src/test/java/org/apache/openejb/resource/AutoConnectionTrackerTest.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/resource/AutoConnectionTrackerTest.java b/container/openejb-core/src/test/java/org/apache/openejb/resource/AutoConnectionTrackerTest.java
new file mode 100644
index 0000000..6c2e455
--- /dev/null
+++ b/container/openejb-core/src/test/java/org/apache/openejb/resource/AutoConnectionTrackerTest.java
@@ -0,0 +1,546 @@
+/**
+ *
+ * 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 WARRANTIES OR 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.openejb.resource;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.connector.outbound.GenericConnectionManager;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
+import org.apache.openejb.assembler.classic.Assembler;
+import org.apache.openejb.assembler.classic.ContainerInfo;
+import org.apache.openejb.assembler.classic.EjbJarInfo;
+import org.apache.openejb.assembler.classic.MdbContainerInfo;
+import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
+import org.apache.openejb.assembler.classic.ResourceInfo;
+import org.apache.openejb.assembler.classic.SecurityServiceInfo;
+import org.apache.openejb.assembler.classic.TransactionServiceInfo;
+import org.apache.openejb.config.ConfigurationFactory;
+import org.apache.openejb.config.EjbModule;
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.StatelessBean;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.spi.ContainerSystem;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+
+import javax.annotation.Resource;
+import javax.ejb.Remote;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.resource.NotSupportedException;
+import javax.resource.ResourceException;
+import javax.resource.spi.ActivationSpec;
+import javax.resource.spi.BootstrapContext;
+import javax.resource.spi.ConnectionEvent;
+import javax.resource.spi.ConnectionEventListener;
+import javax.resource.spi.ConnectionManager;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.InvalidPropertyException;
+import javax.resource.spi.LocalTransaction;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionFactory;
+import javax.resource.spi.ManagedConnectionMetaData;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.spi.ResourceAdapterInternalException;
+import javax.resource.spi.endpoint.MessageEndpointFactory;
+import javax.security.auth.Subject;
+import javax.transaction.xa.XAResource;
+import java.io.PrintWriter;
+import java.lang.SecurityException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class AutoConnectionTrackerTest extends TestCase {
+
+ public static final int LOOP_SIZE = 200;
+ public static final int NUM_THREADS = 4;
+
+ public void test() throws Exception {
+ System.setProperty("openejb.log.async", "false");
+ final Logger logger = Logger.getInstance(LogCategory.OPENEJB_CONNECTOR, AutoConnectionTrackerTest.class);
+ logger.info("Starting test");
+ final java.util.logging.Logger julLogger = LogManager.getLogManager().getLogger(LogCategory.OPENEJB_CONNECTOR.getName());
+ final LogCaptureHandler logCapture = new LogCaptureHandler();
+ julLogger.addHandler(logCapture);
+
+ final ConfigurationFactory config = new ConfigurationFactory();
+ final Assembler assembler = new Assembler();
+
+ // System services
+ assembler.createProxyFactory(config.configureService(ProxyFactoryInfo.class));
+ assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class));
+ assembler.createSecurityService(config.configureService(SecurityServiceInfo.class));
+
+ // JMS persistence datasource
+ final ResourceInfo dataSourceInfo = config.configureService("Default Unmanaged JDBC Database", ResourceInfo.class);
+ dataSourceInfo.properties.setProperty("JdbcUrl", "jdbc:hsqldb:mem:MdbConfigTest");
+ assembler.createResource(dataSourceInfo);
+
+ // FakeRA
+ final ResourceInfo resourceInfo = new ResourceInfo();
+ resourceInfo.service = "Resource";
+ resourceInfo.className = FakeRA.class.getName();
+ resourceInfo.id = "FakeRA";
+ resourceInfo.properties = new Properties();
+ assembler.createResource(resourceInfo);
+
+ // FakeRA container
+ final ContainerInfo containerInfo = config.configureService(MdbContainerInfo.class);
+ containerInfo.id = "FakeContainer";
+ containerInfo.displayName = "Fake Container";
+ containerInfo.properties.setProperty("ResourceAdapter", "FakeRA");
+ containerInfo.properties.setProperty("MessageListenerInterface", FakeMessageListener.class.getName());
+ containerInfo.properties.setProperty("ActivationSpecClass", FakeActivationSpec.class.getName());
+ assembler.createContainer(containerInfo);
+
+ // Fake connection factory
+ final ResourceInfo mcfResourceInfo = new ResourceInfo();
+ mcfResourceInfo.className = FakeManagedConnectionFactory.class.getName();
+ mcfResourceInfo.id = "FakeConnectionFactory";
+ mcfResourceInfo.types = Collections.singletonList(FakeConnectionFactory.class.getName());
+ mcfResourceInfo.properties = new Properties();
+ mcfResourceInfo.properties.setProperty("ResourceAdapter", "FakeRA");
+ mcfResourceInfo.properties.setProperty("TransactionSupport", "None");
+ mcfResourceInfo.properties.setProperty("allConnectionsEqual", "false");
+ assembler.createResource(mcfResourceInfo);
+
+ // generate ejb jar application
+ final EjbJar ejbJar = new EjbJar();
+ ejbJar.addEnterpriseBean(new StatelessBean("TestBean", FakeStatelessBean.class));
+ final EjbModule ejbModule = new EjbModule(getClass().getClassLoader(), "FakeEjbJar", "fake.jar", ejbJar, null);
+
+ // configure and deploy it
+ final EjbJarInfo info = config.configureApplication(ejbModule);
+ assembler.createEjbJar(info);
+
+ final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
+ final FakeConnectionFactory cf = (FakeConnectionFactory) containerSystem.getJNDIContext().lookup("openejb:Resource/FakeConnectionFactory");
+ final FakeRemote bean = (FakeRemote) containerSystem.getJNDIContext().lookup("java:global/FakeEjbJar/FakeEjbJar/TestBean!org.apache.openejb.resource.AutoConnectionTrackerTest$FakeRemote");
+
+
+ {
+ logCapture.clear();
+ runTest(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ bean.nonLeakyTxMethod();
+ System.gc();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ System.gc();
+ cf.getConnection().close();
+ assertEquals(0, logCapture.find("Transaction complete, but connection still has handles associated").size());
+ assertEquals(0, logCapture.find("Detected abandoned connection").size());
+ assertTrue(getConnectionCount((FakeConnectionFactoryImpl) cf) > 0);
+ }
+ {
+ logCapture.clear();
+ runTest(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ bean.nonleakyNonTxMethod();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ System.gc();
+ cf.getConnection().close();
+ assertEquals(0, logCapture.find("Transaction complete, but connection still has handles associated").size());
+ assertEquals(0, logCapture.find("Detected abandoned connection").size());
+ assertTrue(getConnectionCount((FakeConnectionFactoryImpl) cf) > 0);
+ }
+ {
+ logCapture.clear();
+ bean.leakyTxMethod();
+
+ System.gc();
+ cf.getConnection().close();
+ assertEquals(1, logCapture.find("Transaction complete, but connection still has handles associated").size());
+ }
+ {
+ logCapture.clear();
+ bean.leakyNonTxMethod();
+ System.gc();
+ cf.getConnection().close();
+ assertEquals(1, logCapture.find("Detected abandoned connection").size());
+ }
+ }
+
+ private int getConnectionCount(FakeConnectionFactoryImpl cf) {
+ final PoolingSupport pooling = ((GenericConnectionManager) cf.connectionManager).getPooling();
+ return pooling.getConnectionCount();
+ }
+
+ private void runTest(final Runnable testCode) throws InterruptedException {
+ final CountDownLatch startingLine = new CountDownLatch(NUM_THREADS);
+ final CountDownLatch start = new CountDownLatch(1);
+ final CountDownLatch end = new CountDownLatch(NUM_THREADS * LOOP_SIZE);
+ final Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ startingLine.countDown();
+ start.await();
+ for (int i = 0; i < LOOP_SIZE; i++) {
+ testCode.run();
+ end.countDown();
+ }
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ }
+ };
+
+ for (int i = 0; i < NUM_THREADS; i++) {
+ new Thread(runnable).start();
+ }
+
+ startingLine.await(1, TimeUnit.MINUTES);
+ start.countDown();
+ end.await(1, TimeUnit.MINUTES);
+ }
+
+ public interface FakeRemote {
+ void leakyTxMethod() throws Exception;
+ void nonLeakyTxMethod() throws Exception;
+ void leakyNonTxMethod() throws Exception;
+ void nonleakyNonTxMethod() throws Exception;
+ }
+
+ @Remote
+ public static class FakeStatelessBean implements FakeRemote {
+
+ @Resource(name="FakeConnectionFactory")
+ private FakeConnectionFactory cf;
+
+ @Override
+ public void leakyTxMethod() throws Exception {
+ final FakeConnection connection = cf.getConnection();// this leaks!
+ connection.sendMessage("Test message");
+ }
+
+ @Override
+ public void nonLeakyTxMethod() throws Exception {
+ final FakeConnection connection = cf.getConnection();
+ connection.sendMessage("Test message");
+ connection.close();
+ }
+
+ @Override
+ @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+ public void leakyNonTxMethod() throws Exception {
+ final FakeConnection connection = cf.getConnection();// this leaks!
+ connection.sendMessage("Test message");
+ }
+
+ @Override
+ @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+ public void nonleakyNonTxMethod() throws Exception {
+ final FakeConnection connection = cf.getConnection();
+ connection.sendMessage("Test message");
+ connection.close();
+ }
+ }
+
+ public static class FakeMdb implements FakeMessageListener {
+ public void doIt(final Properties properties) {
+ }
+ }
+
+ public interface FakeMessageListener {
+ void doIt(Properties properties);
+ }
+
+ public static class FakeRA implements ResourceAdapter {
+ public boolean started;
+
+ public void start(final BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
+ }
+
+ public void stop() {
+ assertTrue("RA was not started", started);
+ }
+
+ public void endpointActivation(final MessageEndpointFactory messageEndpointFactory, final ActivationSpec activationSpec) throws ResourceException {
+ }
+
+ public void endpointDeactivation(final MessageEndpointFactory messageEndpointFactory, final ActivationSpec activationSpec) {
+ }
+
+ public XAResource[] getXAResources(final ActivationSpec[] activationSpecs) throws ResourceException {
+ return new XAResource[0];
+ }
+ }
+
+ public static class FakeActivationSpec implements ActivationSpec {
+ private FakeRA fakeRA;
+ protected boolean validated;
+
+ public void validate() throws InvalidPropertyException {
+ validated = true;
+ }
+
+ public FakeRA getResourceAdapter() {
+ return fakeRA;
+ }
+
+ public void setResourceAdapter(final ResourceAdapter resourceAdapter) {
+ assertNotNull("resourceAdapter is null", resourceAdapter);
+ assertTrue("resourceAdapter should be an instance of FakeRA", resourceAdapter instanceof FakeRA);
+ this.fakeRA = (FakeRA) resourceAdapter;
+ assertTrue("ActivationSpec has not been validated", validated);
+ }
+ }
+
+ public interface FakeConnection {
+ void sendMessage(final String message);
+ void close();
+ }
+
+ public static class FakeConnectionImpl implements FakeConnection {
+ private final FakeManagedConnection mc;
+ private final FakeManagedConnectionFactory mcf;
+
+ public FakeConnectionImpl(FakeManagedConnection mc, FakeManagedConnectionFactory mcf) {
+ this.mc = mc;
+ this.mcf = mcf;
+ }
+
+ @Override
+ public void sendMessage(final String message) {
+ mc.sendMessage(message);
+ }
+
+ @Override
+ public void close() {
+ mc.closeHandle(this);
+ }
+ }
+
+ public static class FakeManagedConnection implements ManagedConnection {
+ private final Logger logger = Logger.getInstance(LogCategory.OPENEJB_CONNECTOR, FakeManagedConnection.class);
+ private final FakeManagedConnectionFactory mcf;
+ private final List<ConnectionEventListener> listeners = new ArrayList<ConnectionEventListener>();
+ private FakeConnection connection;
+ private PrintWriter writer;
+
+ public FakeManagedConnection(FakeManagedConnectionFactory mcf) {
+ this.mcf = mcf;
+ }
+
+ @Override
+ public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+ connection = new FakeConnectionImpl(this, mcf);
+ return connection;
+ }
+
+ @Override
+ public void destroy() throws ResourceException {
+ }
+
+ @Override
+ public void cleanup() throws ResourceException {
+ }
+
+ @Override
+ public void associateConnection(Object connection) throws ResourceException {
+ if (connection == null)
+ throw new ResourceException("Null connection handle");
+
+ if (!(connection instanceof FakeConnectionImpl))
+ throw new ResourceException("Wrong connection handle");
+
+ this.connection = (FakeConnectionImpl) connection;
+ }
+
+ @Override
+ public void addConnectionEventListener(final ConnectionEventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeConnectionEventListener(final ConnectionEventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ listeners.remove(listener);
+ }
+
+ @Override
+ public XAResource getXAResource() throws ResourceException {
+ throw new NotSupportedException("getXAResource() not supported");
+ }
+
+ @Override
+ public LocalTransaction getLocalTransaction() throws ResourceException {
+ throw new NotSupportedException("getLocalTransaction() not supported");
+ }
+
+ @Override
+ public ManagedConnectionMetaData getMetaData() throws ResourceException {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter writer) throws ResourceException {
+ this.writer = writer;
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws ResourceException {
+ return writer;
+ }
+
+ void closeHandle(final FakeConnection handle) {
+ final ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
+ event.setConnectionHandle(handle);
+ for (ConnectionEventListener cel : listeners) {
+ cel.connectionClosed(event);
+ }
+ }
+
+ public void sendMessage(final String message) {
+ logger.info(message);
+ }
+ }
+
+ public interface FakeConnectionFactory {
+ FakeConnection getConnection() throws ResourceException;
+ }
+
+ public static class FakeConnectionFactoryImpl implements FakeConnectionFactory {
+
+ private final FakeManagedConnectionFactory mcf;
+ private final ConnectionManager connectionManager;
+
+ public FakeConnectionFactoryImpl(final FakeManagedConnectionFactory mcf, final ConnectionManager connectionManager) {
+ this.mcf = mcf;
+ this.connectionManager = connectionManager;
+ }
+
+ @Override
+ public FakeConnection getConnection() throws ResourceException {
+ return (FakeConnection) connectionManager.allocateConnection(mcf, null);
+ }
+ }
+
+ public static class FakeManagedConnectionFactory implements ManagedConnectionFactory {
+
+ private PrintWriter writer;
+
+ @Override
+ public Object createConnectionFactory(final ConnectionManager cxManager) throws ResourceException {
+ return new FakeConnectionFactoryImpl(this, cxManager);
+ }
+
+ @Override
+ public Object createConnectionFactory() throws ResourceException {
+ throw new ResourceException("This resource adapter doesn't support non-managed environments");
+ }
+
+ @Override
+ public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+ return new FakeManagedConnection(this);
+ }
+
+ @Override
+ public ManagedConnection matchManagedConnections(Set connectionSet, Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+ ManagedConnection result = null;
+ Iterator it = connectionSet.iterator();
+ while (result == null && it.hasNext()) {
+ ManagedConnection mc = (ManagedConnection) it.next();
+ if (mc instanceof FakeManagedConnection) {
+ result = mc;
+ }
+
+ }
+ return result;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter writer) throws ResourceException {
+ this.writer = writer;
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws ResourceException {
+ return writer;
+ }
+ }
+
+ public static class LogCaptureHandler extends Handler {
+
+ private List<LogRecord> recordList = Collections.synchronizedList(new ArrayList<LogRecord>());
+
+ @Override
+ public void publish(final LogRecord record) {
+ recordList.add(record);
+ }
+
+ @Override
+ public void flush() {
+
+ }
+
+ @Override
+ public void close() throws SecurityException {
+
+ }
+
+ public List<LogRecord> find(final String message) {
+ final List<LogRecord> allRecords = new ArrayList<LogRecord>(recordList);
+ final List<LogRecord> matchingRecords = new ArrayList<LogRecord>();
+
+ for (final LogRecord record : allRecords) {
+ if (record.getMessage().contains(message)) {
+ matchingRecords.add(record);
+ }
+ }
+
+ return matchingRecords;
+ }
+
+ public void clear() {
+ recordList.clear();
+ }
+ }
+}