-->
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.  [ 4 posts ] 
Author Message
 Post subject: Dom4jEntityTuplizer
PostPosted: Thu Apr 13, 2006 1:53 pm 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
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-422

Please 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,

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 17, 2006 9:46 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Well this overall is a very tricky one to solve. Due to Hibernate's history of POJO persistence, there are quite a few places in the codebase where an instance's class is checked to determine the type of the entity.

I am thinking the correct long term solution, which I will start work on after 3.2 is final, is to introduce a new interface responsible for mapping an incoming instance to the appropriate entityname (?EntityNameResolver?). The various tuplizers then register appropriate implementations of that interface. The code in question then checks with all registered resolvers until it finds one which can properly resove the entity name.

In terms of this particular method you are asking about, I envision it will actually go away. In terms of all non-POJO entity modes the overall approach is flawed. Actually it *can* be made to work with the map entity mode because the maps actually contain an entry of the actual subclass entity name; the dom4j entity mode objects do not. Besides, once you properly determine the appropriate entity name (i.e. via the resolver thingy) there is no need to further extract the subclass entity persister (the one you got based on the "correct" entity name is the right one). The assumption in this method that you ask about works on this principle, and the fact that you always need to pass in the "correct" entity name when working with dom4j entity mode; thus checking instanceof against Node is probably more appropriate here in the short term.

HTH


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 18, 2006 6:26 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
Thank you Steve.
A brilliantly helpful reply!
:-)

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 09, 2007 5:20 am 
Newbie

Joined: Wed Sep 06, 2006 6:08 am
Posts: 11
Location: Rome, Italy
Was a patch ever released for this? I seem to be having the same problem on 3.2.4, and the source code is still unchanged. The same problem seems to occur in org.hibernate.loader.Loader.instanceAlreadyLoaded()


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 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:
cron
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.