Repository: tomee Updated Branches: refs/heads/master eb0c2da70 -> 1a3f66df2
TOMEE-1702 dont store cmp reference globally Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/dac78206 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/dac78206 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/dac78206 Branch: refs/heads/master Commit: dac782064fa1b356686a845f385eca1c2a5534fa Parents: eb0c2da Author: Romain manni-Bucau <rmannibu...@gmail.com> Authored: Tue Feb 2 12:25:55 2016 +0100 Committer: Romain manni-Bucau <rmannibu...@gmail.com> Committed: Tue Feb 2 12:25:55 2016 +0100 ---------------------------------------------------------------------- .../openejb/core/ivm/BaseEjbProxyHandler.java | 104 ++++++-- .../openejb/core/cmp/jpa/SimpleCmpTest.java | 244 +++++++++++++++++++ 2 files changed, 333 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/dac78206/container/openejb-core/src/main/java/org/apache/openejb/core/ivm/BaseEjbProxyHandler.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/ivm/BaseEjbProxyHandler.java b/container/openejb-core/src/main/java/org/apache/openejb/core/ivm/BaseEjbProxyHandler.java index ac0e1dd..309b359 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/core/ivm/BaseEjbProxyHandler.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/core/ivm/BaseEjbProxyHandler.java @@ -23,12 +23,31 @@ import org.apache.openejb.InterfaceType; import org.apache.openejb.OpenEJBException; import org.apache.openejb.ProxyInfo; import org.apache.openejb.RpcContainer; +import org.apache.openejb.core.Operation; import org.apache.openejb.core.ThreadContext; +import org.apache.openejb.core.ThreadContextListener; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.spi.SecurityService; import org.apache.openejb.util.proxy.LocalBeanProxyFactory; +import javax.ejb.AccessLocalException; +import javax.ejb.EJBException; +import javax.ejb.EJBTransactionRequiredException; +import javax.ejb.EJBTransactionRolledbackException; +import javax.ejb.NoSuchEJBException; +import javax.ejb.NoSuchObjectLocalException; +import javax.ejb.TransactionRequiredLocalException; +import javax.ejb.TransactionRolledbackLocalException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionRequiredException; +import javax.transaction.TransactionRolledbackException; +import javax.transaction.TransactionSynchronizationRegistry; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -54,16 +73,6 @@ import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; -import javax.ejb.AccessLocalException; -import javax.ejb.EJBException; -import javax.ejb.EJBTransactionRequiredException; -import javax.ejb.EJBTransactionRolledbackException; -import javax.ejb.NoSuchEJBException; -import javax.ejb.NoSuchObjectLocalException; -import javax.ejb.TransactionRequiredLocalException; -import javax.ejb.TransactionRolledbackLocalException; -import javax.transaction.TransactionRequiredException; -import javax.transaction.TransactionRolledbackException; import static org.apache.openejb.core.ivm.IntraVmCopyMonitor.State.CLASSLOADER_COPY; import static org.apache.openejb.core.ivm.IntraVmCopyMonitor.State.COPY; @@ -74,6 +83,25 @@ public abstract class BaseEjbProxyHandler implements InvocationHandler, Serializ private static final String OPENEJB_LOCALCOPY = "openejb.localcopy"; private static final boolean REMOTE_COPY_ENABLED = parseRemoteCopySetting(); + static { + ThreadContext.addThreadContextListener(new ThreadContextListener() { + @Override + public void contextEntered(final ThreadContext oldContext, final ThreadContext newContext) { + // no-op + } + + @Override + public void contextExited(final ThreadContext exitedContext, final ThreadContext reenteredContext) { + if (exitedContext != null) { + final ProxyRegistry proxyRegistry = exitedContext.get(ProxyRegistry.class); + if (proxyRegistry != null) { + proxyRegistry.liveHandleRegistry.clear(); + } + } + } + }); + } + public final Object deploymentID; public final Object primaryKey; protected final InterfaceType interfaceType; @@ -640,12 +668,58 @@ public abstract class BaseEjbProxyHandler implements InvocationHandler, Serializ public ConcurrentMap getLiveHandleRegistry() { final BeanContext beanContext = getBeanContext(); - ProxyRegistry proxyRegistry = beanContext.get(ProxyRegistry.class); - if (proxyRegistry == null) { - proxyRegistry = new ProxyRegistry(); - beanContext.set(ProxyRegistry.class, proxyRegistry); + + final ThreadContext tc = ThreadContext.getThreadContext(); + if (tc != null && tc.getBeanContext() != beanContext /* parent bean */ && tc.getCurrentOperation() == Operation.BUSINESS) { + ProxyRegistry registry = tc.get(ProxyRegistry.class); + if (registry == null) { + registry = new ProxyRegistry(); + tc.set(ProxyRegistry.class, registry); + } + return registry.liveHandleRegistry; + } else { // use the tx if there + final SystemInstance systemInstance = SystemInstance.get(); + final TransactionManager txMgr = systemInstance.getComponent(TransactionManager.class); + try { + final Transaction tx = txMgr.getTransaction(); + if (tx != null && tx.getStatus() == Status.STATUS_ACTIVE) { + final TransactionSynchronizationRegistry registry = systemInstance.getComponent(TransactionSynchronizationRegistry.class); + final String resourceKey = ProxyRegistry.class.getName(); + ConcurrentMap map = ConcurrentMap.class.cast(registry.getResource(resourceKey)); + if (map == null) { + map = new ConcurrentHashMap(); + registry.putResource(resourceKey, map); + try { + final ConcurrentMap tmp = map; + tx.registerSynchronization(new Synchronization() { + @Override + public void beforeCompletion() { + // no-op + } + + @Override + public void afterCompletion(final int status) { + tmp.clear(); + } + }); + } catch (final RollbackException e) { // not really possible since we check the status + // let it go to default + } + } + return map; + } + } catch (final SystemException e) { + // let it go to default + } + + // back to default but it doesnt release the memory + ProxyRegistry proxyRegistry = beanContext.get(ProxyRegistry.class); + if (proxyRegistry == null) { + proxyRegistry = new ProxyRegistry(); + beanContext.set(ProxyRegistry.class, proxyRegistry); + } + return proxyRegistry.liveHandleRegistry; } - return proxyRegistry.liveHandleRegistry; } private void writeObject(final ObjectOutputStream out) throws IOException { http://git-wip-us.apache.org/repos/asf/tomee/blob/dac78206/container/openejb-core/src/test/java/org/apache/openejb/core/cmp/jpa/SimpleCmpTest.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/cmp/jpa/SimpleCmpTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/cmp/jpa/SimpleCmpTest.java new file mode 100644 index 0000000..9ff4557 --- /dev/null +++ b/container/openejb-core/src/test/java/org/apache/openejb/core/cmp/jpa/SimpleCmpTest.java @@ -0,0 +1,244 @@ +/* + * 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.core.cmp.jpa; + +import org.apache.activemq.util.ByteArrayInputStream; +import org.apache.openejb.OpenEJBException; +import org.apache.openejb.config.ReadDescriptors; +import org.apache.openejb.core.entity.EntityContext; +import org.apache.openejb.core.entity.EntityEjbHomeHandler; +import org.apache.openejb.jee.EjbJar; +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.AppResource; +import org.apache.openejb.testing.Module; +import org.apache.openejb.testing.SimpleLog; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ejb.CreateException; +import javax.ejb.EJBException; +import javax.ejb.EJBLocalObject; +import javax.ejb.EntityBean; +import javax.ejb.RemoveException; +import javax.ejb.SessionBean; +import javax.ejb.SessionContext; +import javax.naming.Context; +import javax.naming.NamingException; +import java.lang.reflect.Proxy; +import java.rmi.RemoteException; + +import static org.junit.Assert.assertNotNull; + +@SimpleLog +@RunWith(ApplicationComposer.class) +public class SimpleCmpTest { + @AppResource + private Context ctx; + + @Test + public void checkNoLeak() throws NamingException, RemoteException { + TestBeanEJBHome home = (TestBeanEJBHome) ctx.lookup("TestBeanRemoteHome"); + TestBeanEJBObject bean = home.create(); + + bean.check(bean.createHotelBean(0)); // the bean has the asserts + } + + @Module + public EjbJar jar() throws OpenEJBException { + return ReadDescriptors.readEjbJar(new ByteArrayInputStream( + ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<ejb-jar xsi:schemaLocation=\"" + + "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd\" " + + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + + "xmlns=\"http://java.sun.com/xml/ns/j2ee\" version=\"2.1\" id=\"ejb-jar_ID\">\n" + + "<display-name>OMEApp</display-name>\n" + + "<enterprise-beans>\n" + + "<entity id=\"ContainerManagedEntity_1\">\n" + + "<ejb-name>HotelBean</ejb-name>\n" + + "<local-home>" + HotelEJBLocalHome.class.getName() + "</local-home>\n" + + "<local>" + HotelEJBLocalObject.class.getName() + "</local>\n" + + "<ejb-class>" + HotelBean.class.getName() + "</ejb-class>\n" + + "<persistence-type>Container</persistence-type>\n" + + "<prim-key-class>java.lang.String</prim-key-class>\n" + + "<reentrant>false</reentrant>\n" + + "<cmp-version>2.x</cmp-version>\n" + + "<abstract-schema-name>HotelBean</abstract-schema-name>\n" + + "<cmp-field id=\"HotelBean_primKey\">\n" + + "<field-name>hotelId</field-name>\n" + + "</cmp-field>\n" + + "<cmp-field id=\"HotelBean_name\">\n" + + "<field-name>hotelname</field-name>\n" + + "</cmp-field>\n" + + "<primkey-field>hotelId</primkey-field>\n" + + "</entity>\n" + + "<session>\n" + + "<ejb-name>TestBean</ejb-name>\n" + + "<home>" + TestBeanEJBHome.class.getName() + "</home>\n" + + "<remote>" + TestBeanEJBObject.class.getName() + "</remote>\n" + + "<local-home>" + TestBeanEJBLocalHome.class.getName() + "</local-home>\n" + + "<local>" + TestBeanEJBLocalObject.class.getName() + "</local>\n" + + "<ejb-class>" + TestBean.class.getName() + "</ejb-class>\n" + + "<session-type>Stateless</session-type>\n" + + "<transaction-type>Container</transaction-type>\n" + + "<ejb-local-ref>\n" + + "<ejb-ref-name>ejb/HotelEJBLocalHome</ejb-ref-name>\n" + + "<ejb-ref-type>Entity</ejb-ref-type>\n" + + "<local-home>" + HotelEJBLocalHome.class.getName() + "</local-home>\n" + + "<local>" + HotelEJBLocalObject.class.getName() + "</local>\n" + + "</ejb-local-ref>\n" + + "</session>\n" + + "</enterprise-beans>\n" + + "</ejb-jar>").getBytes())); + } + + public interface HotelEJBLocalHome extends javax.ejb.EJBLocalHome { + HotelEJBLocalObject create(String id, String name) throws CreateException; + HotelEJBLocalObject findByPrimaryKey(String id); + } + + public interface TestBeanEJBHome extends javax.ejb.EJBHome { + TestBeanEJBObject create() throws RemoteException; + } + + public interface TestBeanEJBLocalHome extends javax.ejb.EJBLocalHome { + + TestBeanEJBLocalObject create(); + } + + public interface TestBeanEJBLocalObject extends javax.ejb.EJBLocalObject { + void createHotelBean(int num) throws CreateException; + } + + public static class TestBean implements SessionBean { + + private static final long serialVersionUID = 1L; + protected SessionContext sessionContext; + private HotelEJBLocalHome hotel; + + public void ejbCreate() { + } + + public void ejbPassivate() throws EJBException, RemoteException { + } + + public void ejbRemove() throws EJBException, RemoteException { + } + + public void setSessionContext(SessionContext arg0) throws EJBException, + RemoteException { + this.sessionContext = arg0; + } + + public void ejbActivate() throws EJBException, RemoteException { + } + + public Object createHotelBean(int num) throws CreateException { + hotel = (HotelEJBLocalHome) this.sessionContext.lookup("ejb/HotelEJBLocalHome"); + final String pk = hotel.create(String.valueOf(num), "Some Hotel").getPrimaryKey().toString(); + assertNotNull(hotel.findByPrimaryKey(pk)); + return pk; + } + + public void check(final String pk) { + hotel = (HotelEJBLocalHome) this.sessionContext.lookup("ejb/HotelEJBLocalHome"); + + // main part of the test is there + final EntityEjbHomeHandler handler = EntityEjbHomeHandler.class.cast(Proxy.getInvocationHandler(hotel)); + try { + final Object registry = handler.getBeanContext().get( + Thread.currentThread().getContextClassLoader() // private so use reflection + .loadClass("org.apache.openejb.core.ivm.BaseEjbProxyHandler$ProxyRegistry")); + assertNotNull(registry); // not even instantiated since we have a wrapper (stateless) + } catch (final ClassNotFoundException e) { + throw new IllegalStateException(e); + } + + // ensure we didn't deleted the entry + assertNotNull(hotel.findByPrimaryKey(pk)); + } + } + + public interface HotelEJBLocalObject extends EJBLocalObject { + + java.lang.String getHotelId(); + + void setHotelId(java.lang.String hotelId); + + String getHotelname(); + + void setHotelname(String hotelname); + } + + public interface TestBeanEJBObject extends javax.ejb.EJBObject { + String createHotelBean(int num) throws RemoteException; + void check(String pk) throws RemoteException; + } + + public static abstract class HotelBean implements EntityBean { + private static final long serialVersionUID = -1740314851444302078L; + + + public String ejbCreate(String id, String name) throws CreateException { + this.setHotelId(id); + this.setHotelname(name); + return null; + } + + public abstract java.lang.String getHotelId(); + + public abstract void setHotelId(java.lang.String hotelId); + + public abstract String getHotelname(); + + public abstract void setHotelname(String hotelname); + + public void ejbPostCreate(String str1, String str2) + throws CreateException { + } + + + public void ejbRemove() throws RemoveException, EJBException, + RemoteException { + // no-op + } + + public void ejbActivate() throws EJBException, RemoteException { + // no-op + } + + public void ejbLoad() throws EJBException, RemoteException { + // no-op + } + + public void ejbPassivate() throws EJBException, RemoteException { + // no-op + } + + public void ejbStore() throws EJBException, RemoteException { + // no-op + } + + public void setEntityContext(EntityContext arg0) throws EJBException, + RemoteException { + // no-op + } + + public void unsetEntityContext() throws EJBException, RemoteException { + // no-op + } + } +}