/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.event.internal; import java.io.Serializable; import java.util.Map; import org.jboss.logging.Logger; import org.hibernate.LockMode; import org.hibernate.NonUniqueObjectException; import org.hibernate.action.internal.EntityIdentityInsertAction; import org.hibernate.action.internal.EntityInsertAction; import org.hibernate.bytecode.instrumentation.internal.FieldInterceptionHelper; import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.id.IdentifierGeneratorHelper; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; /** * A convenience bas class for listeners responding to save events. * * @author Steve Ebersole. */ public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener { public enum EntityState{ PERSISTENT, TRANSIENT, DETACHED, DELETED; } private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, AbstractSaveEventListener.class.getName()); /** * Prepares the save call using the given requested id. * * @param entity The entity to be saved. * @param requestedId The id to which to associate the entity. * @param entityName The name of the entity being saved. * @param anything Generally cascade-specific information. * @param source The session which is the source of this save event. * * @return The id used to save the entity. */ protected Serializable saveWithRequestedId( Object entity, Serializable requestedId, String entityName, Object anything, EventSource source) { return performSave( entity, requestedId, source.getEntityPersister( entityName, entity ), false, anything, source, true ); } /** * Prepares the save call using a newly generated id. * * @param entity The entity to be saved * @param entityName The entity-name for the entity to be saved * @param anything Generally cascade-specific information. * @param source The session which is the source of this save event. * @param requiresImmediateIdAccess does the event context require * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected Serializable saveWithGeneratedId( Object entity, String entityName, Object anything, EventSource source, boolean requiresImmediateIdAccess) { EntityPersister persister = source.getEntityPersister( entityName, entity ); Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity ); if ( generatedId == null ) { throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() ); } else if ( generatedId == IdentifierGeneratorHelper.SHORT_CIRCUIT_INDICATOR ) { return source.getIdentifier( entity ); } else if ( generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR ) { return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess ); } else { // TODO: define toString()s for generators if (LOG.isDebugEnabled()) LOG.debugf("Generated identifier: %s, using strategy: %s", persister.getIdentifierType().toLoggableString(generatedId, source.getFactory()), persister.getIdentifierGenerator().getClass().getName()); return performSave( entity, generatedId, persister, false, anything, source, true ); } } /** * Ppepares the save call by checking the session caches for a pre-existing * entity and performing any lifecycle callbacks. * * @param entity The entity to be saved. * @param id The id by which to save the entity. * @param persister The entity's persister instance. * @param useIdentityColumn Is an identity column being used? * @param anything Generally cascade-specific information. * @param source The session from which the event originated. * @param requiresImmediateIdAccess does the event context require * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected Serializable performSave( Object entity, Serializable id, EntityPersister persister, boolean useIdentityColumn, Object anything, EventSource source, boolean requiresImmediateIdAccess) { if ( LOG.isTraceEnabled() ) { LOG.trace("Saving " + MessageHelper.infoString(persister, id, source.getFactory())); } final EntityKey key; if ( !useIdentityColumn ) { key = source.generateEntityKey( id, persister ); Object old = source.getPersistenceContext().getEntity( key ); if ( old != null ) { if ( source.getPersistenceContext().getEntry( old ).getStatus() == Status.DELETED ) { source.forceFlush( source.getPersistenceContext().getEntry( old ) ); } else { throw new NonUniqueObjectException( id, persister.getEntityName() ); } } persister.setIdentifier( entity, id, source ); } else { key = null; } if ( invokeSaveLifecycle( entity, persister, source ) ) { return id; //EARLY EXIT } return performSaveOrReplicate( entity, key, persister, useIdentityColumn, anything, source, requiresImmediateIdAccess ); } protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, EventSource source) { // Sub-insertions should occur before containing insertion so // Try to do the callback now if ( persister.implementsLifecycle() ) { LOG.debugf("Calling onSave()"); if ( ( ( Lifecycle ) entity ).onSave( source ) ) { LOG.debugf("Insertion vetoed by onSave()"); return true; } } return false; } /** * Performs all the actual work needed to save an entity (well to get the save moved to * the execution queue). * * @param entity The entity to be saved * @param key The id to be used for saving the entity (or null, in the case of identity columns) * @param persister The entity's persister instance. * @param useIdentityColumn Should an identity column be used for id generation? * @param anything Generally cascade-specific information. * @param source The session which is the source of the current event. * @param requiresImmediateIdAccess Is access to the identifier required immediately * after the completion of the save? persist(), for example, does not require this... * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected Serializable performSaveOrReplicate( Object entity, EntityKey key, EntityPersister persister, boolean useIdentityColumn, Object anything, EventSource source, boolean requiresImmediateIdAccess) { Serializable id = key == null ? null : key.getIdentifier(); boolean inTxn = source.getTransactionCoordinator().isTransactionInProgress(); boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? // likewise, should it be done before onUpdate()? source.getPersistenceContext().addEntry( entity, Status.SAVING, null, null, id, null, LockMode.WRITE, useIdentityColumn, persister, false, false ); cascadeBeforeSave( source, persister, entity, anything ); if ( useIdentityColumn && !shouldDelayIdentityInserts ) { LOG.trace("Executing insertions"); source.getActionQueue().executeInserts(); } Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( anything ), source ); Type[] types = persister.getPropertyTypes(); boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source ); if ( persister.hasCollections() ) { substitute = substitute || visitCollectionsBeforeSave( entity, id, values, types, source ); } if ( substitute ) { persister.setPropertyValues( entity, values ); } TypeHelper.deepCopy( values, types, persister.getPropertyUpdateability(), values, source ); new ForeignKeys.Nullifier( entity, false, useIdentityColumn, source ) .nullifyTransientReferences( values, types ); new Nullability( source ).checkNullability( values, persister, false ); if ( useIdentityColumn ) { EntityIdentityInsertAction insert = new EntityIdentityInsertAction( values, entity, persister, source, shouldDelayIdentityInserts ); if ( !shouldDelayIdentityInserts ) { LOG.debugf("Executing identity-insert immediately"); source.getActionQueue().execute( insert ); id = insert.getGeneratedId(); key = source.generateEntityKey( id, persister ); source.getPersistenceContext().checkUniqueness( key, entity ); } else { LOG.debugf("Delaying identity-insert due to no transaction in progress"); source.getActionQueue().addAction( insert ); key = insert.getDelayedEntityKey(); } } Object version = Versioning.getVersion( values, persister ); source.getPersistenceContext().addEntity( entity, ( persister.isMutable() ? Status.MANAGED : Status.READ_ONLY ), values, key, version, LockMode.WRITE, useIdentityColumn, persister, isVersionIncrementDisabled(), false ); //source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) ); if ( !useIdentityColumn ) { source.getActionQueue().addAction( new EntityInsertAction( id, values, entity, version, persister, source ) ); } cascadeAfterSave( source, persister, entity, anything ); markInterceptorDirty( entity, persister, source ); return id; } private void markInterceptorDirty(Object entity, EntityPersister persister, EventSource source) { if ( FieldInterceptionHelper.isInstrumented( entity ) ) { FieldInterceptor interceptor = FieldInterceptionHelper.injectFieldInterceptor( entity, persister.getEntityName(), null, source ); interceptor.dirty(); } } protected Map getMergeMap(Object anything) { return null; } /** * After the save, will te version number be incremented * if the instance is modified? * * @return True if the version will be incremented on an entity change after save; * false otherwise. */ protected boolean isVersionIncrementDisabled() { return false; } protected boolean visitCollectionsBeforeSave(Object entity, Serializable id, Object[] values, Type[] types, EventSource source) { WrapVisitor visitor = new WrapVisitor( source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); } /** * Perform any property value substitution that is necessary * (interceptor callback, version initialization...) * * @param entity The entity * @param id The entity identifier * @param values The snapshot entity state * @param persister The entity persister * @param source The originating session * * @return True if the snapshot state changed such that * reinjection of the values into the entity is required. */ protected boolean substituteValuesIfNecessary( Object entity, Serializable id, Object[] values, EntityPersister persister, SessionImplementor source) { boolean substitute = source.getInterceptor().onSave( entity, id, values, persister.getPropertyNames(), persister.getPropertyTypes() ); //keep the existing version number in the case of replicate! if ( persister.isVersioned() ) { substitute = Versioning.seedVersion( values, persister.getVersionProperty(), persister.getVersionType(), source ) || substitute; } return substitute; } /** * Handles the calls needed to perform pre-save cascades for the given entity. * * @param source The session from whcih the save event originated. * @param persister The entity's persister instance. * @param entity The entity to be saved. * @param anything Generally cascade-specific data */ protected void cascadeBeforeSave( EventSource source, EntityPersister persister, Object entity, Object anything) { // cascade-save to many-to-one BEFORE the parent is saved source.getPersistenceContext().incrementCascadeLevel(); try { new Cascade( getCascadeAction(), Cascade.BEFORE_INSERT_AFTER_DELETE, source ) .cascade( persister, entity, anything ); } finally { source.getPersistenceContext().decrementCascadeLevel(); } } /** * Handles to calls needed to perform post-save cascades. * * @param source The session from which the event originated. * @param persister The entity's persister instance. * @param entity The entity beng saved. * @param anything Generally cascade-specific data */ protected void cascadeAfterSave( EventSource source, EntityPersister persister, Object entity, Object anything) { // cascade-save to collections AFTER the collection owner was saved source.getPersistenceContext().incrementCascadeLevel(); try { new Cascade( getCascadeAction(), Cascade.AFTER_INSERT_BEFORE_DELETE, source ) .cascade( persister, entity, anything ); } finally { source.getPersistenceContext().decrementCascadeLevel(); } } protected abstract CascadingAction getCascadeAction(); /** * Determine whether the entity is persistent, detached, or transient * * @param entity The entity to check * @param entityName The name of the entity * @param entry The entity's entry in the persistence context * @param source The originating session. * * @return The state. */ protected EntityState getEntityState( Object entity, String entityName, EntityEntry entry, //pass this as an argument only to avoid double looking SessionImplementor source) { if ( entry != null ) { // the object is persistent //the entity is associated with the session, so check its status if ( entry.getStatus() != Status.DELETED ) { // do nothing for persistent instances if (LOG.isTraceEnabled()) LOG.trace("Persistent instance of: " + getLoggableName(entityName, entity)); return EntityState.PERSISTENT; } // ie. e.status==DELETED if (LOG.isTraceEnabled()) LOG.trace("Deleted instance of: " + getLoggableName(entityName, entity)); return EntityState.DELETED; } // the object is transient or detached // the entity is not associated with the session, so // try interceptor and unsaved-value if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source )) { if (LOG.isTraceEnabled()) LOG.trace("Transient instance of: " + getLoggableName(entityName, entity)); return EntityState.TRANSIENT; } if (LOG.isTraceEnabled()) LOG.trace("Detached instance of: " + getLoggableName(entityName, entity)); return EntityState.DETACHED; } protected String getLoggableName(String entityName, Object entity) { return entityName == null ? entity.getClass().getName() : entityName; } protected Boolean getAssumedUnsaved() { return null; } }