/* * ============================================================================ * GNU Lesser General Public License * ============================================================================ * * Beanlet - JSE Application Container. * Copyright (C) 2006 Leon van Zantvoort * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Leon van Zantvoort * 243 Acalanes Drive #11 * Sunnyvale, CA 94086 * USA * * zantvoort@users.sourceforge.net * http://beanlet.org */ package org.beanlet.persistence.impl; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.metamodel.Metamodel; import javax.transaction.Status; import javax.transaction.Transaction; import org.beanlet.plugin.TransactionLocal; import org.jargo.ComponentApplicationContext; import org.jargo.ComponentReference; import org.jargo.MetaData; /** * <ul> * <li>A container-managed extended persistence context can only be initiated * within the scope of a stateful session bean. It exists from the point at * which the stateful session bean that declares a dependency on an entity * manager of type PersistenceContextType.EXTENDED is created, and is said to be * bound to the stateful session bean. The dependency on the extended * persistence context is declared by means of the PersistenceContext annotation * or persistence-context-ref deployment descriptor element. (5.6.2 p121) * <li>The persistence context is closed by the container when the @Remove * method of the stateful session bean completes (or the stateful session bean * instance is otherwise destroyed). (5.6.2 p121) * <li>If a stateful session bean instantiates a stateful session bean which * also has such an extended persistence context, the extended persistence * context of the first stateful session bean is inherited by the second * stateful session bean and bound to it, and this rule recursively * applies independently of whether transactions are active or not at the point * of the creation of the stateful session beans. (5.6.2.1 p121) * <li>If the persistence context has been inherited by any stateful session * beans, the container does not close the persistence context until all such * stateful session beans have been removed or otherwise destroyed. * (5.6.2.1 p121) * </ul> * * @author Leon van Zantvoort */ public final class ExtendedEntityManager extends ContainerManagedEntityManager { private static final Map<ComponentReference, ExtendedEntityManager> registry = new HashMap<ComponentReference, ExtendedEntityManager>(); private static final Map<ExtendedEntityManager, AtomicInteger> counter = new HashMap<ExtendedEntityManager, AtomicInteger>(); private static final TransactionLocal<?> txLocal = new TransactionLocal<Object>(); private static final ComponentApplicationContext ctx = ComponentApplicationContext.instance(); private static final Logger logger = Logger.getLogger(ExtendedEntityManager.class.getName()); private static boolean hasExtendedPersistenceContext(ComponentReference<?> reference) { for (MetaData m : reference.getComponentMetaData().getMetaData()) { if (m instanceof PersistenceContextMetaData) { PersistenceContext ctx = ((PersistenceContextMetaData) m). getPersistenceContext(); if (ctx.type() == PersistenceContextType.EXTENDED) { return true; } } } return false; } public static synchronized ExtendedEntityManager getInstance(BeanletEntityManagerFactory emf) { List<ComponentReference<?>> callStack = ctx.referenceStack(); assert !callStack.isEmpty(); Iterator<ComponentReference<?>> i = callStack.iterator(); ComponentReference<?> current = i.next().weakReference(); assert !current.getComponentMetaData().isStatic(); final ExtendedEntityManager em; if (registry.containsKey(current)) { em = registry.get(current); } else { // No em registered for current reference; lookup root pctx reference. ComponentReference<?> root = current; while (i.hasNext()) { ComponentReference<?> prev = i.next().weakReference(); if (!prev.getComponentMetaData().isStatic()) { if (hasExtendedPersistenceContext(prev)) { root = prev; } } else { // Stateful chain broken. break; } } if (current == root) { logger.finest("Creating new extended persistence context for " + root + "."); em = new ExtendedEntityManager(emf.createEntityManager()); registry.put(root, em); counter.put(em, new AtomicInteger(1)); txLocal.onUserTransaction(root.weakReference(), new Runnable() { public void run() { em.join(); } }); registerDestroyHook(root, em); } else { if (registry.containsKey(root)) { em = registry.get(root); logger.finest(current + " inherits extended persistence context from " + root + "."); registry.put(current, em); AtomicInteger ai = counter.get(current); assert ai != null; ai.incrementAndGet(); txLocal.onUserTransaction(current.weakReference(), new Runnable() { public void run() { em.join(); } }); registerDestroyHook(current, em); } else { logger.finest("Creating new extended persistence context for " + root + "."); em = new ExtendedEntityManager(emf.createEntityManager()); registry.put(root, em); counter.put(em, new AtomicInteger(2)); txLocal.onUserTransaction(root.weakReference(), new Runnable() { public void run() { em.join(); } }); registerDestroyHook(root, em); logger.finest(current + " inherits extended persistence context from " + root + "."); registry.put(current, em); txLocal.onUserTransaction(current.weakReference(), new Runnable() { public void run() { em.join(); } }); registerDestroyHook(current, em); } } } assert em != null; return em; } private static void registerDestroyHook(final ComponentReference<?> reference, final ExtendedEntityManager em) { reference.addDestroyHook(new Runnable() { public void run() { ExtendedEntityManager tmp = registry.remove(reference); assert tmp == em; AtomicInteger ai = counter.get(em); assert ai != null; if (ai.decrementAndGet() == 0) { counter.remove(em); em.getEntityManager().close(); } } }); } private final BeanletEntityManager em; public ExtendedEntityManager(BeanletEntityManager em) { this.em = em; } public EntityManager getEntityManager() { return em; } public <T> T find(Class<T> cls, Object object) { return getEntityManager().find(cls, object); } public <T> T getReference(Class<T> cls, Object object) { return getEntityManager().getReference(cls, object); } public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) { return getEntityManager().createNamedQuery(name, resultClass); } public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) { return getEntityManager().find(entityClass, primaryKey, properties); } public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) { return getEntityManager().find(entityClass, primaryKey, lockMode); } public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) { return getEntityManager().find(entityClass, primaryKey, lockMode, properties); } public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) { getEntityManager().lock(entity, lockMode, properties); } public void refresh(Object entity, Map<String, Object> properties) { getEntityManager().refresh(entity, properties); } public void refresh(Object entity, LockModeType lockMode) { getEntityManager().refresh(entity, lockMode); } public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) { getEntityManager().refresh(entity, lockMode, properties); } public void detach(Object entity) { getEntityManager().detach(entity); } public LockModeType getLockMode(Object entity) { return getEntityManager().getLockMode(entity); } public void setProperty(String propertyName, Object value) { getEntityManager().setProperty(propertyName, value); } public Map<String, Object> getProperties() { return getEntityManager().getProperties(); } public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) { return getEntityManager().createQuery(criteriaQuery); } public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) { return getEntityManager().createQuery(qlString, resultClass); } public <T> T unwrap(Class<T> cls) { return getEntityManager().unwrap(cls); } public EntityManagerFactory getEntityManagerFactory() { return getEntityManager().getEntityManagerFactory(); } public CriteriaBuilder getCriteriaBuilder() { return getEntityManager().getCriteriaBuilder(); } public Metamodel getMetamodel() { return getEntityManager().getMetamodel(); } /** * Returns {@code true} if running thread is participating in an active * transaction. * * @throws PersistenceException if failed to obtain transaction status. */ private boolean isTransactionActive() throws PersistenceException { try { Transaction tx = txLocal.getTransaction(); return tx != null && tx.getStatus() == Status.STATUS_ACTIVE; } catch (Exception e) { throw new PersistenceException(e); } } /** * This method has package private visibility. */ @Override void preInvoke() { join(); } /** * This method has package private visibility. */ @Override void postInvoke(boolean commit) { // Do nothing. } private void join() { if (isTransactionActive()) { if (!em.isTransactionJoined()) { if (BeanletPersistenceContext.isTransactionJoined()) { throw new IllegalStateException("Different persistence " + "context already associated with the JTA transaction."); } em.joinTransaction(); logger.finest("Extended entity manager joined transaction: " + this + "."); } else { logger.finest("Extended entity manager already joined transaction: " + this + "."); } } else { logger.finest("Entity manager did not join transaction. " + "No transaction active: " + this + "."); } } }