// $Id: DefaultSaveOrUpdateEventListener.java 9673 2006-03-22 14:57:59Z steve.ebersole@jboss.com $
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 update event listener used by hibernate for updating
* transient entities in response to generated update events.
*
* @author Steve Ebersole, Gavin King
*/
public class DefaultSaveOrUpdateEventListener extends AbstractSaveEventListener implements SaveOrUpdateEventListener {
private static final Log log = LogFactory.getLog(DefaultSaveOrUpdateEventListener.class);
/**
* Handle the given update event.
*
* @param event The update event to be handled.
* @throws HibernateException
*/
public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException {
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().setIdentifier(requestedId);
}
}
if ( reassociateIfUninitializedProxy(object, 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().unproxyAndReassociate(object);
event.setEntity(entity);
event.setEntry( source.getPersistenceContext().getEntry(entity) );
//return the id in the event object
event.setResultId( performSaveOrUpdate(event) );
}
}
protected boolean reassociateIfUninitializedProxy(Object object, SessionImplementor source) {
return source.getPersistenceContext().reassociateIfUninitializedProxy(object);
}
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
// use various roles to determine if the instance is
// transient, persistent or detached:
int entityState = getEntityState(
event.getEntity(),
event.getEntityName(),
event.getEntry(),
event.getSession()
);
switch (entityState) {
case DETACHED:
entityIsDetached(event);
return null;
case PERSISTENT:
return entityIsPersistent(event);
default: //TRANSIENT or DELETED
return entityIsTransient(event);
}
}
protected Serializable entityIsPersistent(SaveOrUpdateEvent event) throws 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()
.isEqual( requestedId, entityEntry.getId(), event.getSession().getEntityMode(), factory );
if ( isEqual ) {
throw new PersistentObjectException(
"object passed to save() was already persistent: " +
MessageHelper.infoString( entityEntry.getPersister(), requestedId, factory )
);
}
savedId = requestedId;
}
if ( log.isTraceEnabled() ) {
log.trace(
"object already associated with session: " +
MessageHelper.infoString( entityEntry.getPersister(), savedId, factory )
);
}
return savedId;
}
}
/**
* Handle the given save event.
*
* @param event The save event to be handled.
* @throws HibernateException
*/
protected Serializable entityIsTransient(SaveOrUpdateEvent event) throws HibernateException {
log.trace("saving transient instance");
final EventSource source = event.getSession();
EntityEntry entityEntry = event.getEntry();
if ( entityEntry != null ) {
if ( entityEntry.getStatus() == Status.DELETED ) {
source.forceFlush(entityEntry);
}
else {
throw new AssertionFailure("entity was persistent");
}
}
Serializable id = saveWithGeneratedOrRequestedId(event);
source.getPersistenceContext().reassociateProxy( event.getObject(), id );
return id;
}
/**
* Save the transient instance, assigning the right identifier
*/
protected Serializable saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) {
return saveWithGeneratedId(
event.getEntity(),
event.getEntityName(),
null,
event.getSession(),
true
);
}
/**
* Handle the given update event.
*
* @param event The update event to be handled.
* @throws HibernateException
*/
protected void entityIsDetached(SaveOrUpdateEvent event) throws HibernateException {
log.trace("updating detached instance");
if ( event.getSession().getPersistenceContext().isEntryFor( event.getEntity() ) ) {
//TODO: assertion only, could be optimized away
throw new AssertionFailure("entity was persistent");
}
Object entity = event.getEntity();
EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), entity );
event.setRequestedId( getUpdateId( entity, persister, event.getRequestedId(), event.getSession().getEntityMode() ) );
performUpdate(event, entity, persister);
}
protected Serializable getUpdateId(Object entity, EntityPersister persister, Serializable requestedId, EntityMode entityMode)
throws HibernateException {
// use the id assigned to the instance
Serializable id = persister.getIdentifier(entity, 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 persister)
throws HibernateException {
if ( !persister.isMutable() ) {
log.trace( "immutable instance passed to doUpdate(), locking" );
reassociate( event, 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 EntityKey( event.getRequestedId(), persister, source.getEntityMode() );
source.getPersistenceContext().checkUniqueness( key, entity );
if ( invokeUpdateLifecycle( entity, persister, source ) ) {
reassociate( event, event.getObject(), event.getRequestedId(), persister );
return;
}
// this is a transient object with existing persistent state not loaded by the session
new OnUpdateVisitor( source, event.getRequestedId() ).process( entity, 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.getVersion( entity, source.getEntityMode() ),
LockMode.NONE,
true,
persister,
false,
true //assume true, since we don't really know, and it doesn't matter
);
persister.afterReassociate(entity, source);
if ( log.isTraceEnabled() ) {
log.trace(
"updating " +
MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() )
);
}
cascadeOnUpdate(event, persister, entity);
}
}
protected boolean invokeUpdateLifecycle(Object entity, EntityPersister persister, EventSource source) {
if ( persister.implementsLifecycle( source.getEntityMode() ) ) {
log.debug( "calling onUpdate()" );
if ( ( ( Lifecycle ) entity ).onUpdate(source) ) {
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 Cascade(CascadingAction.SAVE_UPDATE, Cascade.AFTER_UPDATE, source)
.cascade(persister, entity);
}
finally {
source.getPersistenceContext().decrementCascadeLevel();
}
}
protected CascadingAction getCascadeAction() {
return CascadingAction.SAVE_UPDATE;
}
}