Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » event » def » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.event.def;
   26   
   27   import java.io.Serializable;
   28   
   29   import org.slf4j.Logger;
   30   import org.slf4j.LoggerFactory;
   31   import org.hibernate.AssertionFailure;
   32   import org.hibernate.EntityMode;
   33   import org.hibernate.HibernateException;
   34   import org.hibernate.StaleObjectStateException;
   35   import org.hibernate.action.EntityUpdateAction;
   36   import org.hibernate.action.DelayedPostInsertIdentifier;
   37   import org.hibernate.classic.Validatable;
   38   import org.hibernate.engine.EntityEntry;
   39   import org.hibernate.engine.EntityKey;
   40   import org.hibernate.engine.Nullability;
   41   import org.hibernate.engine.SessionImplementor;
   42   import org.hibernate.engine.Status;
   43   import org.hibernate.engine.Versioning;
   44   import org.hibernate.event.EventSource;
   45   import org.hibernate.event.FlushEntityEvent;
   46   import org.hibernate.event.FlushEntityEventListener;
   47   import org.hibernate.intercept.FieldInterceptionHelper;
   48   import org.hibernate.persister.entity.EntityPersister;
   49   import org.hibernate.pretty.MessageHelper;
   50   import org.hibernate.type.Type;
   51   import org.hibernate.util.ArrayHelper;
   52   
   53   /**
   54    * An event that occurs for each entity instance at flush time
   55    *
   56    * @author Gavin King
   57    */
   58   public class DefaultFlushEntityEventListener implements FlushEntityEventListener {
   59   
   60   	private static final Logger log = LoggerFactory.getLogger(DefaultFlushEntityEventListener.class);
   61   
   62   	/**
   63   	 * make sure user didn't mangle the id
   64   	 */
   65   	public void checkId(Object object, EntityPersister persister, Serializable id, EntityMode entityMode)
   66   	throws HibernateException {
   67   
   68   		if ( id != null && id instanceof DelayedPostInsertIdentifier ) {
   69   			// this is a situation where the entity id is assigned by a post-insert generator
   70   			// and was saved outside the transaction forcing it to be delayed
   71   			return;
   72   		}
   73   
   74   		if ( persister.canExtractIdOutOfEntity() ) {
   75   
   76   			Serializable oid = persister.getIdentifier( object, entityMode );
   77   			if (id==null) {
   78   				throw new AssertionFailure("null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)");
   79   			}
   80   			if ( !persister.getIdentifierType().isEqual(id, oid, entityMode) ) {
   81   				throw new HibernateException(
   82   						"identifier of an instance of " +
   83   						persister.getEntityName() +
   84   						" was altered from " + id +
   85   						" to " + oid
   86   					);
   87   			}
   88   		}
   89   
   90   	}
   91   
   92   	private void checkNaturalId(
   93   			EntityPersister persister,
   94   	        EntityEntry entry,
   95   	        Object[] current,
   96   	        Object[] loaded,
   97   	        EntityMode entityMode,
   98   	        SessionImplementor session) {
   99   		if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) {
  100    			Object[] snapshot = null;			
  101   			Type[] types = persister.getPropertyTypes();
  102   			int[] props = persister.getNaturalIdentifierProperties();
  103   			boolean[] updateable = persister.getPropertyUpdateability();
  104   			for ( int i=0; i<props.length; i++ ) {
  105   				int prop = props[i];
  106   				if ( !updateable[prop] ) {
  107    					Object loadedVal;
  108    					if ( loaded == null ) {
  109    						if ( snapshot == null) {
  110    							snapshot = session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister );
  111    						}
  112    						loadedVal = snapshot[i];
  113    					} else {
  114    						loadedVal = loaded[prop];
  115    					}
  116    					if ( !types[prop].isEqual( current[prop], loadedVal, entityMode ) ) {						
  117   						throw new HibernateException(
  118   								"immutable natural identifier of an instance of " +
  119   								persister.getEntityName() +
  120   								" was altered"
  121   							);
  122   					}
  123   				}
  124   			}
  125   		}
  126   	}
  127   
  128   	/**
  129   	 * Flushes a single entity's state to the database, by scheduling
  130   	 * an update action, if necessary
  131   	 */
  132   	public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
  133   		final Object entity = event.getEntity();
  134   		final EntityEntry entry = event.getEntityEntry();
  135   		final EventSource session = event.getSession();
  136   		final EntityPersister persister = entry.getPersister();
  137   		final Status status = entry.getStatus();
  138   		final EntityMode entityMode = session.getEntityMode();
  139   		final Type[] types = persister.getPropertyTypes();
  140   
  141   		final boolean mightBeDirty = entry.requiresDirtyCheck(entity);
  142   
  143   		final Object[] values = getValues( entity, entry, entityMode, mightBeDirty, session );
  144   
  145   		event.setPropertyValues(values);
  146   
  147   		//TODO: avoid this for non-new instances where mightBeDirty==false
  148   		boolean substitute = wrapCollections( session, persister, types, values);
  149   
  150   		if ( isUpdateNecessary( event, mightBeDirty ) ) {
  151   			substitute = scheduleUpdate( event ) || substitute;
  152   		}
  153   
  154   		if ( status != Status.DELETED ) {
  155   			// now update the object .. has to be outside the main if block above (because of collections)
  156   			if (substitute) persister.setPropertyValues( entity, values, entityMode );
  157   
  158   			// Search for collections by reachability, updating their role.
  159   			// We don't want to touch collections reachable from a deleted object
  160   			if ( persister.hasCollections() ) {
  161   				new FlushVisitor(session, entity).processEntityPropertyValues(values, types);
  162   			}
  163   		}
  164   
  165   	}
  166   
  167   	private Object[] getValues(
  168   			Object entity,
  169   			EntityEntry entry,
  170   			EntityMode entityMode,
  171   			boolean mightBeDirty,
  172   	        SessionImplementor session
  173   	) {
  174   		final Object[] loadedState = entry.getLoadedState();
  175   		final Status status = entry.getStatus();
  176   		final EntityPersister persister = entry.getPersister();
  177   
  178   		final Object[] values;
  179   		if ( status == Status.DELETED ) {
  180   			//grab its state saved at deletion
  181   			values = entry.getDeletedState();
  182   		}
  183   		else if ( !mightBeDirty && loadedState!=null ) {
  184   			values = loadedState;
  185   		}
  186   		else {
  187   			checkId( entity, persister, entry.getId(), entityMode );
  188   
  189   			// grab its current state
  190   			values = persister.getPropertyValues( entity, entityMode );
  191   
  192   			checkNaturalId( persister, entry, values, loadedState, entityMode, session );
  193   		}
  194   		return values;
  195   	}
  196   
  197   	private boolean wrapCollections(
  198   			EventSource session,
  199   			EntityPersister persister,
  200   			Type[] types,
  201   			Object[] values
  202   	) {
  203   		if ( persister.hasCollections() ) {
  204   
  205   			// wrap up any new collections directly referenced by the object
  206   			// or its components
  207   
  208   			// NOTE: we need to do the wrap here even if its not "dirty",
  209   			// because collections need wrapping but changes to _them_
  210   			// don't dirty the container. Also, for versioned data, we
  211   			// need to wrap before calling searchForDirtyCollections
  212   
  213   			WrapVisitor visitor = new WrapVisitor(session);
  214   			// substitutes into values by side-effect
  215   			visitor.processEntityPropertyValues(values, types);
  216   			return visitor.isSubstitutionRequired();
  217   		}
  218   		else {
  219   			return false;
  220   		}
  221   	}
  222   
  223   	private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mightBeDirty) {
  224   		final Status status = event.getEntityEntry().getStatus();
  225   		if ( mightBeDirty || status==Status.DELETED ) {
  226   			// compare to cached state (ignoring collections unless versioned)
  227   			dirtyCheck(event);
  228   			if ( isUpdateNecessary(event) ) {
  229   				return true;
  230   			}
  231   			else {
  232   				FieldInterceptionHelper.clearDirty( event.getEntity() );
  233   				return false;
  234   			}
  235   		}
  236   		else {
  237   			return hasDirtyCollections( event, event.getEntityEntry().getPersister(), status );
  238   		}
  239   	}
  240   
  241   	private boolean scheduleUpdate(final FlushEntityEvent event) {
  242   		
  243   		final EntityEntry entry = event.getEntityEntry();
  244   		final EventSource session = event.getSession();
  245   		final Object entity = event.getEntity();
  246   		final Status status = entry.getStatus();
  247   		final EntityMode entityMode = session.getEntityMode();
  248   		final EntityPersister persister = entry.getPersister();
  249   		final Object[] values = event.getPropertyValues();
  250   		
  251   		if ( log.isTraceEnabled() ) {
  252   			if ( status == Status.DELETED ) {
  253   				log.trace(
  254   						"Updating deleted entity: " +
  255   						MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
  256   					);
  257   			}
  258   			else {
  259   				log.trace(
  260   						"Updating entity: " +
  261   						MessageHelper.infoString( persister, entry.getId(), session.getFactory()  )
  262   					);
  263   			}
  264   		}
  265   
  266   		final boolean intercepted;
  267   		if ( !entry.isBeingReplicated() ) {
  268   			// give the Interceptor a chance to process property values, if the properties
  269   			// were modified by the Interceptor, we need to set them back to the object
  270   			intercepted = handleInterception( event );
  271   		}
  272   		else {
  273   			intercepted = false;
  274   		}
  275   
  276   		validate( entity, persister, status, entityMode );
  277   
  278   		// increment the version number (if necessary)
  279   		final Object nextVersion = getNextVersion(event);
  280   
  281   		// if it was dirtied by a collection only
  282   		int[] dirtyProperties = event.getDirtyProperties();
  283   		if ( event.isDirtyCheckPossible() && dirtyProperties == null ) {
  284   			if ( ! intercepted && !event.hasDirtyCollection() ) {
  285   				throw new AssertionFailure( "dirty, but no dirty properties" );
  286   			}
  287   			dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;
  288   		}
  289   
  290   		// check nullability but do not perform command execute
  291   		// we'll use scheduled updates for that.
  292   		new Nullability(session).checkNullability( values, persister, true );
  293   
  294   		// schedule the update
  295   		// note that we intentionally do _not_ pass in currentPersistentState!
  296   		session.getActionQueue().addAction(
  297   				new EntityUpdateAction(
  298   						entry.getId(),
  299   						values,
  300   						dirtyProperties,
  301   						event.hasDirtyCollection(),
  302   						entry.getLoadedState(),
  303   						entry.getVersion(),
  304   						nextVersion,
  305   						entity,
  306   						entry.getRowId(),
  307   						persister,
  308   						session
  309   					)
  310   			);
  311   		
  312   		return intercepted;
  313   	}
  314   
  315   	protected void validate(Object entity, EntityPersister persister, Status status, EntityMode entityMode) {
  316   		// validate() instances of Validatable
  317   		if ( status == Status.MANAGED && persister.implementsValidatable( entityMode ) ) {
  318   			( (Validatable) entity ).validate();
  319   		}
  320   	}
  321   	
  322   	protected boolean handleInterception(FlushEntityEvent event) {
  323   		SessionImplementor session = event.getSession();
  324   		EntityEntry entry = event.getEntityEntry();
  325   		EntityPersister persister = entry.getPersister();
  326   		Object entity = event.getEntity();
  327   		
  328   		//give the Interceptor a chance to modify property values
  329   		final Object[] values = event.getPropertyValues();
  330   		final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister );
  331   
  332   		//now we might need to recalculate the dirtyProperties array
  333   		if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) {
  334   			int[] dirtyProperties;
  335   			if ( event.hasDatabaseSnapshot() ) {
  336   				dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session );
  337   			}
  338   			else {
  339   				dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session );
  340   			}
  341   			event.setDirtyProperties(dirtyProperties);
  342   		}
  343   		
  344   		return intercepted;
  345   	}
  346   
  347   	protected boolean invokeInterceptor(
  348   			SessionImplementor session,
  349   			Object entity,
  350   			EntityEntry entry,
  351   			final Object[] values,
  352   			EntityPersister persister) {
  353   		return session.getInterceptor().onFlushDirty(
  354   				entity,
  355   				entry.getId(),
  356   				values,
  357   				entry.getLoadedState(),
  358   				persister.getPropertyNames(),
  359   				persister.getPropertyTypes()
  360   		);
  361   	}
  362   
  363   	/**
  364   	 * Convience method to retreive an entities next version value
  365   	 */
  366   	private Object getNextVersion(FlushEntityEvent event) throws HibernateException {
  367   		
  368   		EntityEntry entry = event.getEntityEntry();
  369   		EntityPersister persister = entry.getPersister();
  370   		if ( persister.isVersioned() ) {
  371   
  372   			Object[] values = event.getPropertyValues();
  373   		    
  374   			if ( entry.isBeingReplicated() ) {
  375   				return Versioning.getVersion(values, persister);
  376   			}
  377   			else {
  378   				int[] dirtyProperties = event.getDirtyProperties();
  379   				
  380   				final boolean isVersionIncrementRequired = isVersionIncrementRequired( 
  381   						event, 
  382   						entry, 
  383   						persister, 
  384   						dirtyProperties 
  385   					);
  386   				
  387   				final Object nextVersion = isVersionIncrementRequired ?
  388   						Versioning.increment( entry.getVersion(), persister.getVersionType(), event.getSession() ) :
  389   						entry.getVersion(); //use the current version
  390   						
  391   				Versioning.setVersion(values, nextVersion, persister);
  392   				
  393   				return nextVersion;
  394   			}
  395   		}
  396   		else {
  397   			return null;
  398   		}
  399   		
  400   	}
  401   
  402   	private boolean isVersionIncrementRequired(
  403   			FlushEntityEvent event, 
  404   			EntityEntry entry, 
  405   			EntityPersister persister, 
  406   			int[] dirtyProperties
  407   	) {
  408   		final boolean isVersionIncrementRequired = entry.getStatus()!=Status.DELETED && ( 
  409   				dirtyProperties==null || 
  410   				Versioning.isVersionIncrementRequired( 
  411   						dirtyProperties, 
  412   						event.hasDirtyCollection(),
  413   						persister.getPropertyVersionability()
  414   					) 
  415   			);
  416   		return isVersionIncrementRequired;
  417   	}
  418   
  419   	/**
  420   	 * Performs all necessary checking to determine if an entity needs an SQL update
  421   	 * to synchronize its state to the database. Modifies the event by side-effect!
  422   	 * Note: this method is quite slow, avoid calling if possible!
  423   	 */
  424   	protected final boolean isUpdateNecessary(FlushEntityEvent event) throws HibernateException {
  425   
  426   		EntityPersister persister = event.getEntityEntry().getPersister();
  427   		Status status = event.getEntityEntry().getStatus();
  428   		
  429   		if ( !event.isDirtyCheckPossible() ) {
  430   			return true;
  431   		}
  432   		else {
  433   			
  434   			int[] dirtyProperties = event.getDirtyProperties();
  435   			if ( dirtyProperties!=null && dirtyProperties.length!=0 ) {
  436   				return true; //TODO: suck into event class
  437   			}
  438   			else {
  439   				return hasDirtyCollections( event, persister, status );
  440   			}
  441   			
  442   		}
  443   	}
  444   
  445   	private boolean hasDirtyCollections(FlushEntityEvent event, EntityPersister persister, Status status) {
  446   		if ( isCollectionDirtyCheckNecessary(persister, status) ) {
  447   			DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor( 
  448   					event.getSession(),
  449   					persister.getPropertyVersionability()
  450   				);
  451   			visitor.processEntityPropertyValues( event.getPropertyValues(), persister.getPropertyTypes() );
  452   			boolean hasDirtyCollections = visitor.wasDirtyCollectionFound();
  453   			event.setHasDirtyCollection(hasDirtyCollections);
  454   			return hasDirtyCollections;
  455   		}
  456   		else {
  457   			return false;
  458   		}
  459   	}
  460   
  461   	private boolean isCollectionDirtyCheckNecessary(EntityPersister persister, Status status) {
  462   		return status==Status.MANAGED && 
  463   				persister.isVersioned() && 
  464   				persister.hasCollections();
  465   	}
  466   	
  467   	/**
  468   	 * Perform a dirty check, and attach the results to the event
  469   	 */
  470   	protected void dirtyCheck(FlushEntityEvent event) throws HibernateException {
  471   		
  472   		final Object entity = event.getEntity();
  473   		final Object[] values = event.getPropertyValues();
  474   		final SessionImplementor session = event.getSession();
  475   		final EntityEntry entry = event.getEntityEntry();
  476   		final EntityPersister persister = entry.getPersister();
  477   		final Serializable id = entry.getId();
  478   		final Object[] loadedState = entry.getLoadedState();
  479   
  480   		int[] dirtyProperties = session.getInterceptor().findDirty( 
  481   				entity, 
  482   				id, 
  483   				values, 
  484   				loadedState, 
  485   				persister.getPropertyNames(), 
  486   				persister.getPropertyTypes() 
  487   			);
  488   		
  489   		event.setDatabaseSnapshot(null);
  490   
  491   		final boolean interceptorHandledDirtyCheck;
  492   		boolean cannotDirtyCheck;
  493   		
  494   		if ( dirtyProperties==null ) {
  495   			// Interceptor returned null, so do the dirtycheck ourself, if possible
  496   			interceptorHandledDirtyCheck = false;
  497   			
  498   			cannotDirtyCheck = loadedState==null; // object loaded by update()
  499   			if ( !cannotDirtyCheck ) {
  500   				// dirty check against the usual snapshot of the entity
  501   				dirtyProperties = persister.findDirty( values, loadedState, entity, session );
  502   				
  503   			}
  504   			else {
  505   				// dirty check against the database snapshot, if possible/necessary
  506   				final Object[] databaseSnapshot = getDatabaseSnapshot(session, persister, id);
  507   				if ( databaseSnapshot != null ) {
  508   					dirtyProperties = persister.findModified(databaseSnapshot, values, entity, session);
  509   					cannotDirtyCheck = false;
  510   					event.setDatabaseSnapshot(databaseSnapshot);
  511   				}
  512   			}
  513   		}
  514   		else {
  515   			// the Interceptor handled the dirty checking
  516   			cannotDirtyCheck = false;
  517   			interceptorHandledDirtyCheck = true;
  518   		}
  519   		
  520   		event.setDirtyProperties(dirtyProperties);
  521   		event.setDirtyCheckHandledByInterceptor(interceptorHandledDirtyCheck);
  522   		event.setDirtyCheckPossible(!cannotDirtyCheck);
  523   		
  524   	}
  525   
  526   	private Object[] getDatabaseSnapshot(SessionImplementor session, EntityPersister persister, Serializable id) {
  527   		if ( persister.isSelectBeforeUpdateRequired() ) {
  528   			Object[] snapshot = session.getPersistenceContext()
  529   					.getDatabaseSnapshot(id, persister);
  530   			if (snapshot==null) {
  531   				//do we even really need this? the update will fail anyway....
  532   				if ( session.getFactory().getStatistics().isStatisticsEnabled() ) {
  533   					session.getFactory().getStatisticsImplementor()
  534   							.optimisticFailure( persister.getEntityName() );
  535   				}
  536   				throw new StaleObjectStateException( persister.getEntityName(), id );
  537   			}
  538   			else {
  539   				return snapshot;
  540   			}
  541   		}
  542   		else {
  543   			//TODO: optimize away this lookup for entities w/o unsaved-value="undefined"
  544   			EntityKey entityKey = new EntityKey( id, persister, session.getEntityMode() );
  545   			return session.getPersistenceContext()
  546   					.getCachedDatabaseSnapshot( entityKey ); 
  547   		}
  548   	}
  549   
  550   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » event » def » [javadoc | source]