Open Source Repository

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


org/hibernate/engine/CollectionEntry.java
//$Id: CollectionEntry.java 9551 2006-03-04 03:49:55Z [email protected] $
package org.hibernate.engine;

import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.pretty.MessageHelper;

/**
 * We need an entry to tell us all about the current state
 * of a collection with respect to its persistent state
 
 @author Gavin King
 */
public final class CollectionEntry implements Serializable {

  private static final Log log = LogFactory.getLog(CollectionEntry.class);
  
  //ATTRIBUTES MAINTAINED BETWEEN FLUSH CYCLES
  
  // session-start/post-flush persistent state
  private Serializable snapshot;
  // allow the CollectionSnapshot to be serialized
  private String role;
  
  // "loaded" means the reference that is consistent 
  // with the current database state
  private transient CollectionPersister loadedPersister;
  private Serializable loadedKey;

  // ATTRIBUTES USED ONLY DURING FLUSH CYCLE
  
  // during flush, we navigate the object graph to
  // collections and decide what to do with them
  private transient boolean reached;
  private transient boolean processed;
  private transient boolean doupdate;
  private transient boolean doremove;
  private transient boolean dorecreate;
  // if we instantiate a collection during the flush() process,
  // we must ignore it for the rest of the flush()
  private transient boolean ignore;
  
  // "current" means the reference that was found during flush() 
  private transient CollectionPersister currentPersister;
  private transient Serializable currentKey;

  /**
   * For newly wrapped collections, or dereferenced collection wrappers
   */
  public CollectionEntry(CollectionPersister persister, PersistentCollection collection) {
    // new collections that get found + wrapped
    // during flush shouldn't be ignored
    ignore = false;

    collection.clearDirty()//a newly wrapped collection is NOT dirty (or we get unnecessary version updates)
    
    snapshot = persister.isMutable() ?
        collection.getSnapshot(persister:
        null;
    collection.setSnapshot(loadedKey, role, snapshot);
  }

  /**
   * For collections just loaded from the database
   */
  public CollectionEntry(
      final PersistentCollection collection, 
      final CollectionPersister loadedPersister, 
      final Serializable loadedKey, 
      final boolean ignore
  ) {
    this.ignore=ignore;

    //collection.clearDirty()
    
    this.loadedKey = loadedKey;
    setLoadedPersister(loadedPersister);

    collection.setSnapshot(loadedKey, role, null);

    //postInitialize() will be called after initialization
  }

  /**
   * For uninitialized detached collections
   */
  public CollectionEntry(CollectionPersister loadedPersister, Serializable loadedKey) {
    // detached collection wrappers that get found + reattached
    // during flush shouldn't be ignored
    ignore = false;

    //collection.clearDirty()
    
    this.loadedKey = loadedKey;
    setLoadedPersister(loadedPersister);
  }
  
  /**
   * For initialized detached collections
   */
  CollectionEntry(PersistentCollection collection, SessionFactoryImplementor factory)
  throws MappingException {
    // detached collections that get found + reattached
    // during flush shouldn't be ignored
    ignore = false;

    loadedKey = collection.getKey();
    setLoadedPersisterfactory.getCollectionPersistercollection.getRole() ) );

    snapshot = collection.getStoredSnapshot();    
  }

  /**
   * Used from custom serialization.
   *
   @see #serialize
   @see #deserialize
   */
  private CollectionEntry(
      String role,
          Serializable snapshot,
          Serializable loadedKey,
          SessionFactoryImplementor factory) {
    this.role = role;
    this.snapshot = snapshot;
    this.loadedKey = loadedKey;
    if role != null ) {
      afterDeserializefactory );
    }
  }

  /**
   * Determine if the collection is "really" dirty, by checking dirtiness
   * of the collection elements, if necessary
   */
  private void dirty(PersistentCollection collectionthrows HibernateException {
    
    boolean forceDirty = collection.wasInitialized() &&
        !collection.isDirty() && //optimization
        getLoadedPersister() != null &&
        getLoadedPersister().isMutable() && //optimization
        collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization
        !collection.equalsSnapshotgetLoadedPersister() );
    
    if forceDirty ) {
      collection.dirty();
    }
    
  }

  public void preFlush(PersistentCollection collectionthrows HibernateException {
    
    boolean nonMutableChange = collection.isDirty() && 
        getLoadedPersister()!=null && 
        !getLoadedPersister().isMutable();
    if (nonMutableChange) {
      throw new HibernateException(
          "changed an immutable collection instance: " 
          MessageHelper.collectionInfoStringgetLoadedPersister().getRole(), getLoadedKey() )
        );
    }
    
    dirty(collection);
    
    if log.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
      log.debug(
          "Collection dirty: " +
          MessageHelper.collectionInfoStringgetLoadedPersister().getRole(), getLoadedKey() )
        );
    }

    setDoupdate(false);
    setDoremove(false);
    setDorecreate(false);
    setReached(false);
    setProcessed(false);
  }

  public void postInitialize(PersistentCollection collectionthrows HibernateException {
    snapshot = getLoadedPersister().isMutable() ?
        collection.getSnapshotgetLoadedPersister() ) :
        null;
    collection.setSnapshot(loadedKey, role, snapshot);
  }

  /**
   * Called after a successful flush
   */
  public void postFlush(PersistentCollection collectionthrows HibernateException {
    if isIgnore() ) {
      ignore = false;
    }
    else if !isProcessed() ) {
      throw new AssertionFailure"collection [" + collection.getRole() "] was not processed by flush()" );
    }
    collection.setSnapshot(loadedKey, role, snapshot);
  }
  
  /**
   * Called after execution of an action
   */
  public void afterAction(PersistentCollection collection) {
    loadedKey = getCurrentKey();
    setLoadedPersistergetCurrentPersister() );
    
    boolean resnapshot = collection.wasInitialized() && 
        isDoremove() || isDorecreate() || isDoupdate() );
    if resnapshot ) {
      snapshot = loadedPersister==null || !loadedPersister.isMutable() 
          null 
          collection.getSnapshot(loadedPersister)//re-snapshot
    }
    
    collection.postAction();
  }

  public Serializable getKey() {
    return getLoadedKey();
  }

  public String getRole() {
    return role;
  }

  public Serializable getSnapshot() {
    return snapshot;
  }

  private void setLoadedPersister(CollectionPersister persister) {
    loadedPersister = persister;
    setRolepersister == null null : persister.getRole() );
  }
  
  void afterDeserialize(SessionFactoryImplementor factory) {
    loadedPersister = factory.getCollectionPersister(role);
  }

  public boolean wasDereferenced() {
    return getLoadedKey() == null;
  }

  public boolean isReached() {
    return reached;
  }

  public void setReached(boolean reached) {
    this.reached = reached;
  }

  public boolean isProcessed() {
    return processed;
  }

  public void setProcessed(boolean processed) {
    this.processed = processed;
  }

  public boolean isDoupdate() {
    return doupdate;
  }

  public void setDoupdate(boolean doupdate) {
    this.doupdate = doupdate;
  }

  public boolean isDoremove() {
    return doremove;
  }

  public void setDoremove(boolean doremove) {
    this.doremove = doremove;
  }

  public boolean isDorecreate() {
    return dorecreate;
  }

  public void setDorecreate(boolean dorecreate) {
    this.dorecreate = dorecreate;
  }

  public boolean isIgnore() {
    return ignore;
  }

  public CollectionPersister getCurrentPersister() {
    return currentPersister;
  }

  public void setCurrentPersister(CollectionPersister currentPersister) {
    this.currentPersister = currentPersister;
  }

  /**
   * This is only available late during the flush
   * cycle
   */
  public Serializable getCurrentKey() {
    return currentKey;
  }

  public void setCurrentKey(Serializable currentKey) {
    this.currentKey = currentKey;
  }
  
  /**
   * This is only available late during the flush cycle
   */
  public CollectionPersister getLoadedPersister() {
    return loadedPersister;
  }

  public Serializable getLoadedKey() {
    return loadedKey;
  }

  public void setRole(String role) {
    this.role = role;
  }

  public String toString() {
    String result = "CollectionEntry" 
        MessageHelper.collectionInfoStringloadedPersister.getRole(), loadedKey );
    if (currentPersister!=null) {
      result += "->" 
          MessageHelper.collectionInfoStringcurrentPersister.getRole(), currentKey );
    }
    return result;
  }

  /**
   * Get the collection orphans (entities which were removed from the collection)
   */
  public Collection getOrphans(String entityName, PersistentCollection collection
  throws HibernateException {
    if (snapshot==null) {
      throw new AssertionFailure("no collection snapshot for orphan delete");
    }
    return collection.getOrphanssnapshot, entityName );
  }

  public boolean isSnapshotEmpty(PersistentCollection collection) {
    //TODO: does this really need to be here?
    //      does the collection already have
    //      it's own up-to-date snapshot?
    return collection.wasInitialized() && 
      getLoadedPersister()==null || getLoadedPersister().isMutable() ) &&
      collection.isSnapshotEmptygetSnapshot() );
  }



  /**
   * Custom serialization routine used during serialization of a
   * Session/PersistenceContext for increased performance.
   *
   @param oos The stream to which we should write the serial data.
   @throws java.io.IOException
   */
  void serialize(ObjectOutputStream oosthrows IOException {
    oos.writeObjectrole );
    oos.writeObjectsnapshot );
    oos.writeObjectloadedKey );
  }

  /**
   * Custom deserialization routine used during deserialization of a
   * Session/PersistenceContext for increased performance.
   *
   @param ois The stream from which to read the entry.
   @param session The session being deserialized.
   @return The deserialized CollectionEntry
   @throws IOException
   @throws ClassNotFoundException
   */
  static CollectionEntry deserialize(
      ObjectInputStream ois,
          SessionImplementor sessionthrows IOException, ClassNotFoundException {
    return new CollectionEntry(
        String ois.readObject(),
            Serializable ois.readObject(),
            Serializable ois.readObject(),
            session.getFactory()
    );
  }
}