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