I'm looking for a fix for
Code:
org.hibernate.HibernateException: instance not of expected entity type: eg.MyClass
when using EntityMode.DOM4J with joined-subclass.
I have read several posts in this forum regarding this, as well as all the reports for Bug 422 and its duplicates
http://opensource.atlassian.com/projects/hibernate/browse/HHH-422Please allow me to explain my thinking, and then suggest the possible solutions I'm thinking of:
On 24 Jun 2005 Jessica Marchiori recommends a code patch to
Code:
org.hibernate.persister.entity.BasicEntityPersister
class,
Code:
getSubclassEntityPersister(Object instance, SessionFactoryImplementor factory, EntityMode entityMode)
method. She was working with Hibernate 3.0.5.
For Hibernate 3.1 and 3.2 this would be class
Code:
org.hibernate.persister.entity.AbstractEntityPersister
, same method, same signature.
Code:
// TODO : really need a way to do something like :
// getTuplizer(entityMode).determineConcreteSubclassEntityName(instance)
Class clazz = instance.getClass();
if ( clazz == getMappedClass( entityMode ) ) {
return this;
}
else {
String subclassEntityName = getSubclassEntityName( clazz );
if ( subclassEntityName == null ) {
throw new HibernateException( "instance not of expected entity type: " + getEntityName() );
}
else {
return factory.getEntityPersister( subclassEntityName );
}
}
Following it down:
The class returned by
Code:
instance.getClass()
is
Code:
org.hibernate.tuple.ElementWrapper
which the javadoc says
Quote:
Wraps dom4j elements, allowing them to exist in a non-hierarchical structure
I note this ElementWrapper implements
Code:
org.dom4j.Branch
Code:
org.dom4j.Element
and
Code:
org.dom4j.Node
The comparison in question is:
Code:
instance.getClass() == getMappedClass( entityMode )
which calls:
Code:
public final Class getMappedClass(EntityMode entityMode) {
Tuplizer tup = entityMetamodel.getTuplizerOrNull(entityMode);
return tup==null ? null : tup.getMappedClass();
}
As I'm using EntityMode.DOM4J I assume the the Tuplizer in this case is a
Code:
org.hibernate.tuple.Dom4jEntityTuplizer
which defines the method this way:
Code:
public Class getMappedClass() {
return Element.class;
}
OK, so Element.class != ElementWrapper.class, but the one is the interface of the other.
When this resolves to false we next get check for null on:
Code:
getSubclassEntityName( instance.getClass() )
which calls:
Code:
private String getSubclassEntityName(Class clazz) {
return ( String ) entityNameBySubclass.get( clazz );
}
where entityNameBySubclass is a HashMap<Class, String> of mapped classes to entity names, as shown by the constructor:
Code:
if ( persistentClass.hasPojoRepresentation() ) {
//TODO: this is currently specific to pojos, but need to be available for all entity-modes
Iterator iter = persistentClass.getSubclassIterator();
while ( iter.hasNext() ) {
PersistentClass pc = ( PersistentClass ) iter.next();
entityNameBySubclass.put( pc.getMappedClass(), pc.getEntityName() );
}
}
For EntityMode.DOM4J this returns null, hence the exception.
Obviously this is a known situation due to the TODO comments, but hopefully I can find a satisfactory solution sooner rather than later. I'm keen to get feed back from either members of the Hibernate team, or those who are familiar with the internals of Hibernate, as to the sanity of the various alternatives.
Possible solutions/work arounds:1. Jessica's original suggested fix:
Code:
else if( instance instanceof org.dom4j.tree.DefaultElement) {
return this;
}
Maybe the instanceof should be for ElementWrapper or org.dom4j.Node.
This solution assumes that in EntityMode.DOM4J an EntityPersister is equal to an EntityPersister of the entity's subclass. As I don't know the exact role an EntityPersister plays, I don't know how safe this assumption is.
2. Change the first comparrison using '==' from:
Code:
instance.getClass() == getMappedClass( entityMode )
to:
Code:
getMappedClass( entityMode ).isAssignableFrom(instance.getClass())
Here we are changing the condition from requiring the class to be absolutely exactly the same, to requiring it to be upwards castable, ie widening. Because an org.hibernate.tuple.ElementWrapper implements org.dom4j.Element this will return true.
As for POJO classes, this assumes that mapped class returned by the Tupilizer is of the narrowest required subclass.
Is my logic sound here? Is this a safe assumption?
3.Add ElementWrapper.class (or org.dom4j.Element.class) as a duplicate key in the HashMap<Class, String> entityNameBySubclass.
For example, in the AbstractEntityPersister constructor do this:
Code:
Iterator iter = persistentClass.getSubclassIterator();
while ( iter.hasNext() ) {
PersistentClass pc = ( PersistentClass ) iter.next();
entityNameBySubclass.put( pc.getMappedClass(), pc.getEntityName() );
entityNameBySubclass.put( ElementWrapper.class, pc.getEntityName() );
}
This way we do not get null from:
Code:
getSubclassEntityName( instance.getClass() )
If you've read this far, I thank you. What do you think from both
(a) the long term perspective
(b) the "quick-fix whilst hoping for a Hibernate upgrade" perspective
Thank you again,