Open Source Repository

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


org/hibernate/engine/ForeignKeys.java
//$Id: ForeignKeys.java 10136 2006-07-24 10:46:07Z [email protected] $
package org.hibernate.engine;

import java.io.Serializable;

import org.hibernate.HibernateException;
import org.hibernate.TransientObjectException;
import org.hibernate.intercept.LazyPropertyInitializer;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

/**
 * Algorithms related to foreign key constraint transparency
 
 @author Gavin King
 */
public final class ForeignKeys {
  
  private ForeignKeys() {}
  
  public static class Nullifier {
  
    private final boolean isDelete;
    private final boolean isEarlyInsert;
    private final SessionImplementor session;
    private final Object self;
    
    public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SessionImplementor session) {
      this.isDelete = isDelete;
      this.isEarlyInsert = isEarlyInsert;
      this.session = session;
      this.self = self;
    }
    
    /**
     * Nullify all references to entities that have not yet 
     * been inserted in the database, where the foreign key
     * points toward that entity
     */
    public void nullifyTransientReferences(final Object[] values, final Type[] types
    throws HibernateException {
      for int i = 0; i < types.length; i++ ) {
        values[i= nullifyTransientReferencesvalues[i], types[i] );
      }
    }
  
    /**
     * Return null if the argument is an "unsaved" entity (ie. 
     * one with no existing database row), or the input argument 
     * otherwise. This is how Hibernate avoids foreign key constraint
     * violations.
     */
    private Object nullifyTransientReferences(final Object value, final Type type
    throws HibernateException {
      if value == null ) {
        return null;
      }
      else if type.isEntityType() ) {
        EntityType entityType = (EntityTypetype;
        if entityType.isOneToOne() ) {
          return value;
        }
        else {
          String entityName = entityType.getAssociatedEntityName();
          return isNullifiable(entityName, valuenull : value;
        }
      }
      else if type.isAnyType() ) {
        return isNullifiable(null, valuenull : value;
      }
      else if type.isComponentType() ) {
        AbstractComponentType actype = (AbstractComponentTypetype;
        Object[] subvalues = actype.getPropertyValues(value, session);
        Type[] subtypes = actype.getSubtypes();
        boolean substitute = false;
        for int i = 0; i < subvalues.length; i++ ) {
          Object replacement = nullifyTransientReferencessubvalues[i], subtypes[i] );
          if replacement != subvalues[i] ) {
            substitute = true;
            subvalues[i= replacement;
          }
        }
        if (substituteactype.setPropertyValuesvalue, subvalues, session.getEntityMode() );
        return value;
      }
      else {
        return value;
      }
    }
  
    /**
     * Determine if the object already exists in the database, 
     * using a "best guess"
     */
    private boolean isNullifiable(final String entityName, Object object
    throws HibernateException {
      
      if (object==LazyPropertyInitializer.UNFETCHED_PROPERTYreturn false//this is kinda the best we can do...
      
      if object instanceof HibernateProxy ) {
        // if its an uninitialized proxy it can't be transient
        LazyInitializer li = ( (HibernateProxyobject ).getHibernateLazyInitializer();
        if li.getImplementation(session== null ) {
          return false;
          // ie. we never have to null out a reference to
          // an uninitialized proxy
        }
        else {
          //unwrap it
          object = li.getImplementation();
        }
      }
  
      // if it was a reference to self, don't need to nullify
      // unless we are using native id generation, in which
      // case we definitely need to nullify
      if object == self ) {
        return isEarlyInsert || (
          isDelete &&
          session.getFactory()
            .getDialect()
            .hasSelfReferentialForeignKeyBug()
        );
      }
  
      // See if the entity is already bound to this session, if not look at the
      // entity identifier and assume that the entity is persistent if the
      // id is not "unsaved" (that is, we rely on foreign keys to keep
      // database integrity)
  
      EntityEntry entityEntry = session.getPersistenceContext().getEntry(object);
      if entityEntry==null ) {
        return isTransient(entityName, object, null, session);
      }
      else {
        return entityEntry.isNullifiable(isEarlyInsert, session);
      }
  
    }
    
  }
  
  /**
   * Is this instance persistent or detached?
   * If <tt>assumed</tt> is non-null, don't hit the database to make the 
   * determination, instead assume that value; the client code must be 
   * prepared to "recover" in the case that this assumed result is incorrect.
   */
  public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session
  throws HibernateException {
    if (entity instanceof HibernateProxyreturn true;
    if session.getPersistenceContext().isEntryFor(entity) ) return true;
    return !isTransient(entityName, entity, assumed, session);
  }
  
  /**
   * Is this instance, which we know is not persistent, actually transient?
   * If <tt>assumed</tt> is non-null, don't hit the database to make the 
   * determination, instead assume that value; the client code must be 
   * prepared to "recover" in the case that this assumed result is incorrect.
   */
  public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session
  throws HibernateException {
    
    if (entity==LazyPropertyInitializer.UNFETCHED_PROPERTY) {
      // an unfetched association can only point to
      // an entity that already exists in the db
      return false;
    }
    
    // let the interceptor inspect the instance to decide
    Boolean isUnsaved = session.getInterceptor().isTransient(entity);
    if (isUnsaved!=nullreturn isUnsaved.booleanValue();
    
    // let the persister inspect the instance to decide
    EntityPersister persister = session.getEntityPersister(entityName, entity);
    isUnsaved = persister.isTransient(entity, session);
    if (isUnsaved!=nullreturn isUnsaved.booleanValue();

    // we use the assumed value, if there is one, to avoid hitting
    // the database
    if (assumed!=nullreturn assumed.booleanValue();
    
    // hit the database, after checking the session cache for a snapshot
    Object[] snapshot = session.getPersistenceContext()
            .getDatabaseSnapshotpersister.getIdentifierentity, session.getEntityMode() ), persister );
    return snapshot==null;

  }

  /**
   * Return the identifier of the persistent or transient object, or throw
   * an exception if the instance is "unsaved"
   *
   * Used by OneToOneType and ManyToOneType to determine what id value should 
   * be used for an object that may or may not be associated with the session. 
   * This does a "best guess" using any/all info available to use (not just the 
   * EntityEntry).
   */
  public static Serializable getEntityIdentifierIfNotUnsaved(
      final String entityName, 
      final Object object, 
      final SessionImplementor session
  throws HibernateException {
    if object == null ) {
      return null;
    }
    else {
      Serializable id = session.getContextEntityIdentifierobject );
      if id == null ) {
        // context-entity-identifier returns null explicitly if the entity
        // is not associated with the persistence context; so make some
        // deeper checks...
        if isTransient(entityName, object, Boolean.FALSE, session) ) {
          throw new TransientObjectException(
              "object references an unsaved transient instance - save the transient instance before flushing: " +
              (entityName == null ? session.guessEntityNameobject : entityName)
          );
        }
        id = session.getEntityPersisterentityName, object ).getIdentifierobject, session.getEntityMode() );
      }
      return id;
    }
  }

}