blob: 24624be27d0fcb66230174ce1e173c0dc9961b18 [file] [log] [blame]
/*
* 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.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(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(EntityManagerFactory entityManagerFactory, Map properties, boolean extended, String unitName) throws IllegalStateException {
if (entityManagerFactory == null) throw new NullPointerException("entityManagerFactory is null");
EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
boolean transactionActive = isTransactionActive();
// if we have an active transaction, check the tx registry
if (transactionActive) {
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) {
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");
}
EntityManager entityManager = entityManagerTracker.getEntityManager();
// if transaction is active, we need to register the entity manager with the transaction manager
if (transactionActive) {
entityManager.joinTransaction();
transactionRegistry.putResource(txKey, entityManager);
}
return entityManager;
} else {
// create a new entity manager
EntityManager entityManager;
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(String deploymentId, Object primaryKey, 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(String deploymentId, 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(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(String deploymentId, 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() {
int txStatus = transactionRegistry.getTransactionStatus();
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(InstanceId instanceId, 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 (Map.Entry<EntityManagerFactory, EntityManagerTracker> entry : entityManagers.entrySet()) {
EntityManagerFactory entityManagerFactory = entry.getKey();
EntityManager entityManager = entry.getValue().getEntityManager();
EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
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");
}
entityManager.joinTransaction();
transactionRegistry.putResource(txKey, entityManager);
}
}
entityManagersByDeploymentId.put(instanceId, entityManagers);
}
private Map<EntityManagerFactory, EntityManagerTracker> removeEntityManagers(InstanceId instanceId) {
if (instanceId == null) {
throw new NullPointerException("InstanceId is null");
}
return entityManagersByDeploymentId.remove(instanceId);
}
private EntityManagerTracker getInheritedEntityManager(EntityManagerFactory entityManagerFactory) {
if (entityManagerFactory == null) {
throw new NullPointerException("entityManagerFactory is null");
}
for (Map<EntityManagerFactory, EntityManagerTracker> entityManagers : entityManagersByDeploymentId.values()) {
EntityManagerTracker entityManagerTracker = entityManagers.get(entityManagerFactory);
if (entityManagerTracker != null) {
return entityManagerTracker;
}
}
return null;
}
private void transactionStarted(InstanceId instanceId) {
if (instanceId == null) {
throw new NullPointerException("instanceId is null");
}
if (!isTransactionActive()) {
throw new TransactionRequiredException();
}
Map<EntityManagerFactory, EntityManagerTracker> entityManagers = entityManagersByDeploymentId.get(instanceId);
if (entityManagers == null) {
return;
}
for (Map.Entry<EntityManagerFactory, EntityManagerTracker> entry : entityManagers.entrySet()) {
EntityManagerFactory entityManagerFactory = entry.getKey();
EntityManager entityManager = entry.getValue().getEntityManager();
entityManager.joinTransaction();
EntityManagerTxKey txKey = new EntityManagerTxKey(entityManagerFactory);
transactionRegistry.putResource(txKey, entityManager);
}
}
}
private static class InstanceId {
private final String deploymentId;
private final Object primaryKey;
public InstanceId(String deploymentId, 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(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 EntityManager entityManager;
public EntityManagerTracker(EntityManager entityManager) {
if (entityManager == null) throw new NullPointerException("entityManager is null.");
this.counter = 0;
this.entityManager = entityManager;
}
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 String unitName;
public CloseEntityManager(EntityManager entityManager, String unitName) {
this.entityManager = entityManager;
this.unitName = unitName;
}
public void beforeCompletion() {
}
public void afterCompletion(int i) {
entityManager.close();
logger.debug("Closed EntityManager(unit=" + unitName + ", hashCode=" + entityManager.hashCode() + ")");
}
}
}