object is not an instance of declaring class

In our last episode we were seeing an IllegalArgumentException when Method.invoke() was called, and the message inside the exception was:

object is not an instance of declaring class

Let’s see if we can figure out what’s going on here.

To review, the call that fails is in org.hibernate.property.BasicPropertyAccessor.BasicSetter.set(), line 66, where it calls

method.invoke( target, new Object[] { value } );

And performing a Display (Ctrl+Shift+D) operation to show the values of each of the parts of this call:

  • method=(java.lang.reflect.Method) public void com.ontsys.fw.datatypes.BaseTO.setKey(java.lang.String)
  • target= (java.util.HashMap<K,V>) {VER_REV_TYPE=ADD, originalId={VER_REV=com.ontsys.fw.hibernatesupport.RevisionEntityPO@15134f5, nullkey=2c9094581ef993dd011ef993f2130002, productSet_key=2c9094581ef993dd011ef993f2040001}}
  • value=(java.lang.String) 2c9094581ef993dd011ef993f4730006

Then we get the object is not an instance of declaring class-containin’ IllegalArgumentException.

1. Focusing on the Target

Let’s look at the basic question first: is target a com.ontsys.fw.datatypes.BaseTO ?  It looks like it’s a java.util.HashMap.

Not having much experience with reflection (with Java reflection anyway; I have some experience with personal reflection… ;), I don’t really know what I’m seeing here.  Let’s try this breakpoint with a call that works, and evaluate method and target there.

Right now the breakpoint hit count is set to 78, as it’s the 78th call to BasicPropertyAccessor.BasicSetter.set() that fails.

2. Examinin’ a Known Good method.invoke()

When we set the breakpoint hit count on the previously mentioned method.invoke() to 77 instead of 78, it breaks there with these values (again, gotten by Ctrl+Shift+D-ing :

  • method= (java.lang.reflect.Method) public void com.ontsys.fw.hibernatesupport.RevisionEntityPO.setId(java.lang.Integer)
  • target=(com.ontsys.fw.hibernatesupport.RevisionEntityPO) com.ontsys.fw.hibernatesupport.RevisionEntityPO@103a032
  • value= (java.lang.Integer) 5874

Hmm, notice that the Method is a reflective instance of RevisionEntityPO.setId(), and the target object is a RevisionEntityPO (i.e., the target matches the method it’s being called on?  That’s not the case where the exception gets thrown — there, it’s BaseTO.setKey() being called on a java.util.HashMap.  I wonder where this mismatch is coming from, that 78th time into BasicPropertyAccessor.BasicSetter.set()?

3. Tracking down the mismatch

The HashMap comes into BasicPropertyAccessor.BasicSetter.set() as an Object parameter named target.  Let’s follow the call stack to see where target is coming from.

It’s passed in:

  • As an Object named entity to PojoEntityTuplizer(AbstractEntityTuplizer).setIdentifier();
  • by SingleTableEntityPersister(AbstractEntityPersister).setIdentifier(), which received it as a As an Object named object
  • from DefaultSaveEventListener(AbstractSaveEventListener).performSave(), which received it as an Object named entity
  • from DefaultSaveEventListener(AbstractSaveEventListener).saveWithGeneratedId(), which received it as an Object named entity
  • from DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event), which called event.getEntity().

Now this org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) call is interesting: it passes event.getEntity() and event.getEntityName() to saveWithGeneratedId().  We’ll want to watch and see how Entity and EntityName get into the SaveOrUpdateEvent.

So, continuing on down the call stack:

  • SaveOrUpdateEvent event comes into DefaultSaveEventListener.saveWithGeneratedOrRequestedId(SaveOrUpdateEvent)
  • from DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).entityIsTransient(SaveOrUpdateEvent)
  • from DefaultSaveEventListener.performSaveOrUpdate(SaveOrUpdateEvent)
  • from DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).onSaveOrUpdate(SaveOrUpdateEvent)
  • from SessionImpl.fireSave(SaveOrUpdateEvent)
  • from SessionImpl.save(String, Object).

Ok, it’s in this last spot, org.hibernate.impl.SessionImpl.save(String, Object), that another  interesting thing happens.  The implementation of this method is the one-liner:

        return fireSave( new SaveOrUpdateEvent(entityName, object, this) );
  • entityName= (java.lang.String) com.ontsys.db.inventory.products.po.ProductPO
  • object= (java.util.HashMap<K,V>) {VER_REV_TYPE=ADD, originalId={VER_REV=com.ontsys.fw.hibernatesupport.RevisionEntityPO@ed9a09, nullkey=2c9094581efadd5e011efadd72ca0002, productSet_key=2c9094581efadd5e011efadd72ca0001}}

So it looks wrong even back here.

3.1. PersistentCollectionChangeData is wrong too

One level deeper, we get to org.jboss.envers.synchronization.work.PersistentCollectionChangeWorkUnit.perform(Session, Object), where we see this line of code:

session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());

Here, the persistentCollectionChangeData object’s entityName is  “com.ontsys.db.inventory.products.po.ProductPO”, but the data is a HashMap.

3.2. Whence this errant persistentCollectionChangeData object?

This persistentCollectionChangeData object is one element of the private final List<PersistentCollectionChangeData> collectionChanges, which is set in the constructor of org.jboss.envers.synchronization.work.PersistentCollectionChangeWorkUnit.  It looks from the javadoc for org.jboss.envers.entities.mapper.PropertyMapper.mapCollectionChanges() that it’s the Serializable snapshot parameter coming into the PersistentCollectionChangeWorkUnit constructor that is the wrong type (HashMap instead of ProductPO).

3.3. The creation of the PersistentCollectionChangeWorkUnit: onCollectionAction()

It is here that our debugging forks away from the JTA transaction commit call that kicks off a beforeCompletion() Synchronization event that results in the envers PersistentCollectionChangeWorkUnit.peform() call.  The bad data seems to be getting passed in when this PersistentCollectionChangeWorkUnit is constructed.  Let’s see where that is.

Searching for all references in this project to the PersistentCollectionChangeWorkUnit constructor (using Eclipse’s handy Search -> References -> Project) yields just one reference: org.jboss.envers.event.VersionsEventListener.onCollectionAction(AbstractCollectionEvent, PersistentCollection, Serializable, CollectionEntry).

The second and third parameters to onCollectionAction() are PersistentCollection newColl and Serializable oldColl.  I think it’s one of these that’s the HashMap where we would expect it to be a ProductPO.

onCollectionAction() is called by just three methods, all in org.jboss.envers.event.VersionsEventListener:

  • onPostRecreateCollection()
  • onPreRemoveCollection()
  • onPreUpdateCollection()

4. No Clear Badness Seen at the VersionsEventListener events

I’ve put a simple breakpoint on each of the three collection-related versions events mentioned in the bulleted list above.  Here are the breakpoints we hit, in order, before the fateful method.invoke() where we die:

  1. onPostRecreateCollection():
    • thePersistentCollection passed in is a PersistentSet: [com.ontsys.db.inventory.products.po.ProductPO@11bbdbb]
    • the Serializable is passed as null
    • the affectedOwnerEntityName (which is later read out as entityName) is “com.ontsys.db.inventory.products.po.ProductSetPO”

    — so far, looks good…

  2. onPostRecreateCollection():
    • the PersistentCollection passed in is a PersistentBag: [com.ontsys.db.inventory.products.po.DelinquencyBucketPO@14e7ad5, com.ontsys.db.inventory.products.po.DelinquencyBucketPO@1f5a659];
    • the Serializable is again null; and
    • the affectedOwnerEntityName is com.ontsys.db.inventory.products.po.ProductPO
  3. onPreRemoveCollection():
    • the PersistentCollection passed in is null;
    • the Serializable is (java.util.HashMap<K,V>) {com.ontsys.db.inventory.products.po.ProductPO@11bbdbb=com.ontsys.db.inventory.products.po.ProductPO@11bbdbb}
  4. onPostRecreateCollection():
    • the PersistentCollection passed in is a PersistentSet:  [com.ontsys.db.inventory.products.po.ProductPO@11bbdbb];
    • the Serializable is null; and
    • the affectedOwnerEntityName is com.ontsys.db.inventory.products.po.ProductSetPO

Hmm.  None of these looks obviously wrong to my admittedly non-expert eyes.  Even in step 3 when the Serializable is a HashMap, it’s a HashMap containing a ProductPO mapping, which looks plausible.

5. Wondering off into the Sunset

It looks like org.jboss.envers.entities.mapper.MultiPropertyMapper.mapCollectionChanges(String, PersistentCollection, Serializable, Serializable) (which each of the four envers event listeners gets into) may have something to do with the mismatch.  But I haven’t been able to pinpoint the cause yet.  Seems like it’s likely a configuration problem on our end — though I don’t know where — our Hibernate configuration being:


    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${framework.hibernatedialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${framework.hbm2ddl.auto}</prop>
<prop key="hibernate.transaction.manager_lookup_class">${framework.hibernate.transaction.manager_lookup_class}</prop>
<prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</prop>
<prop key="org.jboss.envers.revisionTypeFieldName">VER_REV_TYPE</prop>
<prop key="org.jboss.envers.revisionFieldName">VER_REV</prop>
<prop key="org.jboss.envers.versionsTableSuffix">_V</prop>
<prop key="org.jboss.envers.versionsTablePrefix"></prop>
<prop key="org.jboss.envers.revisionOnCollectionChange">true</prop>
<prop key="org.jboss.envers.warnOnUnsupportedTypes">false</prop>
<prop key="org.jboss.envers.unversionedOptimisticLockingField">false</prop>

            </props>
        </property>
<property name="eventListeners">
            <map>
                <entry key="post-insert">
                    <ref local="versionsEventListener" />
                </entry>
                <entry key="post-update">
                    <ref local="versionsEventListener" />
                </entry>
                <entry key="post-delete">
                    <ref local="versionsEventListener" />
                </entry>
                <entry key="pre-collection-update">
                    <ref local="versionsEventListener" />
                </entry>
                <entry key="pre-collection-remove">
                    <ref local="versionsEventListener" />
                </entry>
                <entry key="post-collection-recreate">
                    <ref local="versionsEventListener" />
                </entry>
            </map>
        </property>
<property name="mappingResources" ref="contributedHibernateMappingFiles" />
<property name="annotatedClasses" ref="contributedAnnotatedPoClasses" />

    </bean>

The property placeholders in the above configuration are filled in as follows:

framework.hibernatedialect=org.hibernate.dialect.Oracle10gDialect
framework.hbm2ddl.auto=validate
framework.hibernate.transaction.manager_lookup_class=org.hibernate.transaction.BTMTransactionManagerLookup
Advertisements
  1. #1 by Ralf on February 9, 2011 - 4:50 am

    Hi, did you find a solution for the described problem? It seems to be similar in my application…

    Best regards, Ralf

  2. #2 by danielmeyer on February 9, 2011 - 7:19 am

    Ralf,
    This post or this one may help… the second one has a couple of comments from others which might point you in the right direction.

    -Daniel-

  3. #3 by Nickolas Golubev on March 15, 2011 - 3:16 pm

    I found that I had the same issue due to a very simple mapping being wrong, I was doing this:

    and should have been doing this:

    Check your mapping!

  4. #4 by stokito on June 18, 2015 - 10:30 am

    I had a similar error and little bit described here https://youtrack.jetbrains.com/issue/IDEA-141636

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s