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.Iterator;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.hibernate.HibernateException;
34 import org.hibernate.LockMode;
35 import org.hibernate.ReplicationMode;
36 import org.hibernate.TransientObjectException;
37 import org.hibernate.proxy.HibernateProxy;
38 import org.hibernate.persister.entity.EntityPersister;
39 import org.hibernate.collection.PersistentCollection;
40 import org.hibernate.event.EventSource;
41 import org.hibernate.type.CollectionType;
42 import org.hibernate.type.Type;
43 import org.hibernate.type.EntityType;
44
45 /**
46 * A session action that may be cascaded from parent entity to its children
47 *
48 * @author Gavin King
49 */
50 public abstract class CascadingAction {
51
52 private static final Logger log = LoggerFactory.getLogger( CascadingAction.class );
53
54
55 // the CascadingAction contract ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56
57 /**
58 * protected constructor
59 */
60 CascadingAction() {
61 }
62
63 /**
64 * Cascade the action to the child object.
65 *
66 * @param session The session within which the cascade is occuring.
67 * @param child The child to which cascading should be performed.
68 * @param entityName The child's entity name
69 * @param anything Anything ;) Typically some form of cascade-local cache
70 * which is specific to each CascadingAction type
71 * @param isCascadeDeleteEnabled Are cascading deletes enabled.
72 * @throws HibernateException
73 */
74 public abstract void cascade(
75 EventSource session,
76 Object child,
77 String entityName,
78 Object anything,
79 boolean isCascadeDeleteEnabled) throws HibernateException;
80
81 /**
82 * Given a collection, get an iterator of the children upon which the
83 * current cascading action should be visited.
84 *
85 * @param session The session within which the cascade is occuring.
86 * @param collectionType The mapping type of the collection.
87 * @param collection The collection instance.
88 * @return The children iterator.
89 */
90 public abstract Iterator getCascadableChildrenIterator(
91 EventSource session,
92 CollectionType collectionType,
93 Object collection);
94
95 /**
96 * Does this action potentially extrapolate to orphan deletes?
97 *
98 * @return True if this action can lead to deletions of orphans.
99 */
100 public abstract boolean deleteOrphans();
101
102
103 /**
104 * Does the specified cascading action require verification of no cascade validity?
105 *
106 * @return True if this action requires no-cascade verification; false otherwise.
107 */
108 public boolean requiresNoCascadeChecking() {
109 return false;
110 }
111
112 /**
113 * Called (in the case of {@link #requiresNoCascadeChecking} returning true) to validate
114 * that no cascade on the given property is considered a valid semantic.
115 *
116 * @param session The session witin which the cascade is occurring.
117 * @param child The property value
118 * @param parent The property value owner
119 * @param persister The entity persister for the owner
120 * @param propertyIndex The index of the property within the owner.
121 */
122 public void noCascade(EventSource session, Object child, Object parent, EntityPersister persister, int propertyIndex) {
123 }
124
125 /**
126 * Should this action be performed (or noCascade consulted) in the case of lazy properties.
127 */
128 public boolean performOnLazyProperty() {
129 return true;
130 }
131
132
133 // the CascadingAction implementations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134
135 /**
136 * @see org.hibernate.Session#delete(Object)
137 */
138 public static final CascadingAction DELETE = new CascadingAction() {
139 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
140 throws HibernateException {
141 if ( log.isTraceEnabled() ) {
142 log.trace("cascading to delete: " + entityName);
143 }
144 session.delete( entityName, child, isCascadeDeleteEnabled, ( Set ) anything );
145 }
146 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
147 // delete does cascade to uninitialized collections
148 return CascadingAction.getAllElementsIterator(session, collectionType, collection);
149 }
150 public boolean deleteOrphans() {
151 // orphans should be deleted during delete
152 return true;
153 }
154 public String toString() {
155 return "ACTION_DELETE";
156 }
157 };
158
159 /**
160 * @see org.hibernate.Session#lock(Object, LockMode)
161 */
162 public static final CascadingAction LOCK = new CascadingAction() {
163 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
164 throws HibernateException {
165 if ( log.isTraceEnabled() ) {
166 log.trace( "cascading to lock: " + entityName );
167 }
168 session.lock( entityName, child, LockMode.NONE/*(LockMode) anything*/ );
169 }
170 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
171 // lock doesn't cascade to uninitialized collections
172 return getLoadedElementsIterator(session, collectionType, collection);
173 }
174 public boolean deleteOrphans() {
175 //TODO: should orphans really be deleted during lock???
176 return false;
177 }
178 public String toString() {
179 return "ACTION_LOCK";
180 }
181 };
182
183 /**
184 * @see org.hibernate.Session#refresh(Object)
185 */
186 public static final CascadingAction REFRESH = new CascadingAction() {
187 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
188 throws HibernateException {
189 if ( log.isTraceEnabled() ) {
190 log.trace( "cascading to refresh: " + entityName );
191 }
192 session.refresh( child, (Map) anything );
193 }
194 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
195 // refresh doesn't cascade to uninitialized collections
196 return getLoadedElementsIterator(session, collectionType, collection);
197 }
198 public boolean deleteOrphans() {
199 return false;
200 }
201 public String toString() {
202 return "ACTION_REFRESH";
203 }
204 };
205
206 /**
207 * @see org.hibernate.Session#evict(Object)
208 */
209 public static final CascadingAction EVICT = new CascadingAction() {
210 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
211 throws HibernateException {
212 if ( log.isTraceEnabled() ) {
213 log.trace( "cascading to evict: " + entityName );
214 }
215 session.evict(child);
216 }
217 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
218 // evicts don't cascade to uninitialized collections
219 return getLoadedElementsIterator(session, collectionType, collection);
220 }
221 public boolean deleteOrphans() {
222 return false;
223 }
224 public boolean performOnLazyProperty() {
225 return false;
226 }
227 public String toString() {
228 return "ACTION_EVICT";
229 }
230 };
231
232 /**
233 * @see org.hibernate.Session#saveOrUpdate(Object)
234 */
235 public static final CascadingAction SAVE_UPDATE = new CascadingAction() {
236 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
237 throws HibernateException {
238 if ( log.isTraceEnabled() ) {
239 log.trace( "cascading to saveOrUpdate: " + entityName );
240 }
241 session.saveOrUpdate(entityName, child);
242 }
243 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
244 // saves / updates don't cascade to uninitialized collections
245 return getLoadedElementsIterator(session, collectionType, collection);
246 }
247 public boolean deleteOrphans() {
248 // orphans should be deleted during save/update
249 return true;
250 }
251 public boolean performOnLazyProperty() {
252 return false;
253 }
254 public String toString() {
255 return "ACTION_SAVE_UPDATE";
256 }
257 };
258
259 /**
260 * @see org.hibernate.Session#merge(Object)
261 */
262 public static final CascadingAction MERGE = new CascadingAction() {
263 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
264 throws HibernateException {
265 if ( log.isTraceEnabled() ) {
266 log.trace( "cascading to merge: " + entityName );
267 }
268 session.merge( entityName, child, (Map) anything );
269 }
270 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
271 // merges don't cascade to uninitialized collections
272 // //TODO: perhaps this does need to cascade after all....
273 return getLoadedElementsIterator(session, collectionType, collection);
274 }
275 public boolean deleteOrphans() {
276 // orphans should not be deleted during merge??
277 return false;
278 }
279 public String toString() {
280 return "ACTION_MERGE";
281 }
282 };
283
284 /**
285 * @see org.hibernate.classic.Session#saveOrUpdateCopy(Object)
286 */
287 public static final CascadingAction SAVE_UPDATE_COPY = new CascadingAction() {
288 // for deprecated saveOrUpdateCopy()
289 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
290 throws HibernateException {
291 if ( log.isTraceEnabled() ) {
292 log.trace( "cascading to saveOrUpdateCopy: " + entityName );
293 }
294 session.saveOrUpdateCopy( entityName, child, (Map) anything );
295 }
296 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
297 // saves / updates don't cascade to uninitialized collections
298 return getLoadedElementsIterator(session, collectionType, collection);
299 }
300 public boolean deleteOrphans() {
301 // orphans should not be deleted during copy??
302 return false;
303 }
304 public String toString() {
305 return "ACTION_SAVE_UPDATE_COPY";
306 }
307 };
308
309 /**
310 * @see org.hibernate.Session#persist(Object)
311 */
312 public static final CascadingAction PERSIST = new CascadingAction() {
313 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
314 throws HibernateException {
315 if ( log.isTraceEnabled() ) {
316 log.trace( "cascading to persist: " + entityName );
317 }
318 session.persist( entityName, child, (Map) anything );
319 }
320 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
321 // persists don't cascade to uninitialized collections
322 return CascadingAction.getAllElementsIterator(session, collectionType, collection);
323 }
324 public boolean deleteOrphans() {
325 return false;
326 }
327 public boolean performOnLazyProperty() {
328 return false;
329 }
330 public String toString() {
331 return "ACTION_PERSIST";
332 }
333 };
334
335 /**
336 * Execute persist during flush time
337 *
338 * @see org.hibernate.Session#persist(Object)
339 */
340 public static final CascadingAction PERSIST_ON_FLUSH = new CascadingAction() {
341 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
342 throws HibernateException {
343 if ( log.isTraceEnabled() ) {
344 log.trace( "cascading to persistOnFlush: " + entityName );
345 }
346 session.persistOnFlush( entityName, child, (Map) anything );
347 }
348 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
349 // persists don't cascade to uninitialized collections
350 return CascadingAction.getLoadedElementsIterator(session, collectionType, collection);
351 }
352 public boolean deleteOrphans() {
353 return true;
354 }
355 public boolean requiresNoCascadeChecking() {
356 return true;
357 }
358 public void noCascade(
359 EventSource session,
360 Object child,
361 Object parent,
362 EntityPersister persister,
363 int propertyIndex) {
364 if ( child == null ) {
365 return;
366 }
367 Type type = persister.getPropertyTypes()[propertyIndex];
368 if ( type.isEntityType() ) {
369 String childEntityName = ( ( EntityType ) type ).getAssociatedEntityName( session.getFactory() );
370
371 if ( ! isInManagedState( child, session )
372 && ! ( child instanceof HibernateProxy ) //a proxy cannot be transient and it breaks ForeignKeys.isTransient
373 && ForeignKeys.isTransient( childEntityName, child, null, session ) ) {
374 String parentEntiytName = persister.getEntityName();
375 String propertyName = persister.getPropertyNames()[propertyIndex];
376 throw new TransientObjectException(
377 "object references an unsaved transient instance - " +
378 "save the transient instance before flushing: " +
379 parentEntiytName + "." + propertyName + " -> " + childEntityName
380 );
381
382 }
383 }
384 }
385 public boolean performOnLazyProperty() {
386 return false;
387 }
388
389 private boolean isInManagedState(Object child, EventSource session) {
390 EntityEntry entry = session.getPersistenceContext().getEntry( child );
391 return entry != null && (entry.getStatus() == Status.MANAGED || entry.getStatus() == Status.READ_ONLY);
392 }
393
394 public String toString() {
395 return "ACTION_PERSIST_ON_FLUSH";
396 }
397 };
398
399 /**
400 * @see org.hibernate.Session#replicate(Object, org.hibernate.ReplicationMode)
401 */
402 public static final CascadingAction REPLICATE = new CascadingAction() {
403 public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled)
404 throws HibernateException {
405 if ( log.isTraceEnabled() ) {
406 log.trace( "cascading to replicate: " + entityName );
407 }
408 session.replicate( entityName, child, (ReplicationMode) anything );
409 }
410 public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) {
411 // replicate does cascade to uninitialized collections
412 return getLoadedElementsIterator(session, collectionType, collection);
413 }
414 public boolean deleteOrphans() {
415 return false; //I suppose?
416 }
417 public String toString() {
418 return "ACTION_REPLICATE";
419 }
420 };
421
422
423 // static helper methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
424
425 /**
426 * Given a collection, get an iterator of all its children, loading them
427 * from the database if necessary.
428 *
429 * @param session The session within which the cascade is occuring.
430 * @param collectionType The mapping type of the collection.
431 * @param collection The collection instance.
432 * @return The children iterator.
433 */
434 private static Iterator getAllElementsIterator(
435 EventSource session,
436 CollectionType collectionType,
437 Object collection) {
438 return collectionType.getElementsIterator( collection, session );
439 }
440
441 /**
442 * Iterate just the elements of the collection that are already there. Don't load
443 * any new elements from the database.
444 */
445 public static Iterator getLoadedElementsIterator(SessionImplementor session, CollectionType collectionType, Object collection) {
446 if ( collectionIsInitialized(collection) ) {
447 // handles arrays and newly instantiated collections
448 return collectionType.getElementsIterator(collection, session);
449 }
450 else {
451 // does not handle arrays (thats ok, cos they can't be lazy)
452 // or newly instantiated collections, so we can do the cast
453 return ( (PersistentCollection) collection ).queuedAdditionIterator();
454 }
455 }
456
457 private static boolean collectionIsInitialized(Object collection) {
458 return !(collection instanceof PersistentCollection) || ( (PersistentCollection) collection ).wasInitialized();
459 }
460
461 }