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.Map;
   29   
   30   import org.slf4j.Logger;
   31   import org.slf4j.LoggerFactory;
   32   
   33   import org.hibernate.LockMode;
   34   import org.hibernate.NonUniqueObjectException;
   35   import org.hibernate.action.EntityIdentityInsertAction;
   36   import org.hibernate.action.EntityInsertAction;
   37   import org.hibernate.classic.Lifecycle;
   38   import org.hibernate.classic.Validatable;
   39   import org.hibernate.engine.Cascade;
   40   import org.hibernate.engine.CascadingAction;
   41   import org.hibernate.engine.EntityEntry;
   42   import org.hibernate.engine.EntityKey;
   43   import org.hibernate.engine.ForeignKeys;
   44   import org.hibernate.engine.Nullability;
   45   import org.hibernate.engine.SessionImplementor;
   46   import org.hibernate.engine.Status;
   47   import org.hibernate.engine.Versioning;
   48   import org.hibernate.event.EventSource;
   49   import org.hibernate.id.IdentifierGenerationException;
   50   import org.hibernate.id.IdentifierGeneratorFactory;
   51   import org.hibernate.intercept.FieldInterceptionHelper;
   52   import org.hibernate.intercept.FieldInterceptor;
   53   import org.hibernate.persister.entity.EntityPersister;
   54   import org.hibernate.pretty.MessageHelper;
   55   import org.hibernate.type.Type;
   56   import org.hibernate.type.TypeFactory;
   57   
   58   /**
   59    * A convenience bas class for listeners responding to save events.
   60    *
   61    * @author Steve Ebersole.
   62    */
   63   public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener {
   64   
   65   	protected static final int PERSISTENT = 0;
   66   	protected static final int TRANSIENT = 1;
   67   	protected static final int DETACHED = 2;
   68   	protected static final int DELETED = 3;
   69   
   70   	private static final Logger log = LoggerFactory.getLogger( AbstractSaveEventListener.class );
   71   
   72   	/**
   73   	 * Prepares the save call using the given requested id.
   74   	 *
   75   	 * @param entity The entity to be saved.
   76   	 * @param requestedId The id to which to associate the entity.
   77   	 * @param entityName The name of the entity being saved.
   78   	 * @param anything Generally cascade-specific information.
   79   	 * @param source The session which is the source of this save event.
   80   	 *
   81   	 * @return The id used to save the entity.
   82   	 */
   83   	protected Serializable saveWithRequestedId(
   84   			Object entity,
   85   			Serializable requestedId,
   86   			String entityName,
   87   			Object anything,
   88   			EventSource source) {
   89   		return performSave(
   90   				entity,
   91   				requestedId,
   92   				source.getEntityPersister( entityName, entity ),
   93   				false,
   94   				anything,
   95   				source,
   96   				true
   97   		);
   98   	}
   99   
  100   	/**
  101   	 * Prepares the save call using a newly generated id.
  102   	 *
  103   	 * @param entity The entity to be saved
  104   	 * @param entityName The entity-name for the entity to be saved
  105   	 * @param anything Generally cascade-specific information.
  106   	 * @param source The session which is the source of this save event.
  107   	 * @param requiresImmediateIdAccess does the event context require
  108   	 * access to the identifier immediately after execution of this method (if
  109   	 * not, post-insert style id generators may be postponed if we are outside
  110   	 * a transaction).
  111   	 *
  112   	 * @return The id used to save the entity; may be null depending on the
  113   	 *         type of id generator used and the requiresImmediateIdAccess value
  114   	 */
  115   	protected Serializable saveWithGeneratedId(
  116   			Object entity,
  117   			String entityName,
  118   			Object anything,
  119   			EventSource source,
  120   			boolean requiresImmediateIdAccess) {
  121   		EntityPersister persister = source.getEntityPersister( entityName, entity );
  122   		Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity );
  123   		if ( generatedId == null ) {
  124   			throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() );
  125   		}
  126   		else if ( generatedId == IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR ) {
  127   			return source.getIdentifier( entity );
  128   		}
  129   		else if ( generatedId == IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
  130   			return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
  131   		}
  132   		else {
  133   
  134   			if ( log.isDebugEnabled() ) {
  135   				log.debug(
  136   						"generated identifier: " +
  137   								persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ) +
  138   								", using strategy: " +
  139   								persister.getIdentifierGenerator().getClass().getName()
  140   						//TODO: define toString()s for generators
  141   				);
  142   			}
  143   
  144   			return performSave( entity, generatedId, persister, false, anything, source, true );
  145   		}
  146   	}
  147   
  148   	/**
  149   	 * Ppepares the save call by checking the session caches for a pre-existing
  150   	 * entity and performing any lifecycle callbacks.
  151   	 *
  152   	 * @param entity The entity to be saved.
  153   	 * @param id The id by which to save the entity.
  154   	 * @param persister The entity's persister instance.
  155   	 * @param useIdentityColumn Is an identity column being used?
  156   	 * @param anything Generally cascade-specific information.
  157   	 * @param source The session from which the event originated.
  158   	 * @param requiresImmediateIdAccess does the event context require
  159   	 * access to the identifier immediately after execution of this method (if
  160   	 * not, post-insert style id generators may be postponed if we are outside
  161   	 * a transaction).
  162   	 *
  163   	 * @return The id used to save the entity; may be null depending on the
  164   	 *         type of id generator used and the requiresImmediateIdAccess value
  165   	 */
  166   	protected Serializable performSave(
  167   			Object entity,
  168   			Serializable id,
  169   			EntityPersister persister,
  170   			boolean useIdentityColumn,
  171   			Object anything,
  172   			EventSource source,
  173   			boolean requiresImmediateIdAccess) {
  174   
  175   		if ( log.isTraceEnabled() ) {
  176   			log.trace(
  177   					"saving " +
  178   							MessageHelper.infoString( persister, id, source.getFactory() )
  179   			);
  180   		}
  181   
  182   		EntityKey key;
  183   		if ( !useIdentityColumn ) {
  184   			key = new EntityKey( id, persister, source.getEntityMode() );
  185   			Object old = source.getPersistenceContext().getEntity( key );
  186   			if ( old != null ) {
  187   				if ( source.getPersistenceContext().getEntry( old ).getStatus() == Status.DELETED ) {
  188   					source.forceFlush( source.getPersistenceContext().getEntry( old ) );
  189   				}
  190   				else {
  191   					throw new NonUniqueObjectException( id, persister.getEntityName() );
  192   				}
  193   			}
  194   			persister.setIdentifier( entity, id, source.getEntityMode() );
  195   		}
  196   		else {
  197   			key = null;
  198   		}
  199   
  200   		if ( invokeSaveLifecycle( entity, persister, source ) ) {
  201   			return id; //EARLY EXIT
  202   		}
  203   
  204   		return performSaveOrReplicate(
  205   				entity,
  206   				key,
  207   				persister,
  208   				useIdentityColumn,
  209   				anything,
  210   				source,
  211   				requiresImmediateIdAccess
  212   		);
  213   	}
  214   
  215   	protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, EventSource source) {
  216   		// Sub-insertions should occur before containing insertion so
  217   		// Try to do the callback now
  218   		if ( persister.implementsLifecycle( source.getEntityMode() ) ) {
  219   			log.debug( "calling onSave()" );
  220   			if ( ( ( Lifecycle ) entity ).onSave( source ) ) {
  221   				log.debug( "insertion vetoed by onSave()" );
  222   				return true;
  223   			}
  224   		}
  225   		return false;
  226   	}
  227   
  228   	protected void validate(Object entity, EntityPersister persister, EventSource source) {
  229   		if ( persister.implementsValidatable( source.getEntityMode() ) ) {
  230   			( ( Validatable ) entity ).validate();
  231   		}
  232   	}
  233   
  234   	/**
  235   	 * Performs all the actual work needed to save an entity (well to get the save moved to
  236   	 * the execution queue).
  237   	 *
  238   	 * @param entity The entity to be saved
  239   	 * @param key The id to be used for saving the entity (or null, in the case of identity columns)
  240   	 * @param persister The entity's persister instance.
  241   	 * @param useIdentityColumn Should an identity column be used for id generation?
  242   	 * @param anything Generally cascade-specific information.
  243   	 * @param source The session which is the source of the current event.
  244   	 * @param requiresImmediateIdAccess Is access to the identifier required immediately
  245   	 * after the completion of the save?  persist(), for example, does not require this...
  246   	 *
  247   	 * @return The id used to save the entity; may be null depending on the
  248   	 *         type of id generator used and the requiresImmediateIdAccess value
  249   	 */
  250   	protected Serializable performSaveOrReplicate(
  251   			Object entity,
  252   			EntityKey key,
  253   			EntityPersister persister,
  254   			boolean useIdentityColumn,
  255   			Object anything,
  256   			EventSource source,
  257   			boolean requiresImmediateIdAccess) {
  258   
  259   		validate( entity, persister, source );
  260   
  261   		Serializable id = key == null ? null : key.getIdentifier();
  262   
  263   		boolean inTxn = source.getJDBCContext().isTransactionInProgress();
  264   		boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;
  265   
  266   		if ( useIdentityColumn && !shouldDelayIdentityInserts ) {
  267   			log.trace( "executing insertions" );
  268   			source.getActionQueue().executeInserts();
  269   		}
  270   
  271   		// Put a placeholder in entries, so we don't recurse back and try to save() the
  272   		// same object again. QUESTION: should this be done before onSave() is called?
  273   		// likewise, should it be done before onUpdate()?
  274   		source.getPersistenceContext().addEntry(
  275   				entity,
  276   				Status.SAVING,
  277   				null,
  278   				null,
  279   				id,
  280   				null,
  281   				LockMode.WRITE,
  282   				useIdentityColumn,
  283   				persister,
  284   				false,
  285   				false
  286   		);
  287   
  288   		cascadeBeforeSave( source, persister, entity, anything );
  289   
  290   		Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( anything ), source );
  291   		Type[] types = persister.getPropertyTypes();
  292   
  293   		boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );
  294   
  295   		if ( persister.hasCollections() ) {
  296   			substitute = substitute || visitCollectionsBeforeSave( entity, id, values, types, source );
  297   		}
  298   
  299   		if ( substitute ) {
  300   			persister.setPropertyValues( entity, values, source.getEntityMode() );
  301   		}
  302   
  303   		TypeFactory.deepCopy(
  304   				values,
  305   				types,
  306   				persister.getPropertyUpdateability(),
  307   				values,
  308   				source
  309   		);
  310   
  311   		new ForeignKeys.Nullifier( entity, false, useIdentityColumn, source )
  312   				.nullifyTransientReferences( values, types );
  313   		new Nullability( source ).checkNullability( values, persister, false );
  314   
  315   		if ( useIdentityColumn ) {
  316   			EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
  317   					values, entity, persister, source, shouldDelayIdentityInserts
  318   			);
  319   			if ( !shouldDelayIdentityInserts ) {
  320   				log.debug( "executing identity-insert immediately" );
  321   				source.getActionQueue().execute( insert );
  322   				id = insert.getGeneratedId();
  323   				//now done in EntityIdentityInsertAction
  324   				//persister.setIdentifier( entity, id, source.getEntityMode() );
  325   				key = new EntityKey( id, persister, source.getEntityMode() );
  326   				source.getPersistenceContext().checkUniqueness( key, entity );
  327   				//source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed
  328   			}
  329   			else {
  330   				log.debug( "delaying identity-insert due to no transaction in progress" );
  331   				source.getActionQueue().addAction( insert );
  332   				key = insert.getDelayedEntityKey();
  333   			}
  334   		}
  335   
  336   		Object version = Versioning.getVersion( values, persister );
  337   		source.getPersistenceContext().addEntity(
  338   				entity,
  339   				Status.MANAGED,
  340   				values,
  341   				key,
  342   				version,
  343   				LockMode.WRITE,
  344   				useIdentityColumn,
  345   				persister,
  346   				isVersionIncrementDisabled(),
  347   				false
  348   		);
  349   		//source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) );
  350   
  351   		if ( !useIdentityColumn ) {
  352   			source.getActionQueue().addAction(
  353   					new EntityInsertAction( id, values, entity, version, persister, source )
  354   			);
  355   		}
  356   
  357   		cascadeAfterSave( source, persister, entity, anything );
  358   
  359   		markInterceptorDirty( entity, persister, source );
  360   
  361   		return id;
  362   	}
  363   
  364   	private void markInterceptorDirty(Object entity, EntityPersister persister, EventSource source) {
  365   		if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
  366   			FieldInterceptor interceptor = FieldInterceptionHelper.injectFieldInterceptor(
  367   					entity,
  368   					persister.getEntityName(),
  369   					null,
  370   					source
  371   			);
  372   			interceptor.dirty();
  373   		}
  374   	}
  375   
  376   	protected Map getMergeMap(Object anything) {
  377   		return null;
  378   	}
  379   
  380   	/**
  381   	 * After the save, will te version number be incremented
  382   	 * if the instance is modified?
  383   	 *
  384   	 * @return True if the version will be incremented on an entity change after save;
  385   	 *         false otherwise.
  386   	 */
  387   	protected boolean isVersionIncrementDisabled() {
  388   		return false;
  389   	}
  390   
  391   	protected boolean visitCollectionsBeforeSave(Object entity, Serializable id, Object[] values, Type[] types, EventSource source) {
  392   		WrapVisitor visitor = new WrapVisitor( source );
  393   		// substitutes into values by side-effect
  394   		visitor.processEntityPropertyValues( values, types );
  395   		return visitor.isSubstitutionRequired();
  396   	}
  397   
  398   	/**
  399   	 * Perform any property value substitution that is necessary
  400   	 * (interceptor callback, version initialization...)
  401   	 *
  402   	 * @param entity The entity
  403   	 * @param id The entity identifier
  404   	 * @param values The snapshot entity state
  405   	 * @param persister The entity persister
  406   	 * @param source The originating session
  407   	 *
  408   	 * @return True if the snapshot state changed such that
  409   	 * reinjection of the values into the entity is required.
  410   	 */
  411   	protected boolean substituteValuesIfNecessary(
  412   			Object entity,
  413   			Serializable id,
  414   			Object[] values,
  415   			EntityPersister persister,
  416   			SessionImplementor source) {
  417   		boolean substitute = source.getInterceptor().onSave(
  418   				entity,
  419   				id,
  420   				values,
  421   				persister.getPropertyNames(),
  422   				persister.getPropertyTypes()
  423   		);
  424   
  425   		//keep the existing version number in the case of replicate!
  426   		if ( persister.isVersioned() ) {
  427   			substitute = Versioning.seedVersion(
  428   					values,
  429   					persister.getVersionProperty(),
  430   					persister.getVersionType(),
  431   					source
  432   			) || substitute;
  433   		}
  434   		return substitute;
  435   	}
  436   
  437   	/**
  438   	 * Handles the calls needed to perform pre-save cascades for the given entity.
  439   	 *
  440   	 * @param source The session from whcih the save event originated.
  441   	 * @param persister The entity's persister instance.
  442   	 * @param entity The entity to be saved.
  443   	 * @param anything Generally cascade-specific data
  444   	 */
  445   	protected void cascadeBeforeSave(
  446   			EventSource source,
  447   			EntityPersister persister,
  448   			Object entity,
  449   			Object anything) {
  450   
  451   		// cascade-save to many-to-one BEFORE the parent is saved
  452   		source.getPersistenceContext().incrementCascadeLevel();
  453   		try {
  454   			new Cascade( getCascadeAction(), Cascade.BEFORE_INSERT_AFTER_DELETE, source )
  455   					.cascade( persister, entity, anything );
  456   		}
  457   		finally {
  458   			source.getPersistenceContext().decrementCascadeLevel();
  459   		}
  460   	}
  461   
  462   	/**
  463   	 * Handles to calls needed to perform post-save cascades.
  464   	 *
  465   	 * @param source The session from which the event originated.
  466   	 * @param persister The entity's persister instance.
  467   	 * @param entity The entity beng saved.
  468   	 * @param anything Generally cascade-specific data
  469   	 */
  470   	protected void cascadeAfterSave(
  471   			EventSource source,
  472   			EntityPersister persister,
  473   			Object entity,
  474   			Object anything) {
  475   
  476   		// cascade-save to collections AFTER the collection owner was saved
  477   		source.getPersistenceContext().incrementCascadeLevel();
  478   		try {
  479   			new Cascade( getCascadeAction(), Cascade.AFTER_INSERT_BEFORE_DELETE, source )
  480   					.cascade( persister, entity, anything );
  481   		}
  482   		finally {
  483   			source.getPersistenceContext().decrementCascadeLevel();
  484   		}
  485   	}
  486   
  487   	protected abstract CascadingAction getCascadeAction();
  488   
  489   	/**
  490   	 * Determine whether the entity is persistent, detached, or transient
  491   	 *
  492   	 * @param entity The entity to check
  493   	 * @param entityName The name of the entity
  494   	 * @param entry The entity's entry in the persistence context
  495   	 * @param source The originating session.
  496   	 *
  497   	 * @return The state.
  498   	 */
  499   	protected int getEntityState(
  500   			Object entity,
  501   			String entityName,
  502   			EntityEntry entry, //pass this as an argument only to avoid double looking
  503   			SessionImplementor source) {
  504   
  505   		if ( entry != null ) { // the object is persistent
  506   
  507   			//the entity is associated with the session, so check its status
  508   			if ( entry.getStatus() != Status.DELETED ) {
  509   				// do nothing for persistent instances
  510   				if ( log.isTraceEnabled() ) {
  511   					log.trace(
  512   							"persistent instance of: " +
  513   									getLoggableName( entityName, entity )
  514   					);
  515   				}
  516   				return PERSISTENT;
  517   			}
  518   			else {
  519   				//ie. e.status==DELETED
  520   				if ( log.isTraceEnabled() ) {
  521   					log.trace(
  522   							"deleted instance of: " +
  523   									getLoggableName( entityName, entity )
  524   					);
  525   				}
  526   				return DELETED;
  527   			}
  528   
  529   		}
  530   		else { // the object is transient or detached
  531   
  532   			//the entity is not associated with the session, so
  533   			//try interceptor and unsaved-value
  534   
  535   			if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source ) ) {
  536   				if ( log.isTraceEnabled() ) {
  537   					log.trace(
  538   							"transient instance of: " +
  539   									getLoggableName( entityName, entity )
  540   					);
  541   				}
  542   				return TRANSIENT;
  543   			}
  544   			else {
  545   				if ( log.isTraceEnabled() ) {
  546   					log.trace(
  547   							"detached instance of: " +
  548   									getLoggableName( entityName, entity )
  549   					);
  550   				}
  551   				return DETACHED;
  552   			}
  553   
  554   		}
  555   	}
  556   
  557   	protected String getLoggableName(String entityName, Object entity) {
  558   		return entityName == null ? entity.getClass().getName() : entityName;
  559   	}
  560   
  561   	protected Boolean getAssumedUnsaved() {
  562   		return null;
  563   	}
  564   
  565   }

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