KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hibernate > event > def > DefaultFlushEntityEventListener


1 //$Id: DefaultFlushEntityEventListener.java,v 1.18 2005/07/20 20:01:32 epbernard Exp $
2
package org.hibernate.event.def;
3
4 import java.io.Serializable JavaDoc;
5
6 import org.apache.commons.logging.Log;
7 import org.apache.commons.logging.LogFactory;
8 import org.hibernate.AssertionFailure;
9 import org.hibernate.HibernateException;
10 import org.hibernate.StaleObjectStateException;
11 import org.hibernate.EntityMode;
12 import org.hibernate.action.EntityUpdateAction;
13 import org.hibernate.classic.Validatable;
14 import org.hibernate.engine.EntityEntry;
15 import org.hibernate.engine.EntityKey;
16 import org.hibernate.engine.Nullability;
17 import org.hibernate.engine.PersistenceContext;
18 import org.hibernate.engine.Status;
19 import org.hibernate.engine.Versioning;
20 import org.hibernate.event.EventSource;
21 import org.hibernate.event.FlushEntityEvent;
22 import org.hibernate.event.FlushEntityEventListener;
23 import org.hibernate.engine.SessionImplementor;
24 import org.hibernate.intercept.FieldInterceptor;
25 import org.hibernate.persister.entity.EntityPersister;
26 import org.hibernate.pretty.MessageHelper;
27 import org.hibernate.type.Type;
28 import org.hibernate.type.TypeFactory;
29 import org.hibernate.util.ArrayHelper;
30
31 /**
32  * An event that occurs for each entity instance at flush time
33  *
34  * @author Gavin King
35  */

36 public class DefaultFlushEntityEventListener
37     extends AbstractEventListener
38     implements FlushEntityEventListener {
39     
40     private static final Log log = LogFactory.getLog(DefaultFlushEntityEventListener.class);
41
42     /**
43      * make sure user didn't mangle the id
44      */

45     public void checkId(Object JavaDoc object, EntityPersister persister, Serializable JavaDoc id, EntityMode entityMode)
46     throws HibernateException {
47
48         if ( persister.hasIdentifierPropertyOrEmbeddedCompositeIdentifier() ) {
49
50             Serializable JavaDoc oid = persister.getIdentifier( object, entityMode );
51             if (id==null) {
52                 throw new AssertionFailure("null id in entry (don't flush the Session after an exception occurs)");
53             }
54             if ( !persister.getIdentifierType().isEqual(id, oid, entityMode) ) {
55                 throw new HibernateException(
56                         "identifier of an instance of " +
57                         persister.getEntityName() +
58                         " was altered from " + id +
59                         " to " + oid
60                     );
61             }
62         }
63
64     }
65     
66     private void checkNaturalId(EntityPersister persister, Object JavaDoc[] current, Object JavaDoc[] loaded, EntityMode entityMode) {
67         if ( persister.hasNaturalIdentifier() ) {
68             Type[] types = persister.getPropertyTypes();
69             int[] props = persister.getNaturalIdentifierProperties();
70             boolean[] updateable = persister.getPropertyUpdateability();
71             for ( int i=0; i<props.length; i++ ) {
72                 int prop = props[i];
73                 if ( !updateable[prop] ) {
74                     if ( !types[prop].isEqual( current[prop], loaded[prop], entityMode ) ) {
75                         throw new HibernateException(
76                                 "immutable natural identifier of an instance of " +
77                                 persister.getEntityName() +
78                                 " was altered"
79                             );
80                     }
81                 }
82             }
83         }
84     }
85
86     /**
87      * Flushes a single entity's state to the database, by scheduling
88      * an update action, if necessary
89      */

90     public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
91         final Object JavaDoc entity = event.getEntity();
92         final EntityEntry entry = event.getEntityEntry();
93         final EventSource session = event.getSession();
94         final EntityPersister persister = entry.getPersister();
95         final Status status = entry.getStatus();
96         final EntityMode entityMode = session.getEntityMode();
97         final Object JavaDoc[] loadedState = entry.getLoadedState();
98         final Type[] types = persister.getPropertyTypes();
99         
100         final boolean mightBeDirty = entry.requiresDirtyCheck(entity);
101
102         final Object JavaDoc[] values;
103         if ( status == Status.DELETED ) {
104             //grab its state saved at deletion
105
values = entry.getDeletedState();
106         }
107         else if ( !mightBeDirty && loadedState!=null ) {
108             values = loadedState;
109         }
110         else {
111             checkId( entity, persister, entry.getId(), entityMode );
112             
113             // grab its current state
114
values = persister.getPropertyValues( entity, entityMode );
115             
116             checkNaturalId( persister, values, loadedState, entityMode );
117         }
118         
119         event.setPropertyValues(values);
120
121         boolean substitute = false;
122
123         //TODO: avoid this for non-new instances where mightBeDirty==false
124
if ( persister.hasCollections() ) {
125
126             // wrap up any new collections directly referenced by the object
127
// or its components
128

129             // NOTE: we need to do the wrap here even if its not "dirty",
130
// because collections need wrapping but changes to _them_
131
// don't dirty the container. Also, for versioned data, we
132
// need to wrap before calling searchForDirtyCollections
133

134             WrapVisitor visitor = new WrapVisitor(session);
135             // substitutes into values by side-effect
136
visitor.processEntityPropertyValues(values, types);
137             substitute = visitor.isSubstitutionRequired();
138         }
139
140         if ( mightBeDirty || status==Status.DELETED ) {
141             // compare to cached state (ignoring collections unless versioned)
142
dirtyCheck(event);
143             if ( isUpdateNecessary(event) ) {
144                 substitute = scheduleUpdate( event, entity, entry, values, persister, session )
145                         || substitute;
146             }
147             else {
148                 FieldInterceptor.clearDirty(entity);
149             }
150         }
151
152         if ( status != Status.DELETED ) {
153             // now update the object .. has to be outside the main if block above (because of collections)
154
if (substitute) persister.setPropertyValues( entity, values, entityMode );
155
156             // Search for collections by reachability, updating their role.
157
// We don't want to touch collections reachable from a deleted object
158
if ( persister.hasCollections() ) {
159                 new FlushVisitor(session, entity).processEntityPropertyValues(values, types);
160             }
161         }
162
163     }
164
165     private boolean scheduleUpdate(
166             final FlushEntityEvent event,
167             final Object JavaDoc entity,
168             final EntityEntry entry,
169             final Object JavaDoc[] values,
170             final EntityPersister persister,
171             final EventSource session
172     ) {
173
174         final Status status = entry.getStatus();
175         final EntityMode entityMode = session.getEntityMode();
176         final Type[] types = persister.getPropertyTypes();
177         
178         if ( log.isTraceEnabled() ) {
179             if ( status == Status.DELETED ) {
180                 log.trace(
181                         "Updating deleted entity: " +
182                         MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
183                     );
184             }
185             else {
186                 log.trace(
187                         "Updating entity: " +
188                         MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
189                     );
190             }
191         }
192         
193         boolean substitute;
194         
195         if ( !entry.isBeingReplicated() ) {
196             // give the Interceptor a chance to process property values, if the properties
197
// were modified by the Interceptor, we need to set them back to the object
198
substitute = handleInterception(event);
199         }
200         else {
201             substitute = false;
202         }
203
204         validate( entity, persister, status, entityMode );
205
206         // increment the version number (if necessary)
207
final Object JavaDoc nextVersion = getNextVersion(event);
208
209         // get the updated snapshot by cloning current state
210
Object JavaDoc[] updatedState = null;
211         if ( status==Status.MANAGED ) {
212             updatedState = new Object JavaDoc[values.length];
213             TypeFactory.deepCopy(
214                     values,
215                     types,
216                     persister.getPropertyCheckability(),
217                     updatedState,
218                     session
219                 );
220         }
221
222         // if it was dirtied by a collection only
223
int[] dirtyProperties = event.getDirtyProperties();
224         if ( event.isDirtyCheckPossible() && dirtyProperties==null ) {
225             if ( !event.hasDirtyCollection() ) {
226                 throw new AssertionFailure("dirty, but no dirty properties");
227             }
228             dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;
229         }
230
231         // check nullability but do not perform command execute
232
// we'll use scheduled updates for that.
233
new Nullability(session).checkNullability( values, persister, true );
234
235         // schedule the update
236
// note that we intentionally do _not_ pass in currentPersistentState!
237
session.getActionQueue().addAction(
238                 new EntityUpdateAction(
239                         entry.getId(),
240                         values,
241                         dirtyProperties,
242                         event.hasDirtyCollection(),
243                         entry.getLoadedState(),
244                         entry.getVersion(),
245                         nextVersion,
246                         entity,
247                         updatedState,
248                         entry.getRowId(),
249                         persister,
250                         session
251                     )
252             );
253         
254         return substitute;
255     }
256
257     protected void validate(Object JavaDoc entity, EntityPersister persister, Status status, EntityMode entityMode) {
258         // validate() instances of Validatable
259
if ( status == Status.MANAGED && persister.implementsValidatable( entityMode ) ) {
260             ( (Validatable) entity ).validate();
261         }
262     }
263     
264     protected boolean handleInterception(FlushEntityEvent event) {
265         SessionImplementor session = event.getSession();
266         EntityEntry entry = event.getEntityEntry();
267         EntityPersister persister = entry.getPersister();
268         Object JavaDoc entity = event.getEntity();
269         
270         //give the Interceptor a chance to modify property values
271
final Object JavaDoc[] values = event.getPropertyValues();
272         final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister );
273
274         //now we might need to recalculate the dirtyProperties array
275
if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) {
276             int[] dirtyProperties;
277             if ( event.hasDatabaseSnapshot() ) {
278                 dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session );
279             }
280             else {
281                 dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session );
282             }
283             event.setDirtyProperties(dirtyProperties);
284         }
285         
286         return intercepted;
287     }
288
289     protected boolean invokeInterceptor(
290             SessionImplementor session, Object JavaDoc entity, EntityEntry entry, final Object JavaDoc[] values,
291             EntityPersister persister
292             ) {
293         final boolean intercepted = session.getInterceptor().onFlushDirty(
294                 entity,
295                 entry.getId(),
296                 values,
297                 entry.getLoadedState(),
298                 persister.getPropertyNames(),
299                 persister.getPropertyTypes()
300             );
301         return intercepted;
302     }
303
304     /**
305      * Convience method to retreive an entities next version value
306      */

307     private Object JavaDoc getNextVersion(FlushEntityEvent event) throws HibernateException {
308         
309         EntityEntry entry = event.getEntityEntry();
310         EntityPersister persister = entry.getPersister();
311         if ( persister.isVersioned() ) {
312
313             Object JavaDoc[] values = event.getPropertyValues();
314             
315             if ( entry.isBeingReplicated() ) {
316                 return Versioning.getVersion(values, persister);
317             }
318             else {
319                 int[] dirtyProperties = event.getDirtyProperties();
320                 
321                 final boolean isVersionIncrementRequired = entry.getStatus()!=Status.DELETED && (
322                         dirtyProperties==null ||
323                         Versioning.isVersionIncrementRequired(
324                                 dirtyProperties,
325                                 event.hasDirtyCollection(),
326                                 persister.getPropertyVersionability()
327                             )
328                     );
329                 
330                 final Object JavaDoc nextVersion = isVersionIncrementRequired ?
331                         Versioning.increment( entry.getVersion(), persister.getVersionType() ) :
332                         entry.getVersion(); //use the current version
333

334                 Versioning.setVersion(values, nextVersion, persister);
335                 
336                 return nextVersion;
337             }
338         }
339         else {
340             return null;
341         }
342         
343     }
344
345     /**
346      * Performs all necessary checking to determine if an entity needs an SQL update
347      * to synchronize its state to the database. Modifies the event by side-effect!
348      * Note: this method is quite slow, avoid calling if possible!
349      */

350     protected final boolean isUpdateNecessary(FlushEntityEvent event) throws HibernateException {
351
352         EntityPersister persister = event.getEntityEntry().getPersister();
353         Status status = event.getEntityEntry().getStatus();
354         
355         if ( !event.isDirtyCheckPossible() ) {
356             return true;
357         }
358         else {
359             
360             int[] dirtyProperties = event.getDirtyProperties();
361             if ( dirtyProperties!=null && dirtyProperties.length!=0 ) {
362                 return true; //TODO: suck into event class
363
}
364             else {
365                 boolean checkCollections = status==Status.MANAGED &&
366                         persister.isVersioned() &&
367                         persister.hasCollections();
368                 
369                 if ( checkCollections ) {
370                     DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor(
371                             event.getSession(),
372                             persister.getPropertyVersionability()
373                         );
374                     visitor.processEntityPropertyValues( event.getPropertyValues(), persister.getPropertyTypes() );
375                     boolean hasDirtyCollections = visitor.wasDirtyCollectionFound();
376                     event.setHasDirtyCollection(hasDirtyCollections);
377                     return hasDirtyCollections;
378                 }
379                 else {
380                     return false;
381                 }
382             }
383             
384         }
385     }
386     
387     /**
388      * Perform a dirty check, and attach the results to the event
389      */

390     protected void dirtyCheck(FlushEntityEvent event) throws HibernateException {
391         
392         final Object JavaDoc entity = event.getEntity();
393         final Object JavaDoc[] values = event.getPropertyValues();
394         final SessionImplementor session = event.getSession();
395         final EntityEntry entry = event.getEntityEntry();
396         final EntityPersister persister = entry.getPersister();
397         final Serializable JavaDoc id = entry.getId();
398         final Object JavaDoc[] loadedState = entry.getLoadedState();
399
400         int[] dirtyProperties = session.getInterceptor().findDirty(
401                 entity,
402                 id,
403                 values,
404                 loadedState,
405                 persister.getPropertyNames(),
406                 persister.getPropertyTypes()
407             );
408         
409         event.setDatabaseSnapshot(null);
410
411         final boolean interceptorHandledDirtyCheck;
412         boolean cannotDirtyCheck;
413         
414         if ( dirtyProperties==null ) {
415             // Interceptor returned null, so do the dirtycheck ourself, if possible
416
interceptorHandledDirtyCheck = false;
417             
418             cannotDirtyCheck = loadedState==null; // object loaded by update()
419
if ( !cannotDirtyCheck ) {
420                 // dirty check against the usual snapshot of the entity
421
dirtyProperties = persister.findDirty( values, loadedState, entity, session );
422                 
423             }
424             else {
425                 // dirty check against the database snapshot, if possible/necessary
426
final PersistenceContext persistenceContext = session.getPersistenceContext();
427                 Object JavaDoc[] databaseSnapshot;
428                 if ( persister.isSelectBeforeUpdateRequired() ) {
429                     databaseSnapshot = persistenceContext.getDatabaseSnapshot(id, persister);
430                 }
431                 else {
432                     //TODO: optimize away this lookup for entities w/o unsaved-value="undefined"
433
EntityKey entityKey = new EntityKey( id, persister, session.getEntityMode() );
434                     databaseSnapshot = persistenceContext.getCachedDatabaseSnapshot( entityKey );
435                 }
436                         
437                 if ( databaseSnapshot != null ) {
438                     dirtyProperties = persister.findModified(databaseSnapshot, values, entity, session);
439                     cannotDirtyCheck = false;
440                     event.setDatabaseSnapshot(databaseSnapshot);
441                 }
442                 //do we even really need this? the update will fail anyway....
443
else if ( persister.isSelectBeforeUpdateRequired() ) {
444                     throw new StaleObjectStateException( persister.getEntityName(), id );
445                 }
446     
447             }
448         }
449         else {
450             // the Interceptor handled the dirty checking
451
cannotDirtyCheck = false;
452             interceptorHandledDirtyCheck = true;
453         }
454         
455         event.setDirtyProperties(dirtyProperties);
456         event.setDirtyCheckHandledByInterceptor(interceptorHandledDirtyCheck);
457         event.setDirtyCheckPossible(!cannotDirtyCheck);
458         
459     }
460
461 }
462
Popular Tags