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.Set;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import org.hibernate.CacheMode;
34 import org.hibernate.HibernateException;
35 import org.hibernate.LockMode;
36 import org.hibernate.TransientObjectException;
37 import org.hibernate.util.IdentitySet;
38 import org.hibernate.action.EntityDeleteAction;
39 import org.hibernate.classic.Lifecycle;
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.ForeignKeys;
45 import org.hibernate.engine.Nullability;
46 import org.hibernate.engine.PersistenceContext;
47 import org.hibernate.engine.Status;
48 import org.hibernate.event.DeleteEvent;
49 import org.hibernate.event.DeleteEventListener;
50 import org.hibernate.event.EventSource;
51 import org.hibernate.persister.entity.EntityPersister;
52 import org.hibernate.pretty.MessageHelper;
53 import org.hibernate.type.Type;
54 import org.hibernate.type.TypeFactory;
55
56 /**
57 * Defines the default delete event listener used by hibernate for deleting entities
58 * from the datastore in response to generated delete events.
59 *
60 * @author Steve Ebersole
61 */
62 public class DefaultDeleteEventListener implements DeleteEventListener {
63
64 private static final Logger log = LoggerFactory.getLogger( DefaultDeleteEventListener.class );
65
66 /**
67 * Handle the given delete event.
68 *
69 * @param event The delete event to be handled.
70 *
71 * @throws HibernateException
72 */
73 public void onDelete(DeleteEvent event) throws HibernateException {
74 onDelete( event, new IdentitySet() );
75 }
76
77 /**
78 * Handle the given delete event. This is the cascaded form.
79 *
80 * @param event The delete event.
81 * @param transientEntities The cache of entities already deleted
82 *
83 * @throws HibernateException
84 */
85 public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException {
86
87 final EventSource source = event.getSession();
88
89 final PersistenceContext persistenceContext = source.getPersistenceContext();
90 Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
91
92 EntityEntry entityEntry = persistenceContext.getEntry( entity );
93 final EntityPersister persister;
94 final Serializable id;
95 final Object version;
96
97 if ( entityEntry == null ) {
98 log.trace( "entity was not persistent in delete processing" );
99
100 persister = source.getEntityPersister( event.getEntityName(), entity );
101
102 if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
103 deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities );
104 // EARLY EXIT!!!
105 return;
106 }
107 else {
108 performDetachedEntityDeletionCheck( event );
109 }
110
111 id = persister.getIdentifier( entity, source.getEntityMode() );
112
113 if ( id == null ) {
114 throw new TransientObjectException(
115 "the detached instance passed to delete() had a null identifier"
116 );
117 }
118
119 EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
120
121 persistenceContext.checkUniqueness( key, entity );
122
123 new OnUpdateVisitor( source, id, entity ).process( entity, persister );
124
125 version = persister.getVersion( entity, source.getEntityMode() );
126
127 entityEntry = persistenceContext.addEntity(
128 entity,
129 Status.MANAGED,
130 persister.getPropertyValues( entity, source.getEntityMode() ),
131 key,
132 version,
133 LockMode.NONE,
134 true,
135 persister,
136 false,
137 false
138 );
139 }
140 else {
141 log.trace( "deleting a persistent instance" );
142
143 if ( entityEntry.getStatus() == Status.DELETED || entityEntry.getStatus() == Status.GONE ) {
144 log.trace( "object was already deleted" );
145 return;
146 }
147 persister = entityEntry.getPersister();
148 id = entityEntry.getId();
149 version = entityEntry.getVersion();
150 }
151
152 /*if ( !persister.isMutable() ) {
153 throw new HibernateException(
154 "attempted to delete an object of immutable class: " +
155 MessageHelper.infoString(persister)
156 );
157 }*/
158
159 if ( invokeDeleteLifecycle( source, entity, persister ) ) {
160 return;
161 }
162
163 deleteEntity( source, entity, entityEntry, event.isCascadeDeleteEnabled(), persister, transientEntities );
164
165 if ( source.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
166 persister.resetIdentifier( entity, id, version, source.getEntityMode() );
167 }
168 }
169
170 /**
171 * Called when we have recognized an attempt to delete a detached entity.
172 * <p/>
173 * This is perfectly valid in Hibernate usage; JPA, however, forbids this.
174 * Thus, this is a hook for HEM to affect this behavior.
175 *
176 * @param event The event.
177 */
178 protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
179 // ok in normal Hibernate usage to delete a detached entity; JPA however
180 // forbids it, thus this is a hook for HEM to affect this behavior
181 }
182
183 /**
184 * We encountered a delete request on a transient instance.
185 * <p/>
186 * This is a deviation from historical Hibernate (pre-3.2) behavior to
187 * align with the JPA spec, which states that transient entities can be
188 * passed to remove operation in which case cascades still need to be
189 * performed.
190 *
191 * @param session The session which is the source of the event
192 * @param entity The entity being delete processed
193 * @param cascadeDeleteEnabled Is cascading of deletes enabled
194 * @param persister The entity persister
195 * @param transientEntities A cache of already visited transient entities
196 * (to avoid infinite recursion).
197 */
198 protected void deleteTransientEntity(
199 EventSource session,
200 Object entity,
201 boolean cascadeDeleteEnabled,
202 EntityPersister persister,
203 Set transientEntities) {
204 log.info( "handling transient entity in delete processing" );
205 if ( transientEntities.contains( entity ) ) {
206 log.trace( "already handled transient entity; skipping" );
207 return;
208 }
209 transientEntities.add( entity );
210 cascadeBeforeDelete( session, persister, entity, null, transientEntities );
211 cascadeAfterDelete( session, persister, entity, transientEntities );
212 }
213
214 /**
215 * Perform the entity deletion. Well, as with most operations, does not
216 * really perform it; just schedules an action/execution with the
217 * {@link org.hibernate.engine.ActionQueue} for execution during flush.
218 *
219 * @param session The originating session
220 * @param entity The entity to delete
221 * @param entityEntry The entity's entry in the {@link PersistenceContext}
222 * @param isCascadeDeleteEnabled Is delete cascading enabled?
223 * @param persister The entity persister.
224 * @param transientEntities A cache of already deleted entities.
225 */
226 protected final void deleteEntity(
227 final EventSource session,
228 final Object entity,
229 final EntityEntry entityEntry,
230 final boolean isCascadeDeleteEnabled,
231 final EntityPersister persister,
232 final Set transientEntities) {
233
234 if ( log.isTraceEnabled() ) {
235 log.trace(
236 "deleting " +
237 MessageHelper.infoString( persister, entityEntry.getId(), session.getFactory() )
238 );
239 }
240
241 final PersistenceContext persistenceContext = session.getPersistenceContext();
242 final Type[] propTypes = persister.getPropertyTypes();
243 final Object version = entityEntry.getVersion();
244
245 final Object[] currentState;
246 if ( entityEntry.getLoadedState() == null ) { //ie. the entity came in from update()
247 currentState = persister.getPropertyValues( entity, session.getEntityMode() );
248 }
249 else {
250 currentState = entityEntry.getLoadedState();
251 }
252
253 final Object[] deletedState = createDeletedState( persister, currentState, session );
254 entityEntry.setDeletedState( deletedState );
255
256 session.getInterceptor().onDelete(
257 entity,
258 entityEntry.getId(),
259 deletedState,
260 persister.getPropertyNames(),
261 propTypes
262 );
263
264 // before any callbacks, etc, so subdeletions see that this deletion happened first
265 persistenceContext.setEntryStatus( entityEntry, Status.DELETED );
266 EntityKey key = new EntityKey( entityEntry.getId(), persister, session.getEntityMode() );
267
268 cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );
269
270 new ForeignKeys.Nullifier( entity, true, false, session )
271 .nullifyTransientReferences( entityEntry.getDeletedState(), propTypes );
272 new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, true );
273 persistenceContext.getNullifiableEntityKeys().add( key );
274
275 // Ensures that containing deletions happen before sub-deletions
276 session.getActionQueue().addAction(
277 new EntityDeleteAction(
278 entityEntry.getId(),
279 deletedState,
280 version,
281 entity,
282 persister,
283 isCascadeDeleteEnabled,
284 session
285 )
286 );
287
288 cascadeAfterDelete( session, persister, entity, transientEntities );
289
290 // the entry will be removed after the flush, and will no longer
291 // override the stale snapshot
292 // This is now handled by removeEntity() in EntityDeleteAction
293 //persistenceContext.removeDatabaseSnapshot(key);
294 }
295
296 private Object[] createDeletedState(EntityPersister persister, Object[] currentState, EventSource session) {
297 Type[] propTypes = persister.getPropertyTypes();
298 final Object[] deletedState = new Object[propTypes.length];
299 // TypeFactory.deepCopy( currentState, propTypes, persister.getPropertyUpdateability(), deletedState, session );
300 boolean[] copyability = new boolean[propTypes.length];
301 java.util.Arrays.fill( copyability, true );
302 TypeFactory.deepCopy( currentState, propTypes, copyability, deletedState, session );
303 return deletedState;
304 }
305
306 protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) {
307 if ( persister.implementsLifecycle( session.getEntityMode() ) ) {
308 log.debug( "calling onDelete()" );
309 if ( ( ( Lifecycle ) entity ).onDelete( session ) ) {
310 log.debug( "deletion vetoed by onDelete()" );
311 return true;
312 }
313 }
314 return false;
315 }
316
317 protected void cascadeBeforeDelete(
318 EventSource session,
319 EntityPersister persister,
320 Object entity,
321 EntityEntry entityEntry,
322 Set transientEntities) throws HibernateException {
323
324 CacheMode cacheMode = session.getCacheMode();
325 session.setCacheMode( CacheMode.GET );
326 session.getPersistenceContext().incrementCascadeLevel();
327 try {
328 // cascade-delete to collections BEFORE the collection owner is deleted
329 new Cascade( CascadingAction.DELETE, Cascade.AFTER_INSERT_BEFORE_DELETE, session )
330 .cascade( persister, entity, transientEntities );
331 }
332 finally {
333 session.getPersistenceContext().decrementCascadeLevel();
334 session.setCacheMode( cacheMode );
335 }
336 }
337
338 protected void cascadeAfterDelete(
339 EventSource session,
340 EntityPersister persister,
341 Object entity,
342 Set transientEntities) throws HibernateException {
343
344 CacheMode cacheMode = session.getCacheMode();
345 session.setCacheMode( CacheMode.GET );
346 session.getPersistenceContext().incrementCascadeLevel();
347 try {
348 // cascade-delete to many-to-one AFTER the parent was deleted
349 new Cascade( CascadingAction.DELETE, Cascade.BEFORE_INSERT_AFTER_DELETE, session )
350 .cascade( persister, entity, transientEntities );
351 }
352 finally {
353 session.getPersistenceContext().decrementCascadeLevel();
354 session.setCacheMode( cacheMode );
355 }
356 }
357
358 }