/**
   * 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;
  }
/**
 * 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 {

  /**
   * 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;

  public ExtendedEntityManager(
      final String puScopedName,
      final EntityManager underlyingEntityManager,
      final SynchronizationType synchronizationType) {
    this.underlyingEntityManager = underlyingEntityManager;
    this.puScopedName = puScopedName;
    this.synchronizationType = synchronizationType;
  }

  /**
   * 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).
   *
   * <p>Every entity manager call (to AbstractEntityManager) will call this method to get the
   * underlying entity manager (e.g. the Hibernate persistence provider).
   *
   * <p>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)
   *
   * <p>this method is private to the JPA subsystem
   */
  public void internalAssociateWithJtaTx() {
    isInTx = TransactionUtil.isInTx();

    // 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);
      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);
      }
    }
  }

  @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
  public SynchronizationType getSynchronizationType() {
    return SynchronizationType.SYNCHRONIZED;
  }

  @Override
  protected boolean deferEntityDetachUntilClose() {
    return false;
  }
}
 @Override
 public int hashCode() {
   // return hashCode of the ExtendedEntityManagerKey
   return ID != null ? ID.hashCode() : 0;
 }