1
//$Id: DefaultDeleteEventListener.java 10949 2006-12-07 21:53:41Z steve.ebersole@jboss.com $
2
package org.hibernate.event.def;
4
import java.io.Serializable;
7
import org.apache.commons.logging.Log;
8
import org.apache.commons.logging.LogFactory;
10
import org.hibernate.CacheMode;
11
import org.hibernate.HibernateException;
12
import org.hibernate.LockMode;
13
import org.hibernate.TransientObjectException;
14
import org.hibernate.util.IdentitySet;
15
import org.hibernate.action.EntityDeleteAction;
16
import org.hibernate.classic.Lifecycle;
17
import org.hibernate.engine.Cascade;
18
import org.hibernate.engine.CascadingAction;
19
import org.hibernate.engine.EntityEntry;
20
import org.hibernate.engine.EntityKey;
21
import org.hibernate.engine.ForeignKeys;
22
import org.hibernate.engine.Nullability;
23
import org.hibernate.engine.PersistenceContext;
24
import org.hibernate.engine.Status;
25
import org.hibernate.event.DeleteEvent;
26
import org.hibernate.event.DeleteEventListener;
27
import org.hibernate.event.EventSource;
28
import org.hibernate.persister.entity.EntityPersister;
29
import org.hibernate.pretty.MessageHelper;
30
import org.hibernate.type.Type;
31
import org.hibernate.type.TypeFactory;
35
* Defines the default delete event listener used by hibernate for deleting entities
36
* from the datastore in response to generated delete events.
38
* @author Steve Ebersole
40
public class DefaultDeleteEventListener implements DeleteEventListener {
42
private static final Log log = LogFactory.getLog( DefaultDeleteEventListener.class );
45
* Handle the given delete event.
47
* @param event The delete event to be handled.
49
* @throws HibernateException
51
public void onDelete(DeleteEvent event) throws HibernateException {
52
onDelete( event, new IdentitySet() );
56
* Handle the given delete event. This is the cascaded form.
58
* @param event The delete event.
59
* @param transientEntities The cache of entities already deleted
61
* @throws HibernateException
63
public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException {
65
final EventSource source = event.getSession();
67
final PersistenceContext persistenceContext = source.getPersistenceContext();
68
Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
70
EntityEntry entityEntry = persistenceContext.getEntry( entity );
71
final EntityPersister persister;
72
final Serializable id;
75
if ( entityEntry == null ) {
76
log.trace( "entity was not persistent in delete processing" );
78
persister = source.getEntityPersister( event.getEntityName(), entity );
80
if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
81
deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities );
86
performDetachedEntityDeletionCheck( event );
89
id = persister.getIdentifier( entity, source.getEntityMode() );
92
throw new TransientObjectException(
93
"the detached instance passed to delete() had a null identifier"
97
EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
99
persistenceContext.checkUniqueness( key, entity );
101
new OnUpdateVisitor( source, id, entity ).process( entity, persister );
103
version = persister.getVersion( entity, source.getEntityMode() );
105
entityEntry = persistenceContext.addEntity(
108
persister.getPropertyValues( entity, source.getEntityMode() ),
119
log.trace( "deleting a persistent instance" );
121
if ( entityEntry.getStatus() == Status.DELETED || entityEntry.getStatus() == Status.GONE ) {
122
log.trace( "object was already deleted" );
125
persister = entityEntry.getPersister();
126
id = entityEntry.getId();
127
version = entityEntry.getVersion();
130
/*if ( !persister.isMutable() ) {
131
throw new HibernateException(
132
"attempted to delete an object of immutable class: " +
133
MessageHelper.infoString(persister)
137
if ( invokeDeleteLifecycle( source, entity, persister ) ) {
141
deleteEntity( source, entity, entityEntry, event.isCascadeDeleteEnabled(), persister, transientEntities );
143
if ( source.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
144
persister.resetIdentifier( entity, id, version, source.getEntityMode() );
149
* Called when we have recognized an attempt to delete a detached entity.
151
* This is perfectly valid in Hibernate usage; JPA, however, forbids this.
152
* Thus, this is a hook for HEM to affect this behavior.
154
* @param event The event.
156
protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
157
// ok in normal Hibernate usage to delete a detached entity; JPA however
158
// forbids it, thus this is a hook for HEM to affect this behavior
162
* We encountered a delete request on a transient instance.
164
* This is a deviation from historical Hibernate (pre-3.2) behavior to
165
* align with the JPA spec, which states that transient entities can be
166
* passed to remove operation in which case cascades still need to be
169
* @param session The session which is the source of the event
170
* @param entity The entity being delete processed
171
* @param cascadeDeleteEnabled Is cascading of deletes enabled
172
* @param persister The entity persister
173
* @param transientEntities A cache of already visited transient entities
174
* (to avoid infinite recursion).
176
protected void deleteTransientEntity(
179
boolean cascadeDeleteEnabled,
180
EntityPersister persister,
181
Set transientEntities) {
182
log.info( "handling transient entity in delete processing" );
183
if ( transientEntities.contains( entity ) ) {
184
log.trace( "already handled transient entity; skipping" );
187
transientEntities.add( entity );
188
cascadeBeforeDelete( session, persister, entity, null, transientEntities );
189
cascadeAfterDelete( session, persister, entity, transientEntities );
193
* Perform the entity deletion. Well, as with most operations, does not
194
* really perform it; just schedules an action/execution with the
195
* {@link org.hibernate.engine.ActionQueue} for execution during flush.
197
* @param session The originating session
198
* @param entity The entity to delete
199
* @param entityEntry The entity's entry in the {@link PersistenceContext}
200
* @param isCascadeDeleteEnabled Is delete cascading enabled?
201
* @param persister The entity persister.
202
* @param transientEntities A cache of already deleted entities.
204
protected final void deleteEntity(
205
final EventSource session,
207
final EntityEntry entityEntry,
208
final boolean isCascadeDeleteEnabled,
209
final EntityPersister persister,
210
final Set transientEntities) {
212
if ( log.isTraceEnabled() ) {
215
MessageHelper.infoString( persister, entityEntry.getId(), session.getFactory() )
219
final PersistenceContext persistenceContext = session.getPersistenceContext();
220
final Type[] propTypes = persister.getPropertyTypes();
221
final Object version = entityEntry.getVersion();
223
final Object[] currentState;
224
if ( entityEntry.getLoadedState() == null ) { //ie. the entity came in from update()
225
currentState = persister.getPropertyValues( entity, session.getEntityMode() );
228
currentState = entityEntry.getLoadedState();
231
final Object[] deletedState = createDeletedState( persister, currentState, session );
232
entityEntry.setDeletedState( deletedState );
234
session.getInterceptor().onDelete(
238
persister.getPropertyNames(),
242
// before any callbacks, etc, so subdeletions see that this deletion happened first
243
persistenceContext.setEntryStatus( entityEntry, Status.DELETED );
244
EntityKey key = new EntityKey( entityEntry.getId(), persister, session.getEntityMode() );
246
cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );
248
new ForeignKeys.Nullifier( entity, true, false, session )
249
.nullifyTransientReferences( entityEntry.getDeletedState(), propTypes );
250
new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, true );
251
persistenceContext.getNullifiableEntityKeys().add( key );
253
// Ensures that containing deletions happen before sub-deletions
254
session.getActionQueue().addAction(
255
new EntityDeleteAction(
261
isCascadeDeleteEnabled,
266
cascadeAfterDelete( session, persister, entity, transientEntities );
268
// the entry will be removed after the flush, and will no longer
269
// override the stale snapshot
270
// This is now handled by removeEntity() in EntityDeleteAction
271
//persistenceContext.removeDatabaseSnapshot(key);
274
private Object[] createDeletedState(EntityPersister persister, Object[] currentState, EventSource session) {
275
Type[] propTypes = persister.getPropertyTypes();
276
final Object[] deletedState = new Object[propTypes.length];
277
// TypeFactory.deepCopy( currentState, propTypes, persister.getPropertyUpdateability(), deletedState, session );
278
boolean[] copyability = new boolean[propTypes.length];
279
java.util.Arrays.fill( copyability, true );
280
TypeFactory.deepCopy( currentState, propTypes, copyability, deletedState, session );
284
protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) {
285
if ( persister.implementsLifecycle( session.getEntityMode() ) ) {
286
log.debug( "calling onDelete()" );
287
if ( ( ( Lifecycle ) entity ).onDelete( session ) ) {
288
log.debug( "deletion vetoed by onDelete()" );
295
protected void cascadeBeforeDelete(
297
EntityPersister persister,
299
EntityEntry entityEntry,
300
Set transientEntities) throws HibernateException {
302
CacheMode cacheMode = session.getCacheMode();
303
session.setCacheMode( CacheMode.GET );
304
session.getPersistenceContext().incrementCascadeLevel();
306
// cascade-delete to collections BEFORE the collection owner is deleted
307
new Cascade( CascadingAction.DELETE, Cascade.AFTER_INSERT_BEFORE_DELETE, session )
308
.cascade( persister, entity, transientEntities );
311
session.getPersistenceContext().decrementCascadeLevel();
312
session.setCacheMode( cacheMode );
316
protected void cascadeAfterDelete(
318
EntityPersister persister,
320
Set transientEntities) throws HibernateException {
322
CacheMode cacheMode = session.getCacheMode();
323
session.setCacheMode( CacheMode.GET );
324
session.getPersistenceContext().incrementCascadeLevel();
326
// cascade-delete to many-to-one AFTER the parent was deleted
327
new Cascade( CascadingAction.DELETE, Cascade.BEFORE_INSERT_AFTER_DELETE, session )
328
.cascade( persister, entity, transientEntities );
331
session.getPersistenceContext().decrementCascadeLevel();
332
session.setCacheMode( cacheMode );