-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: Journalling Interceptor lazy collection problem
PostPosted: Mon Mar 01, 2004 3:16 pm 
Newbie

Joined: Fri Oct 31, 2003 3:33 pm
Posts: 17
Location: California
Hibernate 2.1.1, Oracle 8i, no mapping docs

I'm writing a JournallingInterceptor. It's SO close to being done, but I'm hitting a snag trying to audit changes to collections. I read this post: http://forum.hibernate.org/viewtopic.ph ... r+auditing and decided to take a different approach.

Instead of looking at currentState and previousState arrays, I'm attempting to load and cache (in the interceptor) copies of all collections to be journalled (journalled classes, properties and collections are defined in an xml config file). So, in onLoad and onFlushDirty, I call cacheCollection for each journalled collection in the entity being loaded or flushed. This (shallow) copies the objects over into a new collection.

Later, in findDirty, I compare the current collection to the cached one to look for changes. I only need to detect additions and deletions, so this works fine.

However, there is a problem when the collections are lazy and not initialized... Here is the cacheCollection method:

Code:
   /**
    * @param entity the entity originally passed into Interceptor.onLoad() or onFlushDirty()
    * @param jc     journalling metadata about the entity (i.e. which properties/collections to journal)
    */
   private void cacheCollection(Object entity, JournalledClass jc)
   {
      // if entity has a collection to be journalled
      if( jc.getCollectionPropertyName() != null ) {
         try {
            // was using PropertyUtils before...didn't work. Switched to straight reflection just
            // to be explicit.
            StringBuffer propertyInitCap = new StringBuffer( jc.getCollectionPropertyName() ) ;
            propertyInitCap.setCharAt( 0, Character.toUpperCase( propertyInitCap.charAt( 0 ) ) ) ;
           
            Method m  = entity.getClass().getMethod( "get" + propertyInitCap.toString(), new Class[0] ) ;
            Collection currCollection = (Collection)m.invoke( entity, new Object[0] ) ;
            // PROBLEM:  currCollection is a HashSet! Not a special hibernate lazy proxy collection!
            // if I actually casted entity into a DocumentBean and called the getReportCategories,
            // the lazy collection IS returned.
             
            // not initialized test
            if( currCollection.size() == 0 ) {
               try {
                  // does nothing since the collection is unknown to hibernate!
                  Hibernate.initialize( currCollection ) ;
                  // try to access elements to wake the collection up
                  currCollection.iterator() ;
               } catch( HibernateException e ) {
                  log.warn( "Error initializing collection", e ) ;
                  return ;
               }
            }

            // ArrayList( anotherList ) constructor was not copying the objects over... addAll works
            ArrayList cachedCollection = new ArrayList() ;
            cachedCollection.addAll( currCollection ) ;
            collectionsMap.put( jc.getClass().getName() + "." + jc.getCollectionPropertyName(), cachedCollection ) ;
         } catch( Exception e ) {
            log.error( "Unable to access collectionProperty " + jc.getCollectionPropertyName() +
                       " for entity " + entity.getClass().getName() ) ;
         }
      }
   }


Problem is that the collection returned by the reflection stuff about is NOT the lazy proxy collection, it is a plain old (empty) HashSet. Hibernate.initialize has no effect on this collection. BUT, if I cast the entity into a domain object and call the getter explicitly, I get back the magical lazy collection. Hibernate.initialize( collection ) works great on this collection.

Problem is, I can't cast here since this Interceptor shouldn't know anything about my domain model... I could require calling code to fully initialize all collections before flushing, but that would be very lame.

Am I up against a wall here? Any ideas on how to get around this problem? Maybe I just don't fully understand all the CGLIB proxy magic going on here. Thanks


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 6:08 pm 
Pro
Pro

Joined: Tue Aug 26, 2003 1:24 pm
Posts: 213
Location: Richardson, TX
I'm having an identical problem trying to replicate objects to a client. I don't want the replicator to know anything about the domain model, but I want to initialize all lazy collections. (and the lazy collections of any related objects from many-to-one relationships)

A recursive initialize would be nice. I suppose I could maintain a seperate mapping for those instances I want the efficiency of laziness, but that would be a pain...


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 7:46 pm 
Newbie

Joined: Fri Oct 31, 2003 3:33 pm
Posts: 17
Location: California
Thanks for replying. Maybe there's a way to emulate invoking the lazy collection getter through cglib or some Hibernate cglib helper class. Hibernate must be doing this somewhere behind the scenes. Anybody more familiar with hibernate source and/or cglib who could help out with this task?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 02, 2004 4:23 am 
Newbie

Joined: Fri Oct 31, 2003 3:33 pm
Posts: 17
Location: California
well, I decided to link up the source code in eclipse and actually see what Hibernate is doing. I must say, the code is absolutely gorgeous: very robust, well designed, easy to follow, descriptive toStrings on all objects, etc. Gavin is definitely an uberguru.

With regards to my problem, it looks like interceptor.onLoad is called right after Hibernate hydrates the entity. When it hits a lazy collection identifier, Hibernate IS setting up a net.hibernate.sf.collection.Set for me (overwriting the original HashSet). No cglib magic! Just a Set wrapper holding onto the Session. So, my interceptor SHOULD be seeing that magic Set, but, instead it is still seeing the original HashSet instantiated by the entities' constructor. Once I figure out I'm not getting a hibernate Set back in the interceptor, Hibernate.initialize will initialize my Set since it is an instance of PersistentCollection. Cool.

So, as usual, I'm probably just doing something retarded and getting in Hibernate's way. I'll post again once I figure out what I did wrong.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 02, 2004 5:39 pm 
Newbie

Joined: Fri Oct 31, 2003 3:33 pm
Posts: 17
Location: California
wonton: RTFM

Quote:
public boolean onLoad(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types)
throws CallbackException

Called just before an object is initialized. The interceptor may change the state, which will be propagated to the persistent object. Note that when this method is called, entity will be an empty uninitialized instance of the class.


This makes sense. Hibernate wants me to change the state (if necessary). It will then populate the entity with my new state.

So, to fix my problem, I just changed my onLoad to look in the state Object[] for collections to cache and it's all good now.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.