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.Map;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import org.hibernate.LockMode;
34 import org.hibernate.NonUniqueObjectException;
35 import org.hibernate.action.EntityIdentityInsertAction;
36 import org.hibernate.action.EntityInsertAction;
37 import org.hibernate.classic.Lifecycle;
38 import org.hibernate.classic.Validatable;
39 import org.hibernate.engine.Cascade;
40 import org.hibernate.engine.CascadingAction;
41 import org.hibernate.engine.EntityEntry;
42 import org.hibernate.engine.EntityKey;
43 import org.hibernate.engine.ForeignKeys;
44 import org.hibernate.engine.Nullability;
45 import org.hibernate.engine.SessionImplementor;
46 import org.hibernate.engine.Status;
47 import org.hibernate.engine.Versioning;
48 import org.hibernate.event.EventSource;
49 import org.hibernate.id.IdentifierGenerationException;
50 import org.hibernate.id.IdentifierGeneratorFactory;
51 import org.hibernate.intercept.FieldInterceptionHelper;
52 import org.hibernate.intercept.FieldInterceptor;
53 import org.hibernate.persister.entity.EntityPersister;
54 import org.hibernate.pretty.MessageHelper;
55 import org.hibernate.type.Type;
56 import org.hibernate.type.TypeFactory;
57
58 /**
59 * A convenience bas class for listeners responding to save events.
60 *
61 * @author Steve Ebersole.
62 */
63 public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener {
64
65 protected static final int PERSISTENT = 0;
66 protected static final int TRANSIENT = 1;
67 protected static final int DETACHED = 2;
68 protected static final int DELETED = 3;
69
70 private static final Logger log = LoggerFactory.getLogger( AbstractSaveEventListener.class );
71
72 /**
73 * Prepares the save call using the given requested id.
74 *
75 * @param entity The entity to be saved.
76 * @param requestedId The id to which to associate the entity.
77 * @param entityName The name of the entity being saved.
78 * @param anything Generally cascade-specific information.
79 * @param source The session which is the source of this save event.
80 *
81 * @return The id used to save the entity.
82 */
83 protected Serializable saveWithRequestedId(
84 Object entity,
85 Serializable requestedId,
86 String entityName,
87 Object anything,
88 EventSource source) {
89 return performSave(
90 entity,
91 requestedId,
92 source.getEntityPersister( entityName, entity ),
93 false,
94 anything,
95 source,
96 true
97 );
98 }
99
100 /**
101 * Prepares the save call using a newly generated id.
102 *
103 * @param entity The entity to be saved
104 * @param entityName The entity-name for the entity to be saved
105 * @param anything Generally cascade-specific information.
106 * @param source The session which is the source of this save event.
107 * @param requiresImmediateIdAccess does the event context require
108 * access to the identifier immediately after execution of this method (if
109 * not, post-insert style id generators may be postponed if we are outside
110 * a transaction).
111 *
112 * @return The id used to save the entity; may be null depending on the
113 * type of id generator used and the requiresImmediateIdAccess value
114 */
115 protected Serializable saveWithGeneratedId(
116 Object entity,
117 String entityName,
118 Object anything,
119 EventSource source,
120 boolean requiresImmediateIdAccess) {
121 EntityPersister persister = source.getEntityPersister( entityName, entity );
122 Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity );
123 if ( generatedId == null ) {
124 throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() );
125 }
126 else if ( generatedId == IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR ) {
127 return source.getIdentifier( entity );
128 }
129 else if ( generatedId == IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
130 return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
131 }
132 else {
133
134 if ( log.isDebugEnabled() ) {
135 log.debug(
136 "generated identifier: " +
137 persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ) +
138 ", using strategy: " +
139 persister.getIdentifierGenerator().getClass().getName()
140 //TODO: define toString()s for generators
141 );
142 }
143
144 return performSave( entity, generatedId, persister, false, anything, source, true );
145 }
146 }
147
148 /**
149 * Ppepares the save call by checking the session caches for a pre-existing
150 * entity and performing any lifecycle callbacks.
151 *
152 * @param entity The entity to be saved.
153 * @param id The id by which to save the entity.
154 * @param persister The entity's persister instance.
155 * @param useIdentityColumn Is an identity column being used?
156 * @param anything Generally cascade-specific information.
157 * @param source The session from which the event originated.
158 * @param requiresImmediateIdAccess does the event context require
159 * access to the identifier immediately after execution of this method (if
160 * not, post-insert style id generators may be postponed if we are outside
161 * a transaction).
162 *
163 * @return The id used to save the entity; may be null depending on the
164 * type of id generator used and the requiresImmediateIdAccess value
165 */
166 protected Serializable performSave(
167 Object entity,
168 Serializable id,
169 EntityPersister persister,
170 boolean useIdentityColumn,
171 Object anything,
172 EventSource source,
173 boolean requiresImmediateIdAccess) {
174
175 if ( log.isTraceEnabled() ) {
176 log.trace(
177 "saving " +
178 MessageHelper.infoString( persister, id, source.getFactory() )
179 );
180 }
181
182 EntityKey key;
183 if ( !useIdentityColumn ) {
184 key = new EntityKey( id, persister, source.getEntityMode() );
185 Object old = source.getPersistenceContext().getEntity( key );
186 if ( old != null ) {
187 if ( source.getPersistenceContext().getEntry( old ).getStatus() == Status.DELETED ) {
188 source.forceFlush( source.getPersistenceContext().getEntry( old ) );
189 }
190 else {
191 throw new NonUniqueObjectException( id, persister.getEntityName() );
192 }
193 }
194 persister.setIdentifier( entity, id, source.getEntityMode() );
195 }
196 else {
197 key = null;
198 }
199
200 if ( invokeSaveLifecycle( entity, persister, source ) ) {
201 return id; //EARLY EXIT
202 }
203
204 return performSaveOrReplicate(
205 entity,
206 key,
207 persister,
208 useIdentityColumn,
209 anything,
210 source,
211 requiresImmediateIdAccess
212 );
213 }
214
215 protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, EventSource source) {
216 // Sub-insertions should occur before containing insertion so
217 // Try to do the callback now
218 if ( persister.implementsLifecycle( source.getEntityMode() ) ) {
219 log.debug( "calling onSave()" );
220 if ( ( ( Lifecycle ) entity ).onSave( source ) ) {
221 log.debug( "insertion vetoed by onSave()" );
222 return true;
223 }
224 }
225 return false;
226 }
227
228 protected void validate(Object entity, EntityPersister persister, EventSource source) {
229 if ( persister.implementsValidatable( source.getEntityMode() ) ) {
230 ( ( Validatable ) entity ).validate();
231 }
232 }
233
234 /**
235 * Performs all the actual work needed to save an entity (well to get the save moved to
236 * the execution queue).
237 *
238 * @param entity The entity to be saved
239 * @param key The id to be used for saving the entity (or null, in the case of identity columns)
240 * @param persister The entity's persister instance.
241 * @param useIdentityColumn Should an identity column be used for id generation?
242 * @param anything Generally cascade-specific information.
243 * @param source The session which is the source of the current event.
244 * @param requiresImmediateIdAccess Is access to the identifier required immediately
245 * after the completion of the save? persist(), for example, does not require this...
246 *
247 * @return The id used to save the entity; may be null depending on the
248 * type of id generator used and the requiresImmediateIdAccess value
249 */
250 protected Serializable performSaveOrReplicate(
251 Object entity,
252 EntityKey key,
253 EntityPersister persister,
254 boolean useIdentityColumn,
255 Object anything,
256 EventSource source,
257 boolean requiresImmediateIdAccess) {
258
259 validate( entity, persister, source );
260
261 Serializable id = key == null ? null : key.getIdentifier();
262
263 boolean inTxn = source.getJDBCContext().isTransactionInProgress();
264 boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;
265
266 if ( useIdentityColumn && !shouldDelayIdentityInserts ) {
267 log.trace( "executing insertions" );
268 source.getActionQueue().executeInserts();
269 }
270
271 // Put a placeholder in entries, so we don't recurse back and try to save() the
272 // same object again. QUESTION: should this be done before onSave() is called?
273 // likewise, should it be done before onUpdate()?
274 source.getPersistenceContext().addEntry(
275 entity,
276 Status.SAVING,
277 null,
278 null,
279 id,
280 null,
281 LockMode.WRITE,
282 useIdentityColumn,
283 persister,
284 false,
285 false
286 );
287
288 cascadeBeforeSave( source, persister, entity, anything );
289
290 Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( anything ), source );
291 Type[] types = persister.getPropertyTypes();
292
293 boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );
294
295 if ( persister.hasCollections() ) {
296 substitute = substitute || visitCollectionsBeforeSave( entity, id, values, types, source );
297 }
298
299 if ( substitute ) {
300 persister.setPropertyValues( entity, values, source.getEntityMode() );
301 }
302
303 TypeFactory.deepCopy(
304 values,
305 types,
306 persister.getPropertyUpdateability(),
307 values,
308 source
309 );
310
311 new ForeignKeys.Nullifier( entity, false, useIdentityColumn, source )
312 .nullifyTransientReferences( values, types );
313 new Nullability( source ).checkNullability( values, persister, false );
314
315 if ( useIdentityColumn ) {
316 EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
317 values, entity, persister, source, shouldDelayIdentityInserts
318 );
319 if ( !shouldDelayIdentityInserts ) {
320 log.debug( "executing identity-insert immediately" );
321 source.getActionQueue().execute( insert );
322 id = insert.getGeneratedId();
323 //now done in EntityIdentityInsertAction
324 //persister.setIdentifier( entity, id, source.getEntityMode() );
325 key = new EntityKey( id, persister, source.getEntityMode() );
326 source.getPersistenceContext().checkUniqueness( key, entity );
327 //source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed
328 }
329 else {
330 log.debug( "delaying identity-insert due to no transaction in progress" );
331 source.getActionQueue().addAction( insert );
332 key = insert.getDelayedEntityKey();
333 }
334 }
335
336 Object version = Versioning.getVersion( values, persister );
337 source.getPersistenceContext().addEntity(
338 entity,
339 Status.MANAGED,
340 values,
341 key,
342 version,
343 LockMode.WRITE,
344 useIdentityColumn,
345 persister,
346 isVersionIncrementDisabled(),
347 false
348 );
349 //source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) );
350
351 if ( !useIdentityColumn ) {
352 source.getActionQueue().addAction(
353 new EntityInsertAction( id, values, entity, version, persister, source )
354 );
355 }
356
357 cascadeAfterSave( source, persister, entity, anything );
358
359 markInterceptorDirty( entity, persister, source );
360
361 return id;
362 }
363
364 private void markInterceptorDirty(Object entity, EntityPersister persister, EventSource source) {
365 if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
366 FieldInterceptor interceptor = FieldInterceptionHelper.injectFieldInterceptor(
367 entity,
368 persister.getEntityName(),
369 null,
370 source
371 );
372 interceptor.dirty();
373 }
374 }
375
376 protected Map getMergeMap(Object anything) {
377 return null;
378 }
379
380 /**
381 * After the save, will te version number be incremented
382 * if the instance is modified?
383 *
384 * @return True if the version will be incremented on an entity change after save;
385 * false otherwise.
386 */
387 protected boolean isVersionIncrementDisabled() {
388 return false;
389 }
390
391 protected boolean visitCollectionsBeforeSave(Object entity, Serializable id, Object[] values, Type[] types, EventSource source) {
392 WrapVisitor visitor = new WrapVisitor( source );
393 // substitutes into values by side-effect
394 visitor.processEntityPropertyValues( values, types );
395 return visitor.isSubstitutionRequired();
396 }
397
398 /**
399 * Perform any property value substitution that is necessary
400 * (interceptor callback, version initialization...)
401 *
402 * @param entity The entity
403 * @param id The entity identifier
404 * @param values The snapshot entity state
405 * @param persister The entity persister
406 * @param source The originating session
407 *
408 * @return True if the snapshot state changed such that
409 * reinjection of the values into the entity is required.
410 */
411 protected boolean substituteValuesIfNecessary(
412 Object entity,
413 Serializable id,
414 Object[] values,
415 EntityPersister persister,
416 SessionImplementor source) {
417 boolean substitute = source.getInterceptor().onSave(
418 entity,
419 id,
420 values,
421 persister.getPropertyNames(),
422 persister.getPropertyTypes()
423 );
424
425 //keep the existing version number in the case of replicate!
426 if ( persister.isVersioned() ) {
427 substitute = Versioning.seedVersion(
428 values,
429 persister.getVersionProperty(),
430 persister.getVersionType(),
431 source
432 ) || substitute;
433 }
434 return substitute;
435 }
436
437 /**
438 * Handles the calls needed to perform pre-save cascades for the given entity.
439 *
440 * @param source The session from whcih the save event originated.
441 * @param persister The entity's persister instance.
442 * @param entity The entity to be saved.
443 * @param anything Generally cascade-specific data
444 */
445 protected void cascadeBeforeSave(
446 EventSource source,
447 EntityPersister persister,
448 Object entity,
449 Object anything) {
450
451 // cascade-save to many-to-one BEFORE the parent is saved
452 source.getPersistenceContext().incrementCascadeLevel();
453 try {
454 new Cascade( getCascadeAction(), Cascade.BEFORE_INSERT_AFTER_DELETE, source )
455 .cascade( persister, entity, anything );
456 }
457 finally {
458 source.getPersistenceContext().decrementCascadeLevel();
459 }
460 }
461
462 /**
463 * Handles to calls needed to perform post-save cascades.
464 *
465 * @param source The session from which the event originated.
466 * @param persister The entity's persister instance.
467 * @param entity The entity beng saved.
468 * @param anything Generally cascade-specific data
469 */
470 protected void cascadeAfterSave(
471 EventSource source,
472 EntityPersister persister,
473 Object entity,
474 Object anything) {
475
476 // cascade-save to collections AFTER the collection owner was saved
477 source.getPersistenceContext().incrementCascadeLevel();
478 try {
479 new Cascade( getCascadeAction(), Cascade.AFTER_INSERT_BEFORE_DELETE, source )
480 .cascade( persister, entity, anything );
481 }
482 finally {
483 source.getPersistenceContext().decrementCascadeLevel();
484 }
485 }
486
487 protected abstract CascadingAction getCascadeAction();
488
489 /**
490 * Determine whether the entity is persistent, detached, or transient
491 *
492 * @param entity The entity to check
493 * @param entityName The name of the entity
494 * @param entry The entity's entry in the persistence context
495 * @param source The originating session.
496 *
497 * @return The state.
498 */
499 protected int getEntityState(
500 Object entity,
501 String entityName,
502 EntityEntry entry, //pass this as an argument only to avoid double looking
503 SessionImplementor source) {
504
505 if ( entry != null ) { // the object is persistent
506
507 //the entity is associated with the session, so check its status
508 if ( entry.getStatus() != Status.DELETED ) {
509 // do nothing for persistent instances
510 if ( log.isTraceEnabled() ) {
511 log.trace(
512 "persistent instance of: " +
513 getLoggableName( entityName, entity )
514 );
515 }
516 return PERSISTENT;
517 }
518 else {
519 //ie. e.status==DELETED
520 if ( log.isTraceEnabled() ) {
521 log.trace(
522 "deleted instance of: " +
523 getLoggableName( entityName, entity )
524 );
525 }
526 return DELETED;
527 }
528
529 }
530 else { // the object is transient or detached
531
532 //the entity is not associated with the session, so
533 //try interceptor and unsaved-value
534
535 if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source ) ) {
536 if ( log.isTraceEnabled() ) {
537 log.trace(
538 "transient instance of: " +
539 getLoggableName( entityName, entity )
540 );
541 }
542 return TRANSIENT;
543 }
544 else {
545 if ( log.isTraceEnabled() ) {
546 log.trace(
547 "detached instance of: " +
548 getLoggableName( entityName, entity )
549 );
550 }
551 return DETACHED;
552 }
553
554 }
555 }
556
557 protected String getLoggableName(String entityName, Object entity) {
558 return entityName == null ? entity.getClass().getName() : entityName;
559 }
560
561 protected Boolean getAssumedUnsaved() {
562 return null;
563 }
564
565 }