/* * 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.persistence; import org.apache.openejb.util.Geronimo; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.SynchronizationType; import javax.persistence.TransactionRequiredException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.TransactionSynchronizationRegistry; import java.util.HashMap; import java.util.Map; /** * The JtaEntityManagerRegistry tracks JTA entity managers for transaction and extended scoped * entity managers. A single instance of this object should be created and shared by all * JtaEntityManagers in the server instance. Failure to do this will result in multiple entity * managers being created for a single persistence until, and that will result in cache * incoherence. */ public class JtaEntityManagerRegistry { private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB.createChild("persistence"), JtaEntityManager.class); /** * Registry of transaction associated entity managers. */ private final TransactionSynchronizationRegistry transactionRegistry; /** * Registry of extended context entity managers. */ private final ThreadLocal<ExtendedRegistry> extendedRegistry = new ThreadLocal<ExtendedRegistry>() { protected ExtendedRegistry initialValue() { return new ExtendedRegistry(); } }; /** * Creates a JtaEntityManagerRegistry using the specified transactionSynchronizationRegistry for the registry * if transaction associated entity managers. */ public JtaEntityManagerRegistry(final TransactionSynchronizationRegistry transactionSynchronizationRegistry) { this.transactionRegistry = transactionSynchronizationRegistry; } /** * Gets an entity manager instance from the transaction registry, extended registry or for a transaction scoped * entity manager, creates a new one when an existing instance is not found. * </p> * It is important that a component adds extended scoped entity managers to this registry when the component is * entered and removes them when exited. If this registration is not performed, an IllegalStateException will * be thrown when entity manger is fetched. * * @param entityManagerFactory the entity manager factory from which an entity manager is required * @param properties the properties passed to the entity manager factory when an entity manager is created * @param extended is the entity manager an extended context * @param unitName * @return the new entity manager * @throws IllegalStateException if the entity manger is extended and there is not an existing entity manager * instance already registered */ @Geronimo public EntityManager getEntityManager(final EntityManagerFactory entityManagerFactory, final Map properties, final boolean extended, final String unitName, final SynchronizationType synchronizationType) throws IllegalStateException { if (entityManagerFactory == null) { throw new NullPointerException("entityManagerFactory is null"); } final EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory); final boolean transactionActive = isTransactionActive(); // if we have an active transaction, check the tx registry if (transactionActive) { final EntityManager entityManager = (EntityManager) transactionRegistry.getResource(txKey); if (entityManager != null) { return entityManager; } } // if extended context, there must be an entity manager already registered with the tx if (extended) { final EntityManagerTracker entityManagerTracker = getInheritedEntityManager(entityManagerFactory); if (entityManagerTracker == null || entityManagerTracker.getEntityManager() == null) { throw new IllegalStateException("InternalError: an entity manager should already be registered for this extended persistence unit"); } final EntityManager entityManager = entityManagerTracker.getEntityManager(); // if transaction is active, we need to register the entity manager with the transaction manager if (transactionActive) { if (entityManagerTracker.autoJoinTx) { entityManager.joinTransaction(); } transactionRegistry.putResource(txKey, entityManager); } return entityManager; } else { // create a new entity manager final EntityManager entityManager; if (synchronizationType != null) { if (properties != null) { entityManager = entityManagerFactory.createEntityManager(synchronizationType, properties); } else { entityManager = entityManagerFactory.createEntityManager(synchronizationType); } } else if (properties != null) { entityManager = entityManagerFactory.createEntityManager(properties); } else { entityManager = entityManagerFactory.createEntityManager(); } logger.debug("Created EntityManager(unit=" + unitName + ", hashCode=" + entityManager.hashCode() + ")"); // if we are in a transaction associate the entity manager with the transaction; otherwise it is // expected the caller will close this entity manager after use if (transactionActive) { transactionRegistry.registerInterposedSynchronization(new CloseEntityManager(entityManager, unitName)); transactionRegistry.putResource(txKey, entityManager); } return entityManager; } } /** * Adds the entity managers for the specified component to the registry. This should be called when the component * is entered. * * @param deploymentId the id of the component * @param entityManagers the entity managers to register * @throws EntityManagerAlreadyRegisteredException if an entity manager is already registered with the transaction * for one of the supplied entity manager factories; for EJBs this should be caught and rethown as an EJBException */ public void addEntityManagers(final String deploymentId, final Object primaryKey, final Map<EntityManagerFactory, EntityManagerTracker> entityManagers) throws EntityManagerAlreadyRegisteredException { extendedRegistry.get().addEntityManagers(new InstanceId(deploymentId, primaryKey), entityManagers); } /** * Removed the registered entity managers for the specified component. * * @param deploymentId the id of the component * @return EntityManager map we are removing */ public Map<EntityManagerFactory, EntityManagerTracker> removeEntityManagers(final String deploymentId, final Object primaryKey) { return extendedRegistry.get().removeEntityManagers(new InstanceId(deploymentId, primaryKey)); } /** * Gets an exiting extended entity manager created by a component down the call stack. * * @param entityManagerFactory the entity manager factory from which an entity manager is needed * @return the existing entity manager or null if one is not found */ public EntityManagerTracker getInheritedEntityManager(final EntityManagerFactory entityManagerFactory) { return extendedRegistry.get().getInheritedEntityManager(entityManagerFactory); } /** * Notifies the registry that a user transaction has been started or the specified component. When a transaction * is started for a component with registered extended entity managers, the entity managers are enrolled in the * transaction. * * @param deploymentId the id of the component */ public void transactionStarted(final String deploymentId, final Object primaryKey) { extendedRegistry.get().transactionStarted(new InstanceId(deploymentId, primaryKey)); } /** * Is a transaction active? * * @return true if a transaction is active; false otherwise */ public boolean isTransactionActive() { final int txStatus = transactionRegistry.getTransactionStatus(); final boolean transactionActive = txStatus == Status.STATUS_ACTIVE || txStatus == Status.STATUS_MARKED_ROLLBACK; return transactionActive; } private class ExtendedRegistry { private final Map<InstanceId, Map<EntityManagerFactory, EntityManagerTracker>> entityManagersByDeploymentId = new HashMap<InstanceId, Map<EntityManagerFactory, EntityManagerTracker>>(); private void addEntityManagers(final InstanceId instanceId, final Map<EntityManagerFactory, EntityManagerTracker> entityManagers) throws EntityManagerAlreadyRegisteredException { if (instanceId == null) { throw new NullPointerException("instanceId is null"); } if (entityManagers == null) { throw new NullPointerException("entityManagers is null"); } if (isTransactionActive()) { for (final Map.Entry<EntityManagerFactory, EntityManagerTracker> entry : entityManagers.entrySet()) { final EntityManagerFactory entityManagerFactory = entry.getKey(); final EntityManagerTracker tracker = entry.getValue(); final EntityManager entityManager = tracker.getEntityManager(); final EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory); final EntityManager oldEntityManager = (EntityManager) transactionRegistry.getResource(txKey); if (entityManager == oldEntityManager) { break; } if (oldEntityManager != null) { throw new EntityManagerAlreadyRegisteredException("Another entity manager is already registered for this persistence unit"); } if (tracker.autoJoinTx) { entityManager.joinTransaction(); } transactionRegistry.putResource(txKey, entityManager); } } entityManagersByDeploymentId.put(instanceId, entityManagers); } private Map<EntityManagerFactory, EntityManagerTracker> removeEntityManagers(final InstanceId instanceId) { if (instanceId == null) { throw new NullPointerException("InstanceId is null"); } return entityManagersByDeploymentId.remove(instanceId); } private EntityManagerTracker getInheritedEntityManager(final EntityManagerFactory entityManagerFactory) { if (entityManagerFactory == null) { throw new NullPointerException("entityManagerFactory is null"); } for (final Map<EntityManagerFactory, EntityManagerTracker> entityManagers : entityManagersByDeploymentId.values()) { final EntityManagerTracker entityManagerTracker = entityManagers.get(entityManagerFactory); if (entityManagerTracker != null) { return entityManagerTracker; } } return null; } private void transactionStarted(final InstanceId instanceId) { if (instanceId == null) { throw new NullPointerException("instanceId is null"); } if (!isTransactionActive()) { throw new TransactionRequiredException(); } final Map<EntityManagerFactory, EntityManagerTracker> entityManagers = entityManagersByDeploymentId.get(instanceId); if (entityManagers == null) { return; } for (final Map.Entry<EntityManagerFactory, EntityManagerTracker> entry : entityManagers.entrySet()) { final EntityManagerFactory entityManagerFactory = entry.getKey(); final EntityManagerTracker value = entry.getValue(); final EntityManager entityManager = value.getEntityManager(); if (value.autoJoinTx) { entityManager.joinTransaction(); } final EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory); transactionRegistry.putResource(txKey, entityManager); } } } private static class InstanceId { private final String deploymentId; private final Object primaryKey; public InstanceId(final String deploymentId, final Object primaryKey) { if (deploymentId == null) { throw new NullPointerException("deploymentId is null"); } if (primaryKey == null) { throw new NullPointerException("primaryKey is null"); } this.deploymentId = deploymentId; this.primaryKey = primaryKey; } public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final InstanceId that = (InstanceId) o; return deploymentId.equals(that.deploymentId) && primaryKey.equals(that.primaryKey); } public int hashCode() { int result; result = deploymentId.hashCode(); result = 29 * result + primaryKey.hashCode(); return result; } } /** * This object is used track all EntityManagers inherited in order * to effectively close it when the latest Extended persistence context * is no more accessed. */ public static class EntityManagerTracker { // must take care of the first inheritance level private transient int counter; private final EntityManager entityManager; private final boolean autoJoinTx; public EntityManagerTracker(final EntityManager entityManager, final boolean autoJoinTx) { if (entityManager == null) { throw new NullPointerException("entityManager is null."); } this.counter = 0; this.entityManager = entityManager; this.autoJoinTx = autoJoinTx; } public int incCounter() { return counter++; } public int decCounter() { return counter--; } public EntityManager getEntityManager() { return entityManager; } } private static class CloseEntityManager implements Synchronization { private final EntityManager entityManager; private final String unitName; public CloseEntityManager(final EntityManager entityManager, final String unitName) { this.entityManager = entityManager; this.unitName = unitName; } public void beforeCompletion() { } public void afterCompletion(final int i) { entityManager.close(); logger.debug("Closed EntityManager(unit=" + unitName + ", hashCode=" + entityManager.hashCode() + ")"); } } }