/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.jpa.container; import static org.jboss.as.jpa.messages.JpaLogger.ROOT_LOGGER; import java.io.IOException; import java.io.Serializable; import java.security.AccessController; import java.security.PrivilegedAction; import javax.persistence.EntityManager; import javax.persistence.SynchronizationType; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; import org.jboss.as.jpa.messages.JpaLogger; import org.jboss.as.jpa.transaction.TransactionUtil; import org.jboss.as.server.CurrentServiceContainer; import org.jboss.as.txn.service.TransactionManagerService; import org.jboss.as.txn.service.TransactionSynchronizationRegistryService; import org.wildfly.security.manager.WildFlySecurityManager; /** * Represents the Extended persistence context injected into a stateful bean. At bean invocation time, * will join the active JTA transaction if one is present. If no active JTA transaction is present, * created/deleted/updated/loaded entities will remain associated with the entity manager until it is joined with a * transaction (commit will save the changes, rollback will lose them). * <p/> * At injection time, an instance of this class is associated with the SFSB. * During a SFSB1 invocation, if a new SFSB2 is created with an XPC referencing the same * persistence unit, the new SFSB2 will inherit the same persistence context from SFSB1. * Both SFSB1 + SFSB2 will maintain a reference to the underlying persistence context, such that * the underlying persistence context will be kept around until both SFSB1 + SFSB2 are destroyed. * At cluster replication time or passivation, both SFSB1 + SFSB2 will be serialized consecutively and this * instance will only be serialized once. * <p/> * Note: Unlike TransactionScopedEntityManager, ExtendedEntityManager will directly be shared instead of the * underlying EntityManager. * <p/> * * During serialization, A NotSerializableException will be thrown if the following conditions are not met: * - The underlying persistence provider (entity manager) must be Serializable. * - The entity classes in the extended persistence context must also be Serializable. * * @author Scott Marlow */ public class ExtendedEntityManager extends AbstractEntityManager implements Serializable, SynchronizationTypeAccess { /** * Adding fields to this class, may require incrementing the serialVersionUID (always increment it). * If a transient field is added that isn't serialized, serialVersionUID doesn't need to change. * By default transient fields are not serialized but can be manually (de)serialized in readObject/writeObject. * Just make sure you think about whether the newly added field should be serialized. */ private static final long serialVersionUID = 432438L; /** * EntityManager obtained from the persistence provider that represents the XPC. */ private EntityManager underlyingEntityManager; /** * fully application scoped persistence unit name */ private String puScopedName; private transient boolean isInTx; /** * Track the number of stateful session beans that are referencing the extended persistence context. * when the reference count reaches zero, the persistence context is closed. */ private int referenceCount = 1; /** * the UUID representing the extended persistence context */ private final ExtendedEntityManagerKey ID = ExtendedEntityManagerKey.extendedEntityManagerID(); private final transient boolean isTraceEnabled = ROOT_LOGGER.isTraceEnabled(); private final SynchronizationType synchronizationType; private transient TransactionSynchronizationRegistry transactionSynchronizationRegistry; private transient TransactionManager transactionManager; public ExtendedEntityManager(final String puScopedName, final EntityManager underlyingEntityManager, final SynchronizationType synchronizationType, TransactionSynchronizationRegistry transactionSynchronizationRegistry, TransactionManager transactionManager) { this.underlyingEntityManager = underlyingEntityManager; this.puScopedName = puScopedName; this.synchronizationType = synchronizationType; this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; this.transactionManager = transactionManager; } /** * The JPA SFSB interceptor will track the stack of SFSB invocations. The underlying EM will be obtained from * the current SFSB being invoked (via our JPA SFSB interceptor). * * Every entity manager call (to AbstractEntityManager) will call this method to get the underlying entity manager * (e.g. the Hibernate persistence provider). * * See org.jboss.ejb3.stateful.EJB3XPCResolver.getExtendedPersistenceContext() to see the as6 implementation of this. * * @return EntityManager */ @Override protected EntityManager getEntityManager() { internalAssociateWithJtaTx(); return underlyingEntityManager; } /** * Associate the extended persistence context with the current JTA transaction (if one is found) * * this method is private to the JPA subsystem */ public void internalAssociateWithJtaTx() { isInTx = TransactionUtil.isInTx(transactionManager); // ensure that a different XPC (with same name) is not already present in the TX if (isInTx) { // 7.6.3.1 throw EJBException if a different persistence context is already joined to the // transaction (with the same puScopedName). EntityManager existing = TransactionUtil.getTransactionScopedEntityManager(puScopedName, transactionSynchronizationRegistry); if (existing != null && existing != this) { // should be enough to test if not the same object throw JpaLogger.ROOT_LOGGER.cannotUseExtendedPersistenceTransaction(puScopedName, existing, this); } else if (existing == null) { if (SynchronizationType.SYNCHRONIZED.equals(synchronizationType)) { // JPA 7.9.1 join the transaction if not already done for SynchronizationType.SYNCHRONIZED. underlyingEntityManager.joinTransaction(); } // associate the entity manager with the current transaction TransactionUtil.putEntityManagerInTransactionRegistry(puScopedName, this, transactionSynchronizationRegistry); } } } @Override protected boolean isExtendedPersistenceContext() { return true; } @Override protected boolean isInTx() { return this.isInTx; } /** * Catch the application trying to close the container managed entity manager and throw an IllegalStateException */ @Override public void close() { // An extended entity manager will be closed when the EJB SFSB @remove method is invoked. throw JpaLogger.ROOT_LOGGER.cannotCloseContainerManagedEntityManager(); } /** * Start of reference count handling. * synchronize on *this* to protect access to the referenceCount (should be mostly uncontended locks). * */ public synchronized void increaseReferenceCount() { referenceCount++; } public synchronized int getReferenceCount() { return referenceCount; } public synchronized void refCountedClose() { referenceCount--; if (referenceCount == 0) { if (underlyingEntityManager.isOpen()) { underlyingEntityManager.close(); if (isTraceEnabled) { ROOT_LOGGER.tracef("closed extended persistence context (%s)", puScopedName); } } } else if (isTraceEnabled) { ROOT_LOGGER.tracef("decremented extended persistence context (%s) owner count to %d", puScopedName, referenceCount); } // referenceCount should never be negative, if it is we need to fix the bug that caused it to decrement too much if (referenceCount < 0) { throw JpaLogger.ROOT_LOGGER.referenceCountedEntityManagerNegativeCount(referenceCount, getScopedPuName()); } } /** * End of reference count handling */ @Override public String toString() { return "ExtendedEntityManager [" + puScopedName + "]"; } /** * Get the fully application scoped persistence unit name * Private api * @return scoped pu name */ public String getScopedPuName() { return puScopedName; } /** * Check if this object's UUID is equal to the otherObject's UUID * * @param otherObject * @return */ @Override public boolean equals(Object otherObject) { if (this == otherObject) return true; if (otherObject == null || getClass() != otherObject.getClass()) return false; ExtendedEntityManager that = (ExtendedEntityManager) otherObject; if (!ID.equals(that.ID)) return false; return true; } @Override public int hashCode() { // return hashCode of the ExtendedEntityManagerKey return ID != null ? ID.hashCode() : 0; } @Override // SynchronizationTypeAccess public SynchronizationType getSynchronizationType() { return synchronizationType; } @Override protected boolean deferEntityDetachUntilClose() { return false; } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if(WildFlySecurityManager.isChecking()) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { transactionManager = (TransactionManager) CurrentServiceContainer.getServiceContainer().getService(TransactionManagerService.SERVICE_NAME).getValue(); transactionSynchronizationRegistry = (TransactionSynchronizationRegistry) CurrentServiceContainer.getServiceContainer().getService(TransactionSynchronizationRegistryService.SERVICE_NAME).getValue(); return null; } }); } else { transactionManager = (TransactionManager) CurrentServiceContainer.getServiceContainer().getService(TransactionManagerService.SERVICE_NAME).getValue(); transactionSynchronizationRegistry = (TransactionSynchronizationRegistry) CurrentServiceContainer.getServiceContainer().getService(TransactionSynchronizationRegistryService.SERVICE_NAME).getValue(); } } }