Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home



org/hibernate/engine/StatefulPersistenceContext.java
// $Id: StatefulPersistenceContext.java 14312 2008-02-05 23:55:35Z gbadner $
package org.hibernate.engine;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.PersistentObjectException;
import org.hibernate.TransientObjectException;
import org.hibernate.engine.loading.LoadContexts;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.tuple.ElementWrapper;
import org.hibernate.util.IdentityMap;
import org.hibernate.util.MarkerObject;

/**
 * A <tt>PersistenceContext</tt> represents the state of persistent "stuff" which
 * Hibernate is tracking.  This includes persistent entities, collections,
 * as well as proxies generated.
 </p>
 * There is meant to be a one-to-one correspondence between a SessionImpl and
 * a PersistentContext.  The SessionImpl uses the PersistentContext to track
 * the current state of its context.  Event-listeners then use the
 * PersistentContext to drive their processing.
 *
 @author Steve Ebersole
 */
public class StatefulPersistenceContext implements PersistenceContext {

  public static final Object NO_ROW = new MarkerObject"NO_ROW" );

  private static final Log log = LogFactory.getLogStatefulPersistenceContext.class );
  private static final Log PROXY_WARN_LOG = LogFactory.getLogStatefulPersistenceContext.class.getName() ".ProxyWarnLog" );
  private static final int INIT_COLL_SIZE = 8;

  private SessionImplementor session;
  
  // Loaded entity instances, by EntityKey
  private Map entitiesByKey;

  // Loaded entity instances, by EntityUniqueKey
  private Map entitiesByUniqueKey;
  
  // Identity map of EntityEntry instances, by the entity instance
  private Map entityEntries;
  
  // Entity proxies, by EntityKey
  private Map proxiesByKey;
  
  // Snapshots of current database state for entities
  // that have *not* been loaded
  private Map entitySnapshotsByKey;
  
  // Identity map of array holder ArrayHolder instances, by the array instance
  private Map arrayHolders;
  
  // Identity map of CollectionEntry instances, by the collection wrapper
  private Map collectionEntries;
  
  // Collection wrappers, by the CollectionKey
  private Map collectionsByKey; //key=CollectionKey, value=PersistentCollection
  
  // Set of EntityKeys of deleted objects
  private HashSet nullifiableEntityKeys;
  
  // properties that we have tried to load, and not found in the database
  private HashSet nullAssociations;
  
  // A list of collection wrappers that were instantiating during result set
  // processing, that we will need to initialize at the end of the query
  private List nonlazyCollections;
  
  // A container for collections we load up when the owning entity is not
  // yet loaded ... for now, this is purely transient!
  private Map unownedCollections;
  
  private int cascading = 0;
  private int loadCounter = 0;
  private boolean flushing = false;
  
  private boolean hasNonReadOnlyEntities = false;
  
  private LoadContexts loadContexts;
  private BatchFetchQueue batchFetchQueue;



  /**
   * Constructs a PersistentContext, bound to the given session.
   *
   @param session The session "owning" this context.
   */
  public StatefulPersistenceContext(SessionImplementor session) {
    this.session = session;

    entitiesByKey = new HashMapINIT_COLL_SIZE );
    entitiesByUniqueKey = new HashMapINIT_COLL_SIZE );
    proxiesByKey = new ReferenceMapReferenceMap.HARD, ReferenceMap.WEAK );
    entitySnapshotsByKey = new HashMapINIT_COLL_SIZE );

    entityEntries = IdentityMap.instantiateSequencedINIT_COLL_SIZE );
    collectionEntries = IdentityMap.instantiateSequencedINIT_COLL_SIZE );
    collectionsByKey = new HashMapINIT_COLL_SIZE );
    arrayHolders = IdentityMap.instantiateINIT_COLL_SIZE );

    nullifiableEntityKeys = new HashSet();

    initTransientState();
  }

  private void initTransientState() {
    nullAssociations = new HashSetINIT_COLL_SIZE );
    nonlazyCollections = new ArrayListINIT_COLL_SIZE );
  }

  public boolean isStateless() {
    return false;
  }
  
  public SessionImplementor getSession() {
    return session;
  }
  
  public LoadContexts getLoadContexts() {
    if loadContexts == null ) {
      loadContexts = new LoadContextsthis );
    }
    return loadContexts;
  }
  
  public void addUnownedCollection(CollectionKey key, PersistentCollection collection) {
    if (unownedCollections==null) {
      unownedCollections = new HashMap(8);
    }
    unownedCollections.put(key, collection);
  }
  
  public PersistentCollection useUnownedCollection(CollectionKey key) {
    if (unownedCollections==null) {
      return null;
    }
    else {
      return (PersistentCollectionunownedCollections.remove(key);
    }
  }
  
  /**
   * Get the <tt>BatchFetchQueue</tt>, instantiating one if
   * necessary.
   */
  public BatchFetchQueue getBatchFetchQueue() {
    if (batchFetchQueue==null) {
      batchFetchQueue = new BatchFetchQueue(this);
    }
    return batchFetchQueue;
  }

  public void clear() {
    Iterator itr = proxiesByKey.values().iterator();
    while itr.hasNext() ) {
      final LazyInitializer li = ( ( HibernateProxy itr.next() ).getHibernateLazyInitializer();
      li.setSessionnull );
    }
    Map.Entry[] collectionEntryArray = IdentityMap.concurrentEntriescollectionEntries );
    for int i = 0; i < collectionEntryArray.length; i++ ) {
      ( ( PersistentCollection collectionEntryArray[i].getKey() ).unsetSessiongetSession() );
    }
    arrayHolders.clear();
    entitiesByKey.clear();
    entitiesByUniqueKey.clear();
    entityEntries.clear();
    entitySnapshotsByKey.clear();
    collectionsByKey.clear();
    collectionEntries.clear();
    if unownedCollections != null ) {
      unownedCollections.clear();
    }
    proxiesByKey.clear();
    nullifiableEntityKeys.clear();
    if batchFetchQueue != null ) {
      batchFetchQueue.clear();
    }
    hasNonReadOnlyEntities = false;
    if loadContexts != null ) {
      loadContexts.cleanup();
    }
  }
  
  public boolean hasNonReadOnlyEntities() {
    return hasNonReadOnlyEntities;
  }
  
  public void setEntryStatus(EntityEntry entry, Status status) {
    entry.setStatus(status);
    setHasNonReadOnlyEnties(status);
  }
  
  private void setHasNonReadOnlyEnties(Status status) {
    if status==Status.DELETED || status==Status.MANAGED || status==Status.SAVING ) {
      hasNonReadOnlyEntities = true;
    }
  }

  public void afterTransactionCompletion() {
    // Downgrade locks
    Iterator iter = entityEntries.values().iterator();
    while iter.hasNext() ) {
      ( (EntityEntryiter.next() ).setLockMode(LockMode.NONE);
    }
  }

  /**
   * Get the current state of the entity as known to the underlying
   * database, or null if there is no corresponding row 
   */
  public Object[] getDatabaseSnapshot(Serializable id, EntityPersister persister)
  throws HibernateException {
    EntityKey key = new EntityKeyid, persister, session.getEntityMode() );
    Object cached = entitySnapshotsByKey.get(key);
    if (cached!=null) {
      return cached==NO_ROW ? null (Object[]) cached;
    }
    else {
      Object[] snapshot = persister.getDatabaseSnapshotid, session );
      entitySnapshotsByKey.putkey, snapshot==null ? NO_ROW : snapshot );
      return snapshot;
    }
  }

  public Object[] getNaturalIdSnapshot(Serializable id, EntityPersister persister)
  throws HibernateException {
    if !persister.hasNaturalIdentifier() ) {
      return null;
    }

    // if the natural-id is marked as non-mutable, it is not retrieved during a
    // normal database-snapshot operation...
    int[] props = persister.getNaturalIdentifierProperties();
    boolean[] updateable = persister.getPropertyUpdateability();
    boolean allNatualIdPropsAreUpdateable = true;
    for int i = 0; i < props.length; i++ ) {
      if !updateableprops[i] ] ) {
        allNatualIdPropsAreUpdateable = false;
        break;
      }
    }

    if allNatualIdPropsAreUpdateable ) {
      // do this when all the properties are updateable since there is
      // a certain likelihood that the information will already be
      // snapshot-cached.
      Object[] entitySnapshot = getDatabaseSnapshotid, persister );
      if entitySnapshot == NO_ROW ) {
        return null;
      }
      Object[] naturalIdSnapshot = new Objectprops.length ];
      for int i = 0; i < props.length; i++ ) {
        naturalIdSnapshot[i= entitySnapshotprops[i] ];
      }
      return naturalIdSnapshot;
    }
    else {
      return persister.getNaturalIdentifierSnapshotid, session );
    }
  }

  /**
   * Retrieve the cached database snapshot for the requested entity key.
   <p/>
   * This differs from {@link #getDatabaseSnapshot} is two important respects:<ol>
   <li>no snapshot is obtained from the database if not already cached</li>
   <li>an entry of {@link #NO_ROW} here is interpretet as an exception</li>
   </ol>
   @param key The entity key for which to retrieve the cached snapshot
   @return The cached snapshot
   @throws IllegalStateException if the cached snapshot was == {@link #NO_ROW}.
   */
  public Object[] getCachedDatabaseSnapshot(EntityKey key) {
    Object snapshot = entitySnapshotsByKey.getkey );
    if snapshot == NO_ROW ) {
      throw new IllegalStateException"persistence context reported no row snapshot for " + MessageHelper.infoStringkey.getEntityName(), key.getIdentifier() ) );
    }
    return Object[] ) snapshot;
  }

  /*public void removeDatabaseSnapshot(EntityKey key) {
    entitySnapshotsByKey.remove(key);
  }*/

  public void addEntity(EntityKey key, Object entity) {
    entitiesByKey.put(key, entity);
    getBatchFetchQueue().removeBatchLoadableEntityKey(key);
  }

  /**
   * Get the entity instance associated with the given 
   <tt>EntityKey</tt>
   */
  public Object getEntity(EntityKey key) {
    return entitiesByKey.get(key);
  }

  public boolean containsEntity(EntityKey key) {
    return entitiesByKey.containsKey(key);
  }

  /**
   * Remove an entity from the session cache, also clear
   * up other state associated with the entity, all except
   * for the <tt>EntityEntry</tt>
   */
  public Object removeEntity(EntityKey key) {
    Object entity = entitiesByKey.remove(key);
    Iterator iter = entitiesByUniqueKey.values().iterator();
    while iter.hasNext() ) {
      if iter.next()==entity iter.remove();
    }
    entitySnapshotsByKey.remove(key);
    nullifiableEntityKeys.remove(key);
    getBatchFetchQueue().removeBatchLoadableEntityKey(key);
    getBatchFetchQueue().removeSubselect(key);
    return entity;
  }

  /**
   * Get an entity cached by unique key
   */
  public Object getEntity(EntityUniqueKey euk) {
    return entitiesByUniqueKey.get(euk);
  }

  /**
   * Add an entity to the cache by unique key
   */
  public void addEntity(EntityUniqueKey euk, Object entity) {
    entitiesByUniqueKey.put(euk, entity);
  }

  /**
   * Retreive the EntityEntry representation of the given entity.
   *
   @param entity The entity for which to locate the EntityEntry.
   @return The EntityEntry for the given entity.
   */
  public EntityEntry getEntry(Object entity) {
    return (EntityEntryentityEntries.get(entity);
  }

  /**
   * Remove an entity entry from the session cache
   */
  public EntityEntry removeEntry(Object entity) {
    return (EntityEntryentityEntries.remove(entity);
  }

  /**
   * Is there an EntityEntry for this instance?
   */
  public boolean isEntryFor(Object entity) {
    return entityEntries.containsKey(entity);
  }

  /**
   * Get the collection entry for a persistent collection
   */
  public CollectionEntry getCollectionEntry(PersistentCollection coll) {
    return (CollectionEntrycollectionEntries.get(coll);
  }

  /**
   * Adds an entity to the internal caches.
   */
  public EntityEntry addEntity(
      final Object entity,
      final Status status,
      final Object[] loadedState,
      final EntityKey entityKey,
      final Object version,
      final LockMode lockMode,
      final boolean existsInDatabase,
      final EntityPersister persister,
      final boolean disableVersionIncrement, 
      boolean lazyPropertiesAreUnfetched
  ) {
    
    addEntityentityKey, entity );
    
    return addEntry(
        entity,
        status,
        loadedState,
        null,
        entityKey.getIdentifier(),
        version,
        lockMode,
        existsInDatabase,
        persister,
        disableVersionIncrement, 
        lazyPropertiesAreUnfetched
      );
  }


  /**
   * Generates an appropriate EntityEntry instance and adds it 
   * to the event source's internal caches.
   */
  public EntityEntry addEntry(
      final Object entity,
      final Status status,
      final Object[] loadedState,
      final Object rowId,
      final Serializable id,
      final Object version,
      final LockMode lockMode,
      final boolean existsInDatabase,
      final EntityPersister persister,
      final boolean disableVersionIncrement, 
      boolean lazyPropertiesAreUnfetched) {
    
    EntityEntry e = new EntityEntry(
        status,
        loadedState,
        rowId,
        id,
        version,
        lockMode,
        existsInDatabase,
        persister,
        session.getEntityMode(),
        disableVersionIncrement,
        lazyPropertiesAreUnfetched
      );
    entityEntries.put(entity, e);
    
    setHasNonReadOnlyEnties(status);
    return e;
  }

  public boolean containsCollection(PersistentCollection collection) {
    return collectionEntries.containsKey(collection);
  }

  public boolean containsProxy(Object entity) {
    return proxiesByKey.containsValueentity );
  }
  
  /**
   * Takes the given object and, if it represents a proxy, reassociates it with this event source.
   *
   @param value The possible proxy to be reassociated.
   @return Whether the passed value represented an actual proxy which got initialized.
   @throws MappingException
   */
  public boolean reassociateIfUninitializedProxy(Object valuethrows MappingException {
    if value instanceof ElementWrapper ) {
      value = ( (ElementWrappervalue ).getElement();
    }
    
    if !Hibernate.isInitialized(value) ) {
      HibernateProxy proxy = (HibernateProxyvalue;
      LazyInitializer li = proxy.getHibernateLazyInitializer();
      reassociateProxy(li, proxy);
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * If a deleted entity instance is re-saved, and it has a proxy, we need to
   * reset the identifier of the proxy 
   */
  public void reassociateProxy(Object value, Serializable idthrows MappingException {
    if value instanceof ElementWrapper ) {
      value = ( (ElementWrappervalue ).getElement();
    }
    
    if value instanceof HibernateProxy ) {
      if log.isDebugEnabled() ) log.debug("setting proxy identifier: " + id);
      HibernateProxy proxy = (HibernateProxyvalue;
      LazyInitializer li = proxy.getHibernateLazyInitializer();
      li.setIdentifier(id);
      reassociateProxy(li, proxy);
    }
  }

  /**
   * Associate a proxy that was instantiated by another session with this session
   *
   @param li The proxy initializer.
   @param proxy The proxy to reassociate.
   */
  private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) {
    if li.getSession() != this.getSession() ) {
      EntityPersister persister = session.getFactory().getEntityPersisterli.getEntityName() );
      EntityKey key = new EntityKeyli.getIdentifier(), persister, session.getEntityMode() );
        // any earlier proxy takes precedence
      if !proxiesByKey.containsKeykey ) ) {
        proxiesByKey.putkey, proxy );
      }
      proxy.getHibernateLazyInitializer().setSessionsession );
    }
  }

  /**
   * Get the entity instance underlying the given proxy, throwing
   * an exception if the proxy is uninitialized. If the given object
   * is not a proxy, simply return the argument.
   */
  public Object unproxy(Object maybeProxythrows HibernateException {
    if maybeProxy instanceof ElementWrapper ) {
      maybeProxy = ( (ElementWrappermaybeProxy ).getElement();
    }
    
    if maybeProxy instanceof HibernateProxy ) {
      HibernateProxy proxy = (HibernateProxymaybeProxy;
      LazyInitializer li = proxy.getHibernateLazyInitializer();
      if li.isUninitialized() ) {
        throw new PersistentObjectException(
            "object was an uninitialized proxy for " +
            li.getEntityName()
        );
      }
      return li.getImplementation()//unwrap the object
    }
    else {
      return maybeProxy;
    }
  }

  /**
   * Possibly unproxy the given reference and reassociate it with the current session.
   *
   @param maybeProxy The reference to be unproxied if it currently represents a proxy.
   @return The unproxied instance.
   @throws HibernateException
   */
  public Object unproxyAndReassociate(Object maybeProxythrows HibernateException {
    if maybeProxy instanceof ElementWrapper ) {
      maybeProxy = ( (ElementWrappermaybeProxy ).getElement();
    }
    
    if maybeProxy instanceof HibernateProxy ) {
      HibernateProxy proxy = (HibernateProxymaybeProxy;
      LazyInitializer li = proxy.getHibernateLazyInitializer();
      reassociateProxy(li, proxy);
      return li.getImplementation()//initialize + unwrap the object
    }
    else {
      return maybeProxy;
    }
  }

  /**
   * Attempts to check whether the given key represents an entity already loaded within the
   * current session.
   @param object The entity reference against which to perform the uniqueness check.
   @throws HibernateException
   */
  public void checkUniqueness(EntityKey key, Object objectthrows HibernateException {
    Object entity = getEntity(key);
    if entity == object ) {
      throw new AssertionFailure"object already associated, but no entry was found" );
    }
    if entity != null ) {
      throw new NonUniqueObjectExceptionkey.getIdentifier(), key.getEntityName() );
    }
  }

  /**
   * If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy
   * and overwrite the registration of the old one. This breaks == and occurs only for
   * "class" proxies rather than "interface" proxies. Also init the proxy to point to
   * the given target implementation if necessary.
   *
   @param proxy The proxy instance to be narrowed.
   @param persister The persister for the proxied entity.
   @param key The internal cache key for the proxied entity.
   @param object (optional) the actual proxied entity instance.
   @return An appropriately narrowed instance.
   @throws HibernateException
   */
  public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object)
  throws HibernateException {
    
    boolean alreadyNarrow = persister.getConcreteProxyClasssession.getEntityMode() )
        .isAssignableFromproxy.getClass() );
    
    if !alreadyNarrow ) {
      if PROXY_WARN_LOG.isWarnEnabled() ) {
        PROXY_WARN_LOG.warn(
            "Narrowing proxy to " +
            persister.getConcreteProxyClasssession.getEntityMode() ) +
            " - this operation breaks =="
        );
      }

      if object != null ) {
        proxiesByKey.remove(key);
        return object; //return the proxied object
      }
      else {
        proxy = persister.createProxykey.getIdentifier(), session );
        proxiesByKey.put(key, proxy)//overwrite old proxy
        return proxy;
      }
      
    }
    else {
      
      if object != null ) {
        LazyInitializer li = ( (HibernateProxyproxy ).getHibernateLazyInitializer();
        li.setImplementation(object);
      }
      
      return proxy;
      
    }
    
  }

  /**
   * Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
   * third argument (the entity associated with the key) if no proxy exists. Init
   * the proxy to the target implementation, if necessary.
   */
  public Object proxyFor(EntityPersister persister, EntityKey key, Object impl
  throws HibernateException {
    if !persister.hasProxy() ) return impl;
    Object proxy = proxiesByKey.get(key);
    if proxy != null ) {
      return narrowProxy(proxy, persister, key, impl);
    }
    else {
      return impl;
    }
  }

  /**
   * Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
   * argument (the entity associated with the key) if no proxy exists.
   * (slower than the form above)
   */
  public Object proxyFor(Object implthrows HibernateException {
    EntityEntry e = getEntry(impl);
    EntityPersister p = e.getPersister();
    return proxyForp, new EntityKeye.getId(), p, session.getEntityMode() ), impl );
  }

  /**
   * Get the entity that owns this persistent collection
   */
  public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersisterthrows MappingException {
    return getEntitynew EntityKeykey, collectionPersister.getOwnerEntityPersister(), session.getEntityMode() ) );
  }

  /**
   * Get the entity that owned this persistent collection when it was loaded
   *
   @param collection The persistent collection
   @return the owner, if its entity ID is available from the collection's loaded key
   * and the owner entity is in the persistence context; otherwise, returns null
   */
  public Object getLoadedCollectionOwnerOrNull(PersistentCollection collection) {
    CollectionEntry ce = getCollectionEntrycollection );
    if ce.getLoadedPersister() == null ) {
      return null// early exit...
    }
    Object loadedOwner = null;
    // TODO: an alternative is to check if the owner has changed; if it hasn't then
    // return collection.getOwner()
    Serializable entityId = getLoadedCollectionOwnerIdOrNullce );
    if entityId != null ) {
      loadedOwner = getCollectionOwnerentityId, ce.getLoadedPersister() );
    }
    return loadedOwner;
  }

  /**
   * Get the ID for the entity that owned this persistent collection when it was loaded
   *
   @param collection The persistent collection
   @return the owner ID if available from the collection's loaded key; otherwise, returns null
   */
  public Serializable getLoadedCollectionOwnerIdOrNull(PersistentCollection collection) {
    return getLoadedCollectionOwnerIdOrNullgetCollectionEntrycollection ) );
  }

  /**
   * Get the ID for the entity that owned this persistent collection when it was loaded
   *
   @param ce The collection entry
   @return the owner ID if available from the collection's loaded key; otherwise, returns null
   */
  private Serializable getLoadedCollectionOwnerIdOrNull(CollectionEntry ce) {
    if ce == null || ce.getLoadedKey() == null || ce.getLoadedPersister() == null ) {
      return null;
    }
    // TODO: an alternative is to check if the owner has changed; if it hasn't then
    // get the ID from collection.getOwner()
    return ce.getLoadedPersister().getCollectionType().getIdOfOwnerOrNullce.getLoadedKey(), session );
  }

  /**
   * add a collection we just loaded up (still needs initializing)
   */
  public void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id) {
    CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
    addCollection(collection, ce, id);
  }

  /**
   * add a detached uninitialized collection
   */
  public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection) {
    CollectionEntry ce = new CollectionEntrypersister, collection.getKey() );
    addCollectioncollection, ce, collection.getKey() );
  }

  /**
   * Add a new collection (ie. a newly created one, just instantiated by the
   * application, with no database state or snapshot)
   @param collection The collection to be associated with the persistence context
   */
  public void addNewCollection(CollectionPersister persister, PersistentCollection collection)
  throws HibernateException {
    addCollection(collection, persister);
  }
  
  /**
   * Add an collection to the cache, with a given collection entry.
   *
   @param coll The collection for which we are adding an entry.
   @param entry The entry representing the collection.
   @param key The key of the collection's entry.
   */
  private void addCollection(PersistentCollection coll, CollectionEntry entry, Serializable key) {
    collectionEntries.putcoll, entry );
    CollectionKey collectionKey = new CollectionKeyentry.getLoadedPersister(), key, session.getEntityMode() );
    PersistentCollection old = PersistentCollection collectionsByKey.putcollectionKey, coll );
    if old != null ) {
      if old == coll ) {
        throw new AssertionFailure("bug adding collection twice");
      }
      // or should it actually throw an exception?
      old.unsetSessionsession );
      collectionEntries.removeold );
      // watch out for a case where old is still referenced
      // somewhere in the object graph! (which is a user error)
    }
  }

  /**
   * Add a collection to the cache, creating a new collection entry for it
   *
   @param collection The collection for which we are adding an entry.
   @param persister The collection persister
   */
  private void addCollection(PersistentCollection collection, CollectionPersister persister) {
    CollectionEntry ce = new CollectionEntrypersister, collection );
    collectionEntries.putcollection, ce );
  }

  /**
   * add an (initialized) collection that was created by another session and passed
   * into update() (ie. one with a snapshot and existing state on the database)
   */
  public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection collection
  throws HibernateException {
    if collection.isUnreferenced() ) {
      //treat it just like a new collection
      addCollectioncollection, collectionPersister );
    }
    else {
      CollectionEntry ce = new CollectionEntrycollection, session.getFactory() );
      addCollectioncollection, ce, collection.getKey() );
    }
  }

  /**
   * add a collection we just pulled out of the cache (does not need initializing)
   */
  public CollectionEntry addInitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id)
  throws HibernateException {
    CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
    ce.postInitialize(collection);
    addCollection(collection, ce, id);
    return ce;
  }
  
  /**
   * Get the collection instance associated with the <tt>CollectionKey</tt>
   */
  public PersistentCollection getCollection(CollectionKey collectionKey) {
    return (PersistentCollectioncollectionsByKey.get(collectionKey);
  }
  
  /**
   * Register a collection for non-lazy loading at the end of the
   * two-phase load
   */
  public void addNonLazyCollection(PersistentCollection collection) {
    nonlazyCollections.add(collection);
  }

  /**
   * Force initialization of all non-lazy collections encountered during
   * the current two-phase load (actually, this is a no-op, unless this
   * is the "outermost" load)
   */
  public void initializeNonLazyCollections() throws HibernateException {
    if loadCounter == ) {
      log.debug"initializing non-lazy collections" );
      //do this work only at the very highest level of the load
      loadCounter++; //don't let this method be called recursively
      try {
        int size;
        while ( ( size = nonlazyCollections.size() ) ) {
          //note that each iteration of the loop may add new elements
          ( (PersistentCollectionnonlazyCollections.removesize - ) ).forceInitialization();
        }
      }
      finally {
        loadCounter--;
        clearNullProperties();
      }
    }
  }


  /**
   * Get the <tt>PersistentCollection</tt> object for an array
   */
  public PersistentCollection getCollectionHolder(Object array) {
    return (PersistentCollectionarrayHolders.get(array);
  }

  /**
   * Register a <tt>PersistentCollection</tt> object for an array.
   * Associates a holder with an array - MUST be called after loading 
   * array, since the array instance is not created until endLoad().
   */
  public void addCollectionHolder(PersistentCollection holder) {
    //TODO:refactor + make this method private
    arrayHolders.putholder.getValue(), holder );
  }

  public PersistentCollection removeCollectionHolder(Object array) {
    return (PersistentCollectionarrayHolders.remove(array);
  }

  /**
   * Get the snapshot of the pre-flush collection state
   */
  public Serializable getSnapshot(PersistentCollection coll) {
    return getCollectionEntry(coll).getSnapshot();
  }

  /**
   * Get the collection entry for a collection passed to filter,
   * which might be a collection wrapper, an array, or an unwrapped
   * collection. Return null if there is no entry.
   */
  public CollectionEntry getCollectionEntryOrNull(Object collection) {
    PersistentCollection coll;
    if collection instanceof PersistentCollection ) {
      coll = (PersistentCollectioncollection;
      //if (collection==null) throw new TransientObjectException("Collection was not yet persistent");
    }
    else {
      coll = getCollectionHolder(collection);
      if coll == null ) {
        //it might be an unwrapped collection reference!
        //try to find a wrapper (slowish)
        Iterator wrappers = IdentityMap.keyIterator(collectionEntries);
        while wrappers.hasNext() ) {
          PersistentCollection pc = (PersistentCollectionwrappers.next();
          if pc.isWrapper(collection) ) {
            coll = pc;
            break;
          }
        }
      }
    }

    return (coll == nullnull : getCollectionEntry(coll);
  }

  /**
   * Get an existing proxy by key
   */
  public Object getProxy(EntityKey key) {
    return proxiesByKey.get(key);
  }

  /**
   * Add a proxy to the session cache
   */
  public void addProxy(EntityKey key, Object proxy) {
    proxiesByKey.put(key, proxy);
  }

  /**
   * Remove a proxy from the session cache.
   <p/>
   * Additionally, ensure that any load optimization references
   * such as batch or subselect loading get cleaned up as well.
   *
   @param key The key of the entity proxy to be removed
   @return The proxy reference.
   */
  public Object removeProxy(EntityKey key) {
    if batchFetchQueue != null ) {
      batchFetchQueue.removeBatchLoadableEntityKeykey );
      batchFetchQueue.removeSubselectkey );
    }
    return proxiesByKey.removekey );
  }

  /**
   * Record the fact that an entity does not exist in the database
   
   @param key the primary key of the entity
   */
  /*public void addNonExistantEntityKey(EntityKey key) {
    nonExistantEntityKeys.add(key);
  }*/

  /**
   * Record the fact that an entity does not exist in the database
   
   @param key a unique key of the entity
   */
  /*public void addNonExistantEntityUniqueKey(EntityUniqueKey key) {
    nonExistentEntityUniqueKeys.add(key);
  }*/

  /*public void removeNonExist(EntityKey key) {
    nonExistantEntityKeys.remove(key);
  }*/

  /** 
   * Retrieve the set of EntityKeys representing nullifiable references
   */
  public HashSet getNullifiableEntityKeys() {
    return nullifiableEntityKeys;
  }

  public Map getEntitiesByKey() {
    return entitiesByKey;
  }

  public Map getEntityEntries() {
    return entityEntries;
  }

  public Map getCollectionEntries() {
    return collectionEntries;
  }

  public Map getCollectionsByKey() {
    return collectionsByKey;
  }

  /**
   * Do we already know that the entity does not exist in the
   * database?
   */
  /*public boolean isNonExistant(EntityKey key) {
    return nonExistantEntityKeys.contains(key);
  }*/

  /**
   * Do we already know that the entity does not exist in the
   * database?
   */
  /*public boolean isNonExistant(EntityUniqueKey key) {
    return nonExistentEntityUniqueKeys.contains(key);
  }*/

  public int getCascadeLevel() {
    return cascading;
  }

  public int incrementCascadeLevel() {
    return ++cascading;
  }

  public int decrementCascadeLevel() {
    return --cascading;
  }

  public boolean isFlushing() {
    return flushing;
  }

  public void setFlushing(boolean flushing) {
    this.flushing = flushing;
  }

  /**
   * Call this before begining a two-phase load
   */
  public void beforeLoad() {
    loadCounter++;
  }

  /**
   * Call this after finishing a two-phase load
   */
  public void afterLoad() {
    loadCounter--;
  }

  /**
   * Returns a string representation of the object.
   *
   @return a string representation of the object.
   */
  public String toString() {
    return new StringBuffer()
        .append("PersistenceContext[entityKeys=")
        .append(entitiesByKey.keySet())
        .append(",collectionKeys=")
        .append(collectionsByKey.keySet())
        .append("]")
        .toString();
  }
  
  /**
   * Search <tt>this</tt> persistence context for an associated entity instance which is considered the "owner" of
   * the given <tt>childEntity</tt>, and return that owner's id value.  This is performed in the scenario of a
   * uni-directional, non-inverse one-to-many collection (which means that the collection elements do not maintain
   * a direct reference to the owner).
   <p/>
   * As such, the processing here is basically to loop over every entity currently associated with this persistence
   * context and for those of the correct entity (sub) type to extract its collection role property value and see
   * if the child is contained within that collection.  If so, we have found the owner; if not, we go on.
   <p/>
   * Also need to account for <tt>mergeMap</tt> which acts as a local copy cache managed for the duration of a merge
   * operation.  It represents a map of the detached entity instances pointing to the corresponding managed instance.
   *
   @param entityName The entity name for the entity type which would own the child
   @param propertyName The name of the property on the owning entity type which would name this child association.
   @param childEntity The child entity instance for which to locate the owner instance id.
   @param mergeMap A map of non-persistent instances from an on-going merge operation (possibly null).
   *
   @return The id of the entityName instance which is said to own the child; null if an appropriate owner not
   * located.
   */
  public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) {
    final String collectionRole = entityName + '.' + propertyName;
    final EntityPersister persister = session.getFactory().getEntityPersisterentityName );
    final CollectionPersister collectionPersister = session.getFactory().getCollectionPersistercollectionRole );

    // iterate all the entities currently associated with the persistence context.
    Iterator entities = entityEntries.entrySet().iterator();
    while entities.hasNext() ) {
      final Map.Entry me = Map.Entry entities.next();
      final EntityEntry entityEntry = EntityEntry me.getValue();
      // does this entity entry pertain to the entity persister in which we are interested (owner)?
      if persister.isSubclassEntityNameentityEntry.getEntityName() ) ) {
        final Object entityEntryInstance = me.getKey();

        //check if the managed object is the parent
        boolean found = isFoundInParent(
            propertyName,
            childEntity,
            persister,
            collectionPersister,
            entityEntryInstance
        );

        if !found && mergeMap != null ) {
          //check if the detached object being merged is the parent
          Object unmergedInstance = mergeMap.getentityEntryInstance );
          Object unmergedChild = mergeMap.getchildEntity );
          if unmergedInstance != null && unmergedChild != null ) {
            found = isFoundInParent(
                propertyName,
                unmergedChild,
                persister,
                collectionPersister,
                unmergedInstance
            );
          }
        }

        if found ) {
          return entityEntry.getId();
        }

      }
    }

    // if we get here, it is possible that we have a proxy 'in the way' of the merge map resolution...
    //     NOTE: decided to put this here rather than in the above loop as I was nervous about the performance
    //    of the loop-in-loop especially considering this is far more likely the 'edge case'
    if mergeMap != null ) {
      Iterator mergeMapItr = mergeMap.entrySet().iterator();
      while mergeMapItr.hasNext() ) {
        final Map.Entry mergeMapEntry = Map.Entry mergeMapItr.next();
        if mergeMapEntry.getKey() instanceof HibernateProxy ) {
          final HibernateProxy proxy = HibernateProxy mergeMapEntry.getKey();
          if persister.isSubclassEntityNameproxy.getHibernateLazyInitializer().getEntityName() ) ) {
            boolean found = isFoundInParent(
                propertyName,
                childEntity,
                persister,
                collectionPersister,
                mergeMap.getproxy )
            );
            if !found ) {
              found = isFoundInParent(
                  propertyName,
                  mergeMap.getchildEntity ),
                  persister,
                  collectionPersister,
                  mergeMap.getproxy )
              );
            }
            if found ) {
              return proxy.getHibernateLazyInitializer().getIdentifier();
            }
          }
        }
      }
    }

    return null;
  }

  private boolean isFoundInParent(
      String property,
      Object childEntity,
      EntityPersister persister,
      CollectionPersister collectionPersister,
      Object potentialParent) {
    Object collection = persister.getPropertyValue(
        potentialParent,
        property,
        session.getEntityMode()
    );
    return collection != null
        && Hibernate.isInitializedcollection 
        && collectionPersister.getCollectionType().containscollection, childEntity, session );
  }

  /**
   * Search the persistence context for an index of the child object,
   * given a collection role
   */
  public Object getIndexInOwner(String entity, String property, Object childEntity, Map mergeMap) {

    EntityPersister persister = session.getFactory()
        .getEntityPersister(entity);
    CollectionPersister cp = session.getFactory()
        .getCollectionPersister(entity + '.' + property);
    Iterator entities = entityEntries.entrySet().iterator();
    while entities.hasNext() ) {
      Map.Entry me = (Map.Entryentities.next();
      EntityEntry ee = (EntityEntryme.getValue();
      if persister.isSubclassEntityNameee.getEntityName() ) ) {
        Object instance = me.getKey();
        
        Object index = getIndexInParent(property, childEntity, persister, cp, instance);
        
        if (index==null && mergeMap!=null) {
          Object unmergedInstance = mergeMap.get(instance);
          Object unmergedChild = mergeMap.get(childEntity);
          if unmergedInstance!=null && unmergedChild!=null ) {
            index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
          }
        }
        
        if (index!=nullreturn index;
      }
    }
    return null;
  }
  
  private Object getIndexInParent(
      String property, 
      Object childEntity, 
      EntityPersister persister, 
      CollectionPersister collectionPersister,
      Object potentialParent
  ){  
    Object collection = persister.getPropertyValuepotentialParent, property, session.getEntityMode() );
    if collection!=null && Hibernate.isInitialized(collection) ) {
      return collectionPersister.getCollectionType().indexOf(collection, childEntity);
    }
    else {
      return null;
    }
  }
  
  /**
   * Record the fact that the association belonging to the keyed
   * entity is null.
   */
  public void addNullProperty(EntityKey ownerKey, String propertyName) {
    nullAssociations.addnew AssociationKey(ownerKey, propertyName) );
  }
  
  /**
   * Is the association property belonging to the keyed entity null?
   */
  public boolean isPropertyNull(EntityKey ownerKey, String propertyName) {
    return nullAssociations.containsnew AssociationKey(ownerKey, propertyName) );
  }
  
  private void clearNullProperties() {
    nullAssociations.clear();
  }

  public void setReadOnly(Object entity, boolean readOnly) {
    EntityEntry entry = getEntry(entity);
    if (entry==null) {
      throw new TransientObjectException("Instance was not associated with the session");
    }
    entry.setReadOnly(readOnly, entity);
    hasNonReadOnlyEntities = hasNonReadOnlyEntities || !readOnly;
  }

  public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) {
    Object entity = entitiesByKey.removeoldKey );
    EntityEntry oldEntry = EntityEntry entityEntries.removeentity );

    EntityKey newKey = new EntityKeygeneratedId, oldEntry.getPersister(), getSession().getEntityMode() );
    addEntitynewKey, entity );
    addEntry(
        entity,
            oldEntry.getStatus(),
            oldEntry.getLoadedState(),
            oldEntry.getRowId(),
            generatedId,
            oldEntry.getVersion(),
            oldEntry.getLockMode(),
            oldEntry.isExistsInDatabase(),
            oldEntry.getPersister(),
            oldEntry.isBeingReplicated(),
            oldEntry.isLoadedWithLazyPropertiesUnfetched()
    );
  }

  /**
   * Used by the owning session to explicitly control serialization of the
   * persistence context.
   *
   @param oos The stream to which the persistence context should get written
   @throws IOException serialization errors.
   */
  public void serialize(ObjectOutputStream oosthrows IOException {
    log.trace"serializing persistent-context" );

    oos.writeBooleanhasNonReadOnlyEntities );

    oos.writeIntentitiesByKey.size() );
    log.trace"starting serialization of [" + entitiesByKey.size() "] entitiesByKey entries" );
    Iterator itr = entitiesByKey.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      ( ( EntityKey entry.getKey() ).serializeoos );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntentitiesByUniqueKey.size() );
    log.trace"starting serialization of [" + entitiesByUniqueKey.size() "] entitiesByUniqueKey entries" );
    itr = entitiesByUniqueKey.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      ( ( EntityUniqueKey entry.getKey() ).serializeoos );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntproxiesByKey.size() );
    log.trace"starting serialization of [" + proxiesByKey.size() "] proxiesByKey entries" );
    itr = proxiesByKey.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      ( ( EntityKey entry.getKey() ).serializeoos );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntentitySnapshotsByKey.size() );
    log.trace"starting serialization of [" + entitySnapshotsByKey.size() "] entitySnapshotsByKey entries" );
    itr = entitySnapshotsByKey.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      ( ( EntityKey entry.getKey() ).serializeoos );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntentityEntries.size() );
    log.trace"starting serialization of [" + entityEntries.size() "] entityEntries entries" );
    itr = entityEntries.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      oos.writeObjectentry.getKey() );
      ( ( EntityEntry entry.getValue() ).serializeoos );
    }

    oos.writeIntcollectionsByKey.size() );
    log.trace"starting serialization of [" + collectionsByKey.size() "] collectionsByKey entries" );
    itr = collectionsByKey.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      ( ( CollectionKey entry.getKey() ).serializeoos );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntcollectionEntries.size() );
    log.trace"starting serialization of [" + collectionEntries.size() "] collectionEntries entries" );
    itr = collectionEntries.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      oos.writeObjectentry.getKey() );
      ( ( CollectionEntry entry.getValue() ).serializeoos );
    }

    oos.writeIntarrayHolders.size() );
    log.trace"starting serialization of [" + arrayHolders.size() "] arrayHolders entries" );
    itr = arrayHolders.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      oos.writeObjectentry.getKey() );
      oos.writeObjectentry.getValue() );
    }

    oos.writeIntnullifiableEntityKeys.size() );
    log.trace"starting serialization of [" + nullifiableEntityKeys.size() "] nullifiableEntityKeys entries" );
    itr = nullifiableEntityKeys.iterator();
    while itr.hasNext() ) {
      EntityKey entry = EntityKey itr.next();
      entry.serializeoos );
    }
  }

  public static StatefulPersistenceContext deserialize(
      ObjectInputStream ois,
          SessionImplementor sessionthrows IOException, ClassNotFoundException {
    log.trace"deserializing persistent-context" );
    StatefulPersistenceContext rtn = new StatefulPersistenceContextsession );

    // during deserialization, we need to reconnect all proxies and
    // collections to this session, as well as the EntityEntry and
    // CollectionEntry instances; these associations are transient
    // because serialization is used for different things.

    try {
      // todo : we can actually just determine this from the incoming EntityEntry-s
      rtn.hasNonReadOnlyEntities = ois.readBoolean();

      int count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] entitiesByKey entries" );
      rtn.entitiesByKey = new HashMapcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        rtn.entitiesByKey.putEntityKey.deserializeois, session ), ois.readObject() );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] entitiesByUniqueKey entries" );
      rtn.entitiesByUniqueKey = new HashMapcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        rtn.entitiesByUniqueKey.putEntityUniqueKey.deserializeois, session ), ois.readObject() );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] proxiesByKey entries" );
      rtn.proxiesByKey = new ReferenceMapReferenceMap.HARD, ReferenceMap.WEAK, count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count, .75f );
      for int i = 0; i < count; i++ ) {
        EntityKey ek = EntityKey.deserializeois, session );
        Object proxy = ois.readObject();
        if proxy instanceof HibernateProxy ) {
          ( ( HibernateProxy proxy ).getHibernateLazyInitializer().setSessionsession );
          rtn.proxiesByKey.putek, proxy );
        }
        else {
          log.trace"encountered prunded proxy" );
        }
        // otherwise, the proxy was pruned during the serialization process
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] entitySnapshotsByKey entries" );
      rtn.entitySnapshotsByKey = new HashMapcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        rtn.entitySnapshotsByKey.putEntityKey.deserializeois, session ), ois.readObject() );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] entityEntries entries" );
      rtn.entityEntries = IdentityMap.instantiateSequencedcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        Object entity = ois.readObject();
        EntityEntry entry = EntityEntry.deserializeois, session );
        rtn.entityEntries.putentity, entry );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] collectionsByKey entries" );
      rtn.collectionsByKey = new HashMapcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        rtn.collectionsByKey.putCollectionKey.deserializeois, session ), ois.readObject() );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] collectionEntries entries" );
      rtn.collectionEntries = IdentityMap.instantiateSequencedcount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        final PersistentCollection pc = PersistentCollection ois.readObject();
        final CollectionEntry ce = CollectionEntry.deserializeois, session );
        pc.setCurrentSessionsession );
        rtn.collectionEntries.putpc, ce );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] arrayHolders entries" );
      rtn.arrayHolders = IdentityMap.instantiatecount < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
      for int i = 0; i < count; i++ ) {
        rtn.arrayHolders.putois.readObject(), ois.readObject() );
      }

      count = ois.readInt();
      log.trace"staring deserialization of [" + count + "] nullifiableEntityKeys entries" );
      rtn.nullifiableEntityKeys = new HashSet();
      for int i = 0; i < count; i++ ) {
        rtn.nullifiableEntityKeys.addEntityKey.deserializeois, session ) );
      }

    }
    catch HibernateException he ) {
      throw new InvalidObjectExceptionhe.getMessage() );
    }

    return rtn;
  }
}