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   import java.util.Iterator;
   29   import java.util.Map;
   30   
   31   import org.slf4j.Logger;
   32   import org.slf4j.LoggerFactory;
   33   
   34   import org.hibernate.AssertionFailure;
   35   import org.hibernate.HibernateException;
   36   import org.hibernate.ObjectDeletedException;
   37   import org.hibernate.StaleObjectStateException;
   38   import org.hibernate.TransientObjectException;
   39   import org.hibernate.WrongClassException;
   40   import org.hibernate.engine.Cascade;
   41   import org.hibernate.engine.CascadingAction;
   42   import org.hibernate.engine.EntityEntry;
   43   import org.hibernate.engine.EntityKey;
   44   import org.hibernate.engine.SessionImplementor;
   45   import org.hibernate.engine.Status;
   46   import org.hibernate.event.EventSource;
   47   import org.hibernate.event.MergeEvent;
   48   import org.hibernate.event.MergeEventListener;
   49   import org.hibernate.intercept.FieldInterceptionHelper;
   50   import org.hibernate.intercept.FieldInterceptor;
   51   import org.hibernate.persister.entity.EntityPersister;
   52   import org.hibernate.proxy.HibernateProxy;
   53   import org.hibernate.proxy.LazyInitializer;
   54   import org.hibernate.type.ForeignKeyDirection;
   55   import org.hibernate.type.TypeFactory;
   56   import org.hibernate.util.IdentityMap;
   57   
   58   /**
   59    * Defines the default copy event listener used by hibernate for copying entities
   60    * in response to generated copy events.
   61    *
   62    * @author Gavin King
   63    */
   64   public class DefaultMergeEventListener extends AbstractSaveEventListener 
   65   	implements MergeEventListener {
   66   
   67   	private static final Logger log = LoggerFactory.getLogger(DefaultMergeEventListener.class);
   68   	
   69   	protected Map getMergeMap(Object anything) {
   70   		return IdentityMap.invert( (Map) anything );
   71   	}
   72   
   73   	/**
   74   	 * Handle the given merge event.
   75   	 *
   76   	 * @param event The merge event to be handled.
   77   	 * @throws HibernateException
   78   	 */
   79   	public void onMerge(MergeEvent event) throws HibernateException {
   80   		Map copyCache = IdentityMap.instantiate(10);
   81   		onMerge( event, copyCache );
   82   		for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
   83   			Object entity = it.next();
   84   			if ( entity instanceof HibernateProxy ) {
   85   				entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
   86   			}
   87   			EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
   88   			if ( entry == null ) {
   89   				throw new TransientObjectException(
   90   						"object references an unsaved transient instance - save the transient instance before merging: " +
   91   						event.getSession().guessEntityName( entity )
   92   				);
   93   				// TODO: cache the entity name somewhere so that it is available to this exception
   94   				// entity name will not be available for non-POJO entities
   95   			}
   96   			if ( entry.getStatus() != Status.MANAGED ) {
   97   				throw new AssertionFailure( "Merged entity does not have status set to MANAGED; "+entry+" status="+entry.getStatus() );
   98   			}
   99   		}
  100   	}
  101   
  102   	/** 
  103   	 * Handle the given merge event.
  104   	 *
  105   	 * @param event The merge event to be handled.
  106   	 * @throws HibernateException
  107   	 */
  108   	public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
  109   
  110   		final EventSource source = event.getSession();
  111   		final Object original = event.getOriginal();
  112   
  113   		if ( original != null ) {
  114   
  115   			final Object entity;
  116   			if ( original instanceof HibernateProxy ) {
  117   				LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer();
  118   				if ( li.isUninitialized() ) {
  119   					log.trace("ignoring uninitialized proxy");
  120   					event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) );
  121   					return; //EARLY EXIT!
  122   				}
  123   				else {
  124   					entity = li.getImplementation();
  125   				}
  126   			}
  127   			else {
  128   				entity = original;
  129   			}
  130   			
  131   			if ( copyCache.containsKey(entity) &&
  132   					source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
  133   				log.trace("already merged");
  134   				event.setResult(entity);
  135   			}
  136   			else {
  137   				event.setEntity( entity );
  138   				int entityState = -1;
  139   
  140   				// Check the persistence context for an entry relating to this
  141   				// entity to be merged...
  142   				EntityEntry entry = source.getPersistenceContext().getEntry( entity );
  143   				if ( entry == null ) {
  144   					EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
  145   					Serializable id = persister.getIdentifier( entity, source.getEntityMode() );
  146   					if ( id != null ) {
  147   						EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
  148   						Object managedEntity = source.getPersistenceContext().getEntity( key );
  149   						entry = source.getPersistenceContext().getEntry( managedEntity );
  150   						if ( entry != null ) {
  151   							// we have specialized case of a detached entity from the
  152   							// perspective of the merge operation.  Specifically, we
  153   							// have an incoming entity instance which has a corresponding
  154   							// entry in the current persistence context, but registered
  155   							// under a different entity instance
  156   							entityState = DETACHED;
  157   						}
  158   					}
  159   				}
  160   
  161   				if ( entityState == -1 ) {
  162   					entityState = getEntityState( entity, event.getEntityName(), entry, source );
  163   				}
  164   				
  165   				switch (entityState) {
  166   					case DETACHED:
  167   						entityIsDetached(event, copyCache);
  168   						break;
  169   					case TRANSIENT:
  170   						entityIsTransient(event, copyCache);
  171   						break;
  172   					case PERSISTENT:
  173   						entityIsPersistent(event, copyCache);
  174   						break;
  175   					default: //DELETED
  176   						throw new ObjectDeletedException(
  177   								"deleted instance passed to merge", 
  178   								null, 
  179   								getLoggableName( event.getEntityName(), entity )
  180   							);			
  181   				}
  182   			}
  183   			
  184   		}
  185   		
  186   	}
  187   
  188   	protected void entityIsPersistent(MergeEvent event, Map copyCache) {
  189   		log.trace("ignoring persistent instance");
  190   		
  191   		//TODO: check that entry.getIdentifier().equals(requestedId)
  192   		
  193   		final Object entity = event.getEntity();
  194   		final EventSource source = event.getSession();
  195   		final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
  196   		
  197   		copyCache.put(entity, entity); //before cascade!
  198   		
  199   		cascadeOnMerge(source, persister, entity, copyCache);
  200   		copyValues(persister, entity, entity, source, copyCache);
  201   		
  202   		event.setResult(entity);
  203   	}
  204   	
  205   	protected void entityIsTransient(MergeEvent event, Map copyCache) {
  206   		
  207   		log.trace("merging transient instance");
  208   		
  209   		final Object entity = event.getEntity();
  210   		final EventSource source = event.getSession();
  211   
  212   		final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
  213   		final String entityName = persister.getEntityName();
  214   		
  215   		final Serializable id = persister.hasIdentifierProperty() ?
  216   				persister.getIdentifier( entity, source.getEntityMode() ) :
  217   		        null;
  218   		if ( copyCache.containsKey( entity ) ) {
  219   			persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
  220   		}
  221   		else {
  222   			copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
  223   			//TODO: should this be Session.instantiate(Persister, ...)?
  224   		}
  225   		final Object copy = copyCache.get( entity );
  226   
  227   		// cascade first, so that all unsaved objects get their
  228   		// copy created before we actually copy
  229   		//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
  230   		super.cascadeBeforeSave(source, persister, entity, copyCache);
  231   		copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
  232   		
  233   		//this bit is only *really* absolutely necessary for handling 
  234   		//requestedId, but is also good if we merge multiple object 
  235   		//graphs, since it helps ensure uniqueness
  236   		final Serializable requestedId = event.getRequestedId();
  237   		if (requestedId==null) {
  238   			saveWithGeneratedId( copy, entityName, copyCache, source, false );
  239   		}
  240   		else {
  241   			saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
  242   		}
  243   		
  244   		// cascade first, so that all unsaved objects get their 
  245   		// copy created before we actually copy
  246   		super.cascadeAfterSave(source, persister, entity, copyCache);
  247   		copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
  248   		
  249   		event.setResult(copy);
  250   
  251   	}
  252   
  253   	protected void entityIsDetached(MergeEvent event, Map copyCache) {
  254   		
  255   		log.trace("merging detached instance");
  256   		
  257   		final Object entity = event.getEntity();
  258   		final EventSource source = event.getSession();
  259   
  260   		final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
  261   		final String entityName = persister.getEntityName();
  262   			
  263   		Serializable id = event.getRequestedId();
  264   		if ( id == null ) {
  265   			id = persister.getIdentifier( entity, source.getEntityMode() );
  266   		}
  267   		else {
  268   			// check that entity id = requestedId
  269   			Serializable entityId = persister.getIdentifier( entity, source.getEntityMode() );
  270   			if ( !persister.getIdentifierType().isEqual( id, entityId, source.getEntityMode(), source.getFactory() ) ) {
  271   				throw new HibernateException( "merge requested with id not matching id of passed entity" );
  272   			}
  273   		}
  274   		
  275   		String previousFetchProfile = source.getFetchProfile();
  276   		source.setFetchProfile("merge");
  277   		//we must clone embedded composite identifiers, or 
  278   		//we will get back the same instance that we pass in
  279   		final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType()
  280   				.deepCopy( id, source.getEntityMode(), source.getFactory() );
  281   		final Object result = source.get(entityName, clonedIdentifier);
  282   		source.setFetchProfile(previousFetchProfile);
  283   		
  284   		if ( result == null ) {
  285   			//TODO: we should throw an exception if we really *know* for sure  
  286   			//      that this is a detached instance, rather than just assuming
  287   			//throw new StaleObjectStateException(entityName, id);
  288   			
  289   			// we got here because we assumed that an instance
  290   			// with an assigned id was detached, when it was
  291   			// really persistent
  292   			entityIsTransient(event, copyCache);
  293   		}
  294   		else {
  295   			copyCache.put(entity, result); //before cascade!
  296   	
  297   			final Object target = source.getPersistenceContext().unproxy(result);
  298   			if ( target == entity ) {
  299   				throw new AssertionFailure("entity was not detached");
  300   			}
  301   			else if ( !source.getEntityName(target).equals(entityName) ) {
  302   				throw new WrongClassException(
  303   						"class of the given object did not match class of persistent copy",
  304   						event.getRequestedId(),
  305   						entityName
  306   					);
  307   			}
  308   			else if ( isVersionChanged( entity, source, persister, target ) ) {
  309   				if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
  310   					source.getFactory().getStatisticsImplementor()
  311   							.optimisticFailure( entityName );
  312   				}
  313   				throw new StaleObjectStateException( entityName, id );
  314   			}
  315   	
  316   			// cascade first, so that all unsaved objects get their 
  317   			// copy created before we actually copy
  318   			cascadeOnMerge(source, persister, entity, copyCache);
  319   			copyValues(persister, entity, target, source, copyCache);
  320   			
  321   			//copyValues works by reflection, so explicitly mark the entity instance dirty
  322   			markInterceptorDirty( entity, target );
  323   			
  324   			event.setResult(result);
  325   		}
  326   
  327   	}
  328   
  329   	private void markInterceptorDirty(final Object entity, final Object target) {
  330   		if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
  331   			FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( target );
  332   			if ( interceptor != null ) {
  333   				interceptor.dirty();
  334   			}
  335   		}
  336   	}
  337   
  338   	private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {
  339   		if ( ! persister.isVersioned() ) {
  340   			return false;
  341   		}
  342   		// for merging of versioned entities, we consider the version having
  343   		// been changed only when:
  344   		// 1) the two version values are different;
  345   		//      *AND*
  346   		// 2) The target actually represents database state!
  347   		//
  348   		// This second condition is a special case which allows
  349   		// an entity to be merged during the same transaction
  350   		// (though during a seperate operation) in which it was
  351   		// originally persisted/saved
  352   		boolean changed = ! persister.getVersionType().isSame(
  353   				persister.getVersion( target, source.getEntityMode() ),
  354   				persister.getVersion( entity, source.getEntityMode() ),
  355   				source.getEntityMode()
  356   		);
  357   
  358   		// TODO : perhaps we should additionally require that the incoming entity
  359   		// version be equivalent to the defined unsaved-value?
  360   		return changed && existsInDatabase( target, source, persister );
  361   	}
  362   
  363   	private boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) {
  364   		EntityEntry entry = source.getPersistenceContext().getEntry( entity );
  365   		if ( entry == null ) {
  366   			Serializable id = persister.getIdentifier( entity, source.getEntityMode() );
  367   			if ( id != null ) {
  368   				EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
  369   				Object managedEntity = source.getPersistenceContext().getEntity( key );
  370   				entry = source.getPersistenceContext().getEntry( managedEntity );
  371   			}
  372   		}
  373   
  374   		if ( entry == null ) {
  375   			// perhaps this should be an exception since it is only ever used
  376   			// in the above method?
  377   			return false;
  378   		}
  379   		else {
  380   			return entry.isExistsInDatabase();
  381   		}
  382   	}
  383   	
  384   	protected void copyValues(
  385   		final EntityPersister persister, 
  386   		final Object entity, 
  387   		final Object target, 
  388   		final SessionImplementor source,
  389   		final Map copyCache
  390   	) {
  391   		
  392   		final Object[] copiedValues = TypeFactory.replace(
  393   				persister.getPropertyValues( entity, source.getEntityMode() ),
  394   				persister.getPropertyValues( target, source.getEntityMode() ),
  395   				persister.getPropertyTypes(),
  396   				source,
  397   				target, 
  398   				copyCache
  399   			);
  400   
  401   		persister.setPropertyValues( target, copiedValues, source.getEntityMode() );
  402   	}
  403   
  404   	protected void copyValues(
  405   			final EntityPersister persister,
  406   			final Object entity, 
  407   			final Object target, 
  408   			final SessionImplementor source,
  409   			final Map copyCache,
  410   			final ForeignKeyDirection foreignKeyDirection) {
  411   
  412   		final Object[] copiedValues;
  413   
  414   		if ( foreignKeyDirection == ForeignKeyDirection.FOREIGN_KEY_TO_PARENT ) {
  415   			// this is the second pass through on a merge op, so here we limit the
  416   			// replacement to associations types (value types were already replaced
  417   			// during the first pass)
  418   			copiedValues = TypeFactory.replaceAssociations(
  419   					persister.getPropertyValues( entity, source.getEntityMode() ),
  420   					persister.getPropertyValues( target, source.getEntityMode() ),
  421   					persister.getPropertyTypes(),
  422   					source,
  423   					target,
  424   					copyCache,
  425   					foreignKeyDirection
  426   			);
  427   		}
  428   		else {
  429   			copiedValues = TypeFactory.replace(
  430   					persister.getPropertyValues( entity, source.getEntityMode() ),
  431   					persister.getPropertyValues( target, source.getEntityMode() ),
  432   					persister.getPropertyTypes(),
  433   					source,
  434   					target,
  435   					copyCache,
  436   					foreignKeyDirection
  437   			);
  438   		}
  439   
  440   		persister.setPropertyValues( target, copiedValues, source.getEntityMode() );
  441   	}
  442   
  443   	/** 
  444   	 * Perform any cascades needed as part of this copy event.
  445   	 *
  446   	 * @param source The merge event being processed.
  447   	 * @param persister The persister of the entity being copied.
  448   	 * @param entity The entity being copied.
  449   	 * @param copyCache A cache of already copied instance.
  450   	 */
  451   	protected void cascadeOnMerge(
  452   		final EventSource source,
  453   		final EntityPersister persister,
  454   		final Object entity,
  455   		final Map copyCache
  456   	) {
  457   		source.getPersistenceContext().incrementCascadeLevel();
  458   		try {
  459   			new Cascade( getCascadeAction(), Cascade.BEFORE_MERGE, source )
  460   					.cascade(persister, entity, copyCache);
  461   		}
  462   		finally {
  463   			source.getPersistenceContext().decrementCascadeLevel();
  464   		}
  465   	}
  466   
  467   
  468   	protected CascadingAction getCascadeAction() {
  469   		return CascadingAction.MERGE;
  470   	}
  471   
  472   	protected Boolean getAssumedUnsaved() {
  473   		return Boolean.FALSE;
  474   	}
  475   	
  476   	/**
  477   	 * Cascade behavior is redefined by this subclass, disable superclass behavior
  478   	 */
  479   	protected void cascadeAfterSave(EventSource source, EntityPersister persister, Object entity, Object anything) 
  480   	throws HibernateException {
  481   	}
  482   
  483   	/**
  484   	 * Cascade behavior is redefined by this subclass, disable superclass behavior
  485   	 */
  486   	protected void cascadeBeforeSave(EventSource source, EntityPersister persister, Object entity, Object anything) 
  487   	throws HibernateException {
  488   	}
  489   
  490   }

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