FileDocCategorySizeDatePackage
DefaultFlushEntityEventListener.javaAPI DocHibernate 3.2.516922Wed Jun 06 16:33:32 BST 2007org.hibernate.event.def

DefaultFlushEntityEventListener

public class DefaultFlushEntityEventListener extends Object implements org.hibernate.event.FlushEntityEventListener
An event that occurs for each entity instance at flush time
author
Gavin King

Fields Summary
private static final Log
log
Constructors Summary
Methods Summary
public voidcheckId(java.lang.Object object, org.hibernate.persister.entity.EntityPersister persister, java.io.Serializable id, org.hibernate.EntityMode entityMode)
make sure user didn't mangle the id


	       	 
	         
	  

		if ( id != null && id instanceof DelayedPostInsertIdentifier ) {
			// this is a situation where the entity id is assigned by a post-insert generator
			// and was saved outside the transaction forcing it to be delayed
			return;
		}

		if ( persister.canExtractIdOutOfEntity() ) {

			Serializable oid = persister.getIdentifier( object, entityMode );
			if (id==null) {
				throw new AssertionFailure("null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)");
			}
			if ( !persister.getIdentifierType().isEqual(id, oid, entityMode) ) {
				throw new HibernateException(
						"identifier of an instance of " +
						persister.getEntityName() +
						" was altered from " + id +
						" to " + oid
					);
			}
		}

	
private voidcheckNaturalId(org.hibernate.persister.entity.EntityPersister persister, java.io.Serializable identifier, java.lang.Object[] current, java.lang.Object[] loaded, org.hibernate.EntityMode entityMode, org.hibernate.engine.SessionImplementor session)

		if ( persister.hasNaturalIdentifier() ) {
 			Object[] snapshot = null;			
			Type[] types = persister.getPropertyTypes();
			int[] props = persister.getNaturalIdentifierProperties();
			boolean[] updateable = persister.getPropertyUpdateability();
			for ( int i=0; i<props.length; i++ ) {
				int prop = props[i];
				if ( !updateable[prop] ) {
 					Object loadedVal;
 					if ( loaded == null ) {
 						if ( snapshot == null) {
 							snapshot = session.getPersistenceContext().getNaturalIdSnapshot( identifier, persister );
 						}
 						loadedVal = snapshot[i];
 					} else {
 						loadedVal = loaded[prop];
 					}
 					if ( !types[prop].isEqual( current[prop], loadedVal, entityMode ) ) {						
						throw new HibernateException(
								"immutable natural identifier of an instance of " +
								persister.getEntityName() +
								" was altered"
							);
					}
				}
			}
		}
	
protected voiddirtyCheck(org.hibernate.event.FlushEntityEvent event)
Perform a dirty check, and attach the results to the event

		
		final Object entity = event.getEntity();
		final Object[] values = event.getPropertyValues();
		final SessionImplementor session = event.getSession();
		final EntityEntry entry = event.getEntityEntry();
		final EntityPersister persister = entry.getPersister();
		final Serializable id = entry.getId();
		final Object[] loadedState = entry.getLoadedState();

		int[] dirtyProperties = session.getInterceptor().findDirty( 
				entity, 
				id, 
				values, 
				loadedState, 
				persister.getPropertyNames(), 
				persister.getPropertyTypes() 
			);
		
		event.setDatabaseSnapshot(null);

		final boolean interceptorHandledDirtyCheck;
		boolean cannotDirtyCheck;
		
		if ( dirtyProperties==null ) {
			// Interceptor returned null, so do the dirtycheck ourself, if possible
			interceptorHandledDirtyCheck = false;
			
			cannotDirtyCheck = loadedState==null; // object loaded by update()
			if ( !cannotDirtyCheck ) {
				// dirty check against the usual snapshot of the entity
				dirtyProperties = persister.findDirty( values, loadedState, entity, session );
				
			}
			else {
				// dirty check against the database snapshot, if possible/necessary
				final Object[] databaseSnapshot = getDatabaseSnapshot(session, persister, id);
				if ( databaseSnapshot != null ) {
					dirtyProperties = persister.findModified(databaseSnapshot, values, entity, session);
					cannotDirtyCheck = false;
					event.setDatabaseSnapshot(databaseSnapshot);
				}
			}
		}
		else {
			// the Interceptor handled the dirty checking
			cannotDirtyCheck = false;
			interceptorHandledDirtyCheck = true;
		}
		
		event.setDirtyProperties(dirtyProperties);
		event.setDirtyCheckHandledByInterceptor(interceptorHandledDirtyCheck);
		event.setDirtyCheckPossible(!cannotDirtyCheck);
		
	
private java.lang.Object[]getDatabaseSnapshot(org.hibernate.engine.SessionImplementor session, org.hibernate.persister.entity.EntityPersister persister, java.io.Serializable id)

		if ( persister.isSelectBeforeUpdateRequired() ) {
			Object[] snapshot = session.getPersistenceContext()
					.getDatabaseSnapshot(id, persister);
			if (snapshot==null) {
				//do we even really need this? the update will fail anyway....
				if ( session.getFactory().getStatistics().isStatisticsEnabled() ) {
					session.getFactory().getStatisticsImplementor()
							.optimisticFailure( persister.getEntityName() );
				}
				throw new StaleObjectStateException( persister.getEntityName(), id );
			}
			else {
				return snapshot;
			}
		}
		else {
			//TODO: optimize away this lookup for entities w/o unsaved-value="undefined"
			EntityKey entityKey = new EntityKey( id, persister, session.getEntityMode() );
			return session.getPersistenceContext()
					.getCachedDatabaseSnapshot( entityKey ); 
		}
	
private java.lang.ObjectgetNextVersion(org.hibernate.event.FlushEntityEvent event)
Convience method to retreive an entities next version value

		
		EntityEntry entry = event.getEntityEntry();
		EntityPersister persister = entry.getPersister();
		if ( persister.isVersioned() ) {

			Object[] values = event.getPropertyValues();
		    
			if ( entry.isBeingReplicated() ) {
				return Versioning.getVersion(values, persister);
			}
			else {
				int[] dirtyProperties = event.getDirtyProperties();
				
				final boolean isVersionIncrementRequired = isVersionIncrementRequired( 
						event, 
						entry, 
						persister, 
						dirtyProperties 
					);
				
				final Object nextVersion = isVersionIncrementRequired ?
						Versioning.increment( entry.getVersion(), persister.getVersionType(), event.getSession() ) :
						entry.getVersion(); //use the current version
						
				Versioning.setVersion(values, nextVersion, persister);
				
				return nextVersion;
			}
		}
		else {
			return null;
		}
		
	
private java.lang.Object[]getValues(java.lang.Object entity, org.hibernate.engine.EntityEntry entry, org.hibernate.EntityMode entityMode, boolean mightBeDirty, org.hibernate.engine.SessionImplementor session)

		final Object[] loadedState = entry.getLoadedState();
		final Status status = entry.getStatus();
		final EntityPersister persister = entry.getPersister();

		final Object[] values;
		if ( status == Status.DELETED ) {
			//grab its state saved at deletion
			values = entry.getDeletedState();
		}
		else if ( !mightBeDirty && loadedState!=null ) {
			values = loadedState;
		}
		else {
			checkId( entity, persister, entry.getId(), entityMode );

			// grab its current state
			values = persister.getPropertyValues( entity, entityMode );

			checkNaturalId( persister, entry.getId(), values, loadedState, entityMode, session );
		}
		return values;
	
protected booleanhandleInterception(org.hibernate.event.FlushEntityEvent event)

		SessionImplementor session = event.getSession();
		EntityEntry entry = event.getEntityEntry();
		EntityPersister persister = entry.getPersister();
		Object entity = event.getEntity();
		
		//give the Interceptor a chance to modify property values
		final Object[] values = event.getPropertyValues();
		final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister );

		//now we might need to recalculate the dirtyProperties array
		if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) {
			int[] dirtyProperties;
			if ( event.hasDatabaseSnapshot() ) {
				dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session );
			}
			else {
				dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session );
			}
			event.setDirtyProperties(dirtyProperties);
		}
		
		return intercepted;
	
private booleanhasDirtyCollections(org.hibernate.event.FlushEntityEvent event, org.hibernate.persister.entity.EntityPersister persister, org.hibernate.engine.Status status)

		if ( isCollectionDirtyCheckNecessary(persister, status) ) {
			DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor( 
					event.getSession(),
					persister.getPropertyVersionability()
				);
			visitor.processEntityPropertyValues( event.getPropertyValues(), persister.getPropertyTypes() );
			boolean hasDirtyCollections = visitor.wasDirtyCollectionFound();
			event.setHasDirtyCollection(hasDirtyCollections);
			return hasDirtyCollections;
		}
		else {
			return false;
		}
	
protected booleaninvokeInterceptor(org.hibernate.engine.SessionImplementor session, java.lang.Object entity, org.hibernate.engine.EntityEntry entry, java.lang.Object[] values, org.hibernate.persister.entity.EntityPersister persister)

		return session.getInterceptor().onFlushDirty(
				entity,
				entry.getId(),
				values,
				entry.getLoadedState(),
				persister.getPropertyNames(),
				persister.getPropertyTypes()
		);
	
private booleanisCollectionDirtyCheckNecessary(org.hibernate.persister.entity.EntityPersister persister, org.hibernate.engine.Status status)

		return status==Status.MANAGED && 
				persister.isVersioned() && 
				persister.hasCollections();
	
protected final booleanisUpdateNecessary(org.hibernate.event.FlushEntityEvent event)
Performs all necessary checking to determine if an entity needs an SQL update to synchronize its state to the database. Modifies the event by side-effect! Note: this method is quite slow, avoid calling if possible!


		EntityPersister persister = event.getEntityEntry().getPersister();
		Status status = event.getEntityEntry().getStatus();
		
		if ( !event.isDirtyCheckPossible() ) {
			return true;
		}
		else {
			
			int[] dirtyProperties = event.getDirtyProperties();
			if ( dirtyProperties!=null && dirtyProperties.length!=0 ) {
				return true; //TODO: suck into event class
			}
			else {
				return hasDirtyCollections( event, persister, status );
			}
			
		}
	
private booleanisUpdateNecessary(org.hibernate.event.FlushEntityEvent event, boolean mightBeDirty)

		final Status status = event.getEntityEntry().getStatus();
		if ( mightBeDirty || status==Status.DELETED ) {
			// compare to cached state (ignoring collections unless versioned)
			dirtyCheck(event);
			if ( isUpdateNecessary(event) ) {
				return true;
			}
			else {
				FieldInterceptionHelper.clearDirty( event.getEntity() );
				return false;
			}
		}
		else {
			return hasDirtyCollections( event, event.getEntityEntry().getPersister(), status );
		}
	
private booleanisVersionIncrementRequired(org.hibernate.event.FlushEntityEvent event, org.hibernate.engine.EntityEntry entry, org.hibernate.persister.entity.EntityPersister persister, int[] dirtyProperties)

		final boolean isVersionIncrementRequired = entry.getStatus()!=Status.DELETED && ( 
				dirtyProperties==null || 
				Versioning.isVersionIncrementRequired( 
						dirtyProperties, 
						event.hasDirtyCollection(),
						persister.getPropertyVersionability()
					) 
			);
		return isVersionIncrementRequired;
	
public voidonFlushEntity(org.hibernate.event.FlushEntityEvent event)
Flushes a single entity's state to the database, by scheduling an update action, if necessary

		final Object entity = event.getEntity();
		final EntityEntry entry = event.getEntityEntry();
		final EventSource session = event.getSession();
		final EntityPersister persister = entry.getPersister();
		final Status status = entry.getStatus();
		final EntityMode entityMode = session.getEntityMode();
		final Type[] types = persister.getPropertyTypes();

		final boolean mightBeDirty = entry.requiresDirtyCheck(entity);

		final Object[] values = getValues( entity, entry, entityMode, mightBeDirty, session );

		event.setPropertyValues(values);

		//TODO: avoid this for non-new instances where mightBeDirty==false
		boolean substitute = wrapCollections( session, persister, types, values);

		if ( isUpdateNecessary( event, mightBeDirty ) ) {
			substitute = scheduleUpdate( event ) || substitute;
		}

		if ( status != Status.DELETED ) {
			// now update the object .. has to be outside the main if block above (because of collections)
			if (substitute) persister.setPropertyValues( entity, values, entityMode );

			// Search for collections by reachability, updating their role.
			// We don't want to touch collections reachable from a deleted object
			if ( persister.hasCollections() ) {
				new FlushVisitor(session, entity).processEntityPropertyValues(values, types);
			}
		}

	
private booleanscheduleUpdate(org.hibernate.event.FlushEntityEvent event)

		
		final EntityEntry entry = event.getEntityEntry();
		final EventSource session = event.getSession();
		final Object entity = event.getEntity();
		final Status status = entry.getStatus();
		final EntityMode entityMode = session.getEntityMode();
		final EntityPersister persister = entry.getPersister();
		final Object[] values = event.getPropertyValues();
		
		if ( log.isTraceEnabled() ) {
			if ( status == Status.DELETED ) {
				log.trace(
						"Updating deleted entity: " +
						MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
					);
			}
			else {
				log.trace(
						"Updating entity: " +
						MessageHelper.infoString( persister, entry.getId(), session.getFactory()  )
					);
			}
		}

		boolean intercepted;
		
		if ( !entry.isBeingReplicated() ) {
			// give the Interceptor a chance to process property values, if the properties 
			// were modified by the Interceptor, we need to set them back to the object
			intercepted = handleInterception( event );
		}
		else {
			intercepted = false;
		}

		validate( entity, persister, status, entityMode );

		// increment the version number (if necessary)
		final Object nextVersion = getNextVersion(event);

		// if it was dirtied by a collection only
		int[] dirtyProperties = event.getDirtyProperties();
		if ( event.isDirtyCheckPossible() && dirtyProperties == null ) {
			if ( ! intercepted && !event.hasDirtyCollection() ) {
				throw new AssertionFailure( "dirty, but no dirty properties" );
			}
			dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;
		}

		// check nullability but do not perform command execute
		// we'll use scheduled updates for that.
		new Nullability(session).checkNullability( values, persister, true );

		// schedule the update
		// note that we intentionally do _not_ pass in currentPersistentState!
		session.getActionQueue().addAction(
				new EntityUpdateAction(
						entry.getId(),
						values,
						dirtyProperties,
						event.hasDirtyCollection(),
						entry.getLoadedState(),
						entry.getVersion(),
						nextVersion,
						entity,
						entry.getRowId(),
						persister,
						session
					)
			);
		
		return intercepted;
	
protected voidvalidate(java.lang.Object entity, org.hibernate.persister.entity.EntityPersister persister, org.hibernate.engine.Status status, org.hibernate.EntityMode entityMode)

		// validate() instances of Validatable
		if ( status == Status.MANAGED && persister.implementsValidatable( entityMode ) ) {
			( (Validatable) entity ).validate();
		}
	
private booleanwrapCollections(org.hibernate.event.EventSource session, org.hibernate.persister.entity.EntityPersister persister, org.hibernate.type.Type[] types, java.lang.Object[] values)

		if ( persister.hasCollections() ) {

			// wrap up any new collections directly referenced by the object
			// or its components

			// NOTE: we need to do the wrap here even if its not "dirty",
			// because collections need wrapping but changes to _them_
			// don't dirty the container. Also, for versioned data, we
			// need to wrap before calling searchForDirtyCollections

			WrapVisitor visitor = new WrapVisitor(session);
			// substitutes into values by side-effect
			visitor.processEntityPropertyValues(values, types);
			return visitor.isSubstitutionRequired();
		}
		else {
			return false;
		}