blob: 7c47cc81e544f654ba64bdc912747fd3c0c5fe06 [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.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.
*
* 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<>();
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() + ")");
}
}
}