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.engine;
26
27 import java.util.Collection;
28 import java.util.HashSet;
29 import java.util.Iterator;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.hibernate.EntityMode;
34 import org.hibernate.HibernateException;
35 import org.hibernate.collection.PersistentCollection;
36 import org.hibernate.event.EventSource;
37 import org.hibernate.persister.collection.CollectionPersister;
38 import org.hibernate.persister.entity.EntityPersister;
39 import org.hibernate.type.AbstractComponentType;
40 import org.hibernate.type.AssociationType;
41 import org.hibernate.type.CollectionType;
42 import org.hibernate.type.EntityType;
43 import org.hibernate.type.Type;
44 import org.hibernate.util.CollectionHelper;
45
46 /**
47 * Delegate responsible for, in conjunction with the various
48 * {@link CascadingAction actions}, implementing cascade processing.
49 *
50 * @author Gavin King
51 * @see CascadingAction
52 */
53 public final class Cascade {
54
55 /**
56 * A cascade point that occurs just after the insertion of the parent entity and
57 * just before deletion
58 */
59 public static final int AFTER_INSERT_BEFORE_DELETE = 1;
60 /**
61 * A cascade point that occurs just before the insertion of the parent entity and
62 * just after deletion
63 */
64 public static final int BEFORE_INSERT_AFTER_DELETE = 2;
65 /**
66 * A cascade point that occurs just after the insertion of the parent entity and
67 * just before deletion, inside a collection
68 */
69 public static final int AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION = 3;
70 /**
71 * A cascade point that occurs just after update of the parent entity
72 */
73 public static final int AFTER_UPDATE = 0;
74 /**
75 * A cascade point that occurs just before the session is flushed
76 */
77 public static final int BEFORE_FLUSH = 0;
78 /**
79 * A cascade point that occurs just after eviction of the parent entity from the
80 * session cache
81 */
82 public static final int AFTER_EVICT = 0;
83 /**
84 * A cascade point that occurs just after locking a transient parent entity into the
85 * session cache
86 */
87 public static final int BEFORE_REFRESH = 0;
88 /**
89 * A cascade point that occurs just after refreshing a parent entity
90 */
91 public static final int AFTER_LOCK = 0;
92 /**
93 * A cascade point that occurs just before merging from a transient parent entity into
94 * the object in the session cache
95 */
96 public static final int BEFORE_MERGE = 0;
97
98
99 private static final Logger log = LoggerFactory.getLogger( Cascade.class );
100
101
102 private int cascadeTo;
103 private EventSource eventSource;
104 private CascadingAction action;
105
106 public Cascade(final CascadingAction action, final int cascadeTo, final EventSource eventSource) {
107 this.cascadeTo = cascadeTo;
108 this.eventSource = eventSource;
109 this.action = action;
110 }
111
112 /**
113 * Cascade an action from the parent entity instance to all its children.
114 *
115 * @param persister The parent's entity persister
116 * @param parent The parent reference.
117 * @throws HibernateException
118 */
119 public void cascade(final EntityPersister persister, final Object parent)
120 throws HibernateException {
121 cascade( persister, parent, null );
122 }
123
124 /**
125 * Cascade an action from the parent entity instance to all its children. This
126 * form is typicaly called from within cascade actions.
127 *
128 * @param persister The parent's entity persister
129 * @param parent The parent reference.
130 * @param anything Anything ;) Typically some form of cascade-local cache
131 * which is specific to each CascadingAction type
132 * @throws HibernateException
133 */
134 public void cascade(final EntityPersister persister, final Object parent, final Object anything)
135 throws HibernateException {
136
137 if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt
138 if ( log.isTraceEnabled() ) {
139 log.trace( "processing cascade " + action + " for: " + persister.getEntityName() );
140 }
141
142 Type[] types = persister.getPropertyTypes();
143 CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles();
144 EntityMode entityMode = eventSource.getEntityMode();
145 boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent, entityMode );
146 for ( int i=0; i<types.length; i++) {
147 CascadeStyle style = cascadeStyles[i];
148 if ( hasUninitializedLazyProperties && persister.getPropertyLaziness()[i] && ! action.performOnLazyProperty() ) {
149 //do nothing to avoid a lazy property initialization
150 continue;
151 }
152
153 if ( style.doCascade( action ) ) {
154 cascadeProperty(
155 persister.getPropertyValue( parent, i, entityMode ),
156 types[i],
157 style,
158 anything,
159 false
160 );
161 }
162 else if ( action.requiresNoCascadeChecking() ) {
163 action.noCascade(
164 eventSource,
165 persister.getPropertyValue( parent, i, entityMode ),
166 parent,
167 persister,
168 i
169 );
170 }
171 }
172
173 if ( log.isTraceEnabled() ) {
174 log.trace( "done processing cascade " + action + " for: " + persister.getEntityName() );
175 }
176 }
177 }
178
179 /**
180 * Cascade an action to the child or children
181 */
182 private void cascadeProperty(
183 final Object child,
184 final Type type,
185 final CascadeStyle style,
186 final Object anything,
187 final boolean isCascadeDeleteEnabled) throws HibernateException {
188
189 if (child!=null) {
190 if ( type.isAssociationType() ) {
191 AssociationType associationType = (AssociationType) type;
192 if ( cascadeAssociationNow( associationType ) ) {
193 cascadeAssociation(
194 child,
195 type,
196 style,
197 anything,
198 isCascadeDeleteEnabled
199 );
200 }
201 }
202 else if ( type.isComponentType() ) {
203 cascadeComponent( child, (AbstractComponentType) type, anything );
204 }
205 }
206 }
207
208 private boolean cascadeAssociationNow(AssociationType associationType) {
209 return associationType.getForeignKeyDirection().cascadeNow(cascadeTo) &&
210 ( eventSource.getEntityMode()!=EntityMode.DOM4J || associationType.isEmbeddedInXML() );
211 }
212
213 private void cascadeComponent(
214 final Object child,
215 final AbstractComponentType componentType,
216 final Object anything) {
217 Object[] children = componentType.getPropertyValues(child, eventSource);
218 Type[] types = componentType.getSubtypes();
219 for ( int i=0; i<types.length; i++ ) {
220 CascadeStyle componentPropertyStyle = componentType.getCascadeStyle(i);
221 if ( componentPropertyStyle.doCascade(action) ) {
222 cascadeProperty(
223 children[i],
224 types[i],
225 componentPropertyStyle,
226 anything,
227 false
228 );
229 }
230 }
231 }
232
233 private void cascadeAssociation(
234 final Object child,
235 final Type type,
236 final CascadeStyle style,
237 final Object anything,
238 final boolean isCascadeDeleteEnabled) {
239 if ( type.isEntityType() || type.isAnyType() ) {
240 cascadeToOne( child, type, style, anything, isCascadeDeleteEnabled );
241 }
242 else if ( type.isCollectionType() ) {
243 cascadeCollection( child, style, anything, (CollectionType) type );
244 }
245 }
246
247 /**
248 * Cascade an action to a collection
249 */
250 private void cascadeCollection(
251 final Object child,
252 final CascadeStyle style,
253 final Object anything,
254 final CollectionType type) {
255 CollectionPersister persister = eventSource.getFactory()
256 .getCollectionPersister( type.getRole() );
257 Type elemType = persister.getElementType();
258
259 final int oldCascadeTo = cascadeTo;
260 if ( cascadeTo==AFTER_INSERT_BEFORE_DELETE) {
261 cascadeTo = AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION;
262 }
263
264 //cascade to current collection elements
265 if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) {
266 cascadeCollectionElements(
267 child,
268 type,
269 style,
270 elemType,
271 anything,
272 persister.isCascadeDeleteEnabled()
273 );
274 }
275
276 cascadeTo = oldCascadeTo;
277 }
278
279 /**
280 * Cascade an action to a to-one association or any type
281 */
282 private void cascadeToOne(
283 final Object child,
284 final Type type,
285 final CascadeStyle style,
286 final Object anything,
287 final boolean isCascadeDeleteEnabled) {
288 final String entityName = type.isEntityType()
289 ? ( (EntityType) type ).getAssociatedEntityName()
290 : null;
291 if ( style.reallyDoCascade(action) ) { //not really necessary, but good for consistency...
292 action.cascade(eventSource, child, entityName, anything, isCascadeDeleteEnabled);
293 }
294 }
295
296 /**
297 * Cascade to the collection elements
298 */
299 private void cascadeCollectionElements(
300 final Object child,
301 final CollectionType collectionType,
302 final CascadeStyle style,
303 final Type elemType,
304 final Object anything,
305 final boolean isCascadeDeleteEnabled) throws HibernateException {
306 // we can't cascade to non-embedded elements
307 boolean embeddedElements = eventSource.getEntityMode()!=EntityMode.DOM4J ||
308 ( (EntityType) collectionType.getElementType( eventSource.getFactory() ) ).isEmbeddedInXML();
309
310 boolean reallyDoCascade = style.reallyDoCascade(action) &&
311 embeddedElements && child!=CollectionType.UNFETCHED_COLLECTION;
312
313 if ( reallyDoCascade ) {
314 if ( log.isTraceEnabled() ) {
315 log.trace( "cascade " + action + " for collection: " + collectionType.getRole() );
316 }
317
318 Iterator iter = action.getCascadableChildrenIterator(eventSource, collectionType, child);
319 while ( iter.hasNext() ) {
320 cascadeProperty(
321 iter.next(),
322 elemType,
323 style,
324 anything,
325 isCascadeDeleteEnabled
326 );
327 }
328
329 if ( log.isTraceEnabled() ) {
330 log.trace( "done cascade " + action + " for collection: " + collectionType.getRole() );
331 }
332 }
333
334 final boolean deleteOrphans = style.hasOrphanDelete() &&
335 action.deleteOrphans() &&
336 elemType.isEntityType() &&
337 child instanceof PersistentCollection; //a newly instantiated collection can't have orphans
338
339 if ( deleteOrphans ) { // handle orphaned entities!!
340 if ( log.isTraceEnabled() ) {
341 log.trace( "deleting orphans for collection: " + collectionType.getRole() );
342 }
343
344 // we can do the cast since orphan-delete does not apply to:
345 // 1. newly instantiated collections
346 // 2. arrays (we can't track orphans for detached arrays)
347 final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() );
348 deleteOrphans( entityName, (PersistentCollection) child );
349
350 if ( log.isTraceEnabled() ) {
351 log.trace( "done deleting orphans for collection: " + collectionType.getRole() );
352 }
353 }
354 }
355
356 /**
357 * Delete any entities that were removed from the collection
358 */
359 private void deleteOrphans(String entityName, PersistentCollection pc) throws HibernateException {
360 //TODO: suck this logic into the collection!
361 final Collection orphans;
362 if ( pc.wasInitialized() ) {
363 CollectionEntry ce = eventSource.getPersistenceContext().getCollectionEntry(pc);
364 orphans = ce==null ?
365 CollectionHelper.EMPTY_COLLECTION :
366 ce.getOrphans(entityName, pc);
367 }
368 else {
369 orphans = pc.getQueuedOrphans(entityName);
370 }
371
372 final Iterator orphanIter = orphans.iterator();
373 while ( orphanIter.hasNext() ) {
374 Object orphan = orphanIter.next();
375 if (orphan!=null) {
376 if ( log.isTraceEnabled() ) {
377 log.trace("deleting orphaned entity instance: " + entityName);
378 }
379 eventSource.delete( entityName, orphan, false, new HashSet() );
380 }
381 }
382 }
383
384 }