Open Source Repository

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


org/hibernate/event/def/DefaultSaveOrUpdateEventListener.java
// $Id: DefaultSaveOrUpdateEventListener.java 10949 2006-12-07 21:53:41Z [email protected] $
package org.hibernate.event.def;

import java.io.Serializable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.AssertionFailure;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.PersistentObjectException;
import org.hibernate.TransientObjectException;
import org.hibernate.classic.Lifecycle;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.Status;
import org.hibernate.event.EventSource;
import org.hibernate.event.SaveOrUpdateEvent;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;

/**
 * Defines the default listener used by Hibernate for handling save-update
 * events.
 *
 @author Steve Ebersole
 @author Gavin King
 */
public class DefaultSaveOrUpdateEventListener extends AbstractSaveEventListener implements SaveOrUpdateEventListener {

  private static final Log log = LogFactory.getLogDefaultSaveOrUpdateEventListener.class );

  /**
   * Handle the given update event.
   *
   @param event The update event to be handled.
   */
  public void onSaveOrUpdate(SaveOrUpdateEvent event) {
    final SessionImplementor source = event.getSession();
    final Object object = event.getObject();
    final Serializable requestedId = event.getRequestedId();

    if requestedId != null ) {
      //assign the requested id to the proxy, *before* 
      //reassociating the proxy
      if object instanceof HibernateProxy ) {
        ( ( HibernateProxy object ).getHibernateLazyInitializer().setIdentifierrequestedId );
      }
    }

    if reassociateIfUninitializedProxyobject, source ) ) {
      log.trace"reassociated uninitialized proxy" );
      // an uninitialized proxy, noop, don't even need to 
      // return an id, since it is never a save()
    }
    else {
      //initialize properties of the event:
      final Object entity = source.getPersistenceContext().unproxyAndReassociateobject );
      event.setEntityentity );
      event.setEntrysource.getPersistenceContext().getEntryentity ) );
      //return the id in the event object
      event.setResultIdperformSaveOrUpdateevent ) );
    }

  }

  protected boolean reassociateIfUninitializedProxy(Object object, SessionImplementor source) {
    return source.getPersistenceContext().reassociateIfUninitializedProxyobject );
  }

  protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
    int entityState = getEntityState(
        event.getEntity(),
        event.getEntityName(),
        event.getEntry(),
        event.getSession()
    );

    switch entityState ) {
      case DETACHED:
        entityIsDetachedevent );
        return null;
      case PERSISTENT:
        return entityIsPersistentevent );
      default//TRANSIENT or DELETED
        return entityIsTransientevent );
    }
  }

  protected Serializable entityIsPersistent(SaveOrUpdateEvent eventthrows HibernateException {
    log.trace"ignoring persistent instance" );

    EntityEntry entityEntry = event.getEntry();
    if entityEntry == null ) {
      throw new AssertionFailure"entity was transient or detached" );
    }
    else {

      if entityEntry.getStatus() == Status.DELETED ) {
        throw new AssertionFailure"entity was deleted" );
      }

      final SessionFactoryImplementor factory = event.getSession().getFactory();

      Serializable requestedId = event.getRequestedId();

      Serializable savedId;
      if requestedId == null ) {
        savedId = entityEntry.getId();
      }
      else {

        final boolean isEqual = !entityEntry.getPersister().getIdentifierType()
            .isEqualrequestedId, entityEntry.getId(), event.getSession().getEntityMode(), factory );

        if isEqual ) {
          throw new PersistentObjectException(
              "object passed to save() was already persistent: " +
                  MessageHelper.infoStringentityEntry.getPersister(), requestedId, factory )
          );
        }

        savedId = requestedId;

      }

      if log.isTraceEnabled() ) {
        log.trace(
            "object already associated with session: " +
                MessageHelper.infoStringentityEntry.getPersister(), savedId, factory )
        );
      }

      return savedId;

    }
  }

  /**
   * The given save-update event named a transient entity.
   <p/>
   * Here, we will perform the save processing.
   *
   @param event The save event to be handled.
   *
   @return The entity's identifier after saving.
   */
  protected Serializable entityIsTransient(SaveOrUpdateEvent event) {

    log.trace"saving transient instance" );

    final EventSource source = event.getSession();

    EntityEntry entityEntry = event.getEntry();
    if entityEntry != null ) {
      if entityEntry.getStatus() == Status.DELETED ) {
        source.forceFlushentityEntry );
      }
      else {
        throw new AssertionFailure"entity was persistent" );
      }
    }

    Serializable id = saveWithGeneratedOrRequestedIdevent );

    source.getPersistenceContext().reassociateProxyevent.getObject(), id );

    return id;
  }

  /**
   * Save the transient instance, assigning the right identifier
   *
   @param event The initiating event.
   *
   @return The entity's identifier value after saving.
   */
  protected Serializable saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) {
    return saveWithGeneratedId(
        event.getEntity(),
        event.getEntityName(),
        null,
        event.getSession(),
        true
    );
  }

  /**
   * The given save-update event named a detached entity.
   <p/>
   * Here, we will perform the update processing.
   *
   @param event The update event to be handled.
   */
  protected void entityIsDetached(SaveOrUpdateEvent event) {

    log.trace"updating detached instance" );


    if event.getSession().getPersistenceContext().isEntryForevent.getEntity() ) ) {
      //TODO: assertion only, could be optimized away
      throw new AssertionFailure"entity was persistent" );
    }

    Object entity = event.getEntity();

    EntityPersister persister = event.getSession().getEntityPersisterevent.getEntityName(), entity );

    event.setRequestedId(
        getUpdateId(
            entity, persister, event.getRequestedId(), event.getSession().getEntityMode()
        )
    );

    performUpdateevent, entity, persister );

  }

  /**
   * Determine the id to use for updating.
   *
   @param entity The entity.
   @param persister The entity persister
   @param requestedId The requested identifier
   @param entityMode The entity mode.
   *
   @return The id.
   *
   @throws TransientObjectException If the entity is considered transient.
   */
  protected Serializable getUpdateId(
      Object entity,
      EntityPersister persister,
      Serializable requestedId,
      EntityMode entityMode) {
    // use the id assigned to the instance
    Serializable id = persister.getIdentifierentity, entityMode );
    if id == null ) {
      // assume this is a newly instantiated transient object
      // which should be saved rather than updated
      throw new TransientObjectException(
          "The given object has a null identifier: " +
              persister.getEntityName()
      );
    }
    else {
      return id;
    }

  }

  protected void performUpdate(
      SaveOrUpdateEvent event,
      Object entity,
      EntityPersister persisterthrows HibernateException {

    if !persister.isMutable() ) {
      log.trace"immutable instance passed to doUpdate(), locking" );
      reassociateevent, entity, event.getRequestedId(), persister );
    }
    else {

      if log.isTraceEnabled() ) {
        log.trace(
            "updating " +
                MessageHelper.infoString(
                    persister, event.getRequestedId(), event.getSession().getFactory()
                )
        );
      }

      final EventSource source = event.getSession();

      EntityKey key = new EntityKeyevent.getRequestedId(), persister, source.getEntityMode() );

      source.getPersistenceContext().checkUniquenesskey, entity );

      if invokeUpdateLifecycleentity, persister, source ) ) {
        reassociateevent, event.getObject(), event.getRequestedId(), persister );
        return;
      }

      // this is a transient object with existing persistent state not loaded by the session

      new OnUpdateVisitorsource, event.getRequestedId(), entity ).processentity, persister );

      //TODO: put this stuff back in to read snapshot from
      //      the second-level cache (needs some extra work)
      /*Object[] cachedState = null;

      if ( persister.hasCache() ) {
        CacheEntry entry = (CacheEntry) persister.getCache()
            .get( event.getRequestedId(), source.getTimestamp() );
          cachedState = entry==null ? 
              null : 
              entry.getState(); //TODO: half-assemble this stuff
      }*/

      source.getPersistenceContext().addEntity(
          entity,
          Status.MANAGED,
          null, //cachedState,
          key,
          persister.getVersionentity, source.getEntityMode() ),
          LockMode.NONE,
          true,
          persister,
          false,
          true //assume true, since we don't really know, and it doesn't matter
      );

      persister.afterReassociateentity, source );

      if log.isTraceEnabled() ) {
        log.trace(
            "updating " +
                MessageHelper.infoStringpersister, event.getRequestedId(), source.getFactory() )
        );
      }

      cascadeOnUpdateevent, persister, entity );

    }
  }

  protected boolean invokeUpdateLifecycle(Object entity, EntityPersister persister, EventSource source) {
    if persister.implementsLifecyclesource.getEntityMode() ) ) {
      log.debug"calling onUpdate()" );
      if ( ( ( Lifecycle entity ).onUpdatesource ) ) {
        log.debug"update vetoed by onUpdate()" );
        return true;
      }
    }
    return false;
  }

  /**
   * Handles the calls needed to perform cascades as part of an update request
   * for the given entity.
   *
   @param event The event currently being processed.
   @param persister The defined persister for the entity being updated.
   @param entity The entity being updated.
   */
  private void cascadeOnUpdate(SaveOrUpdateEvent event, EntityPersister persister, Object entity) {
    EventSource source = event.getSession();
    source.getPersistenceContext().incrementCascadeLevel();
    try {
      new CascadeCascadingAction.SAVE_UPDATE, Cascade.AFTER_UPDATE, source )
          .cascadepersister, entity );
    }
    finally {
      source.getPersistenceContext().decrementCascadeLevel();
    }
  }

  protected CascadingAction getCascadeAction() {
    return CascadingAction.SAVE_UPDATE;
  }
}