-->
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.  [ 8 posts ] 
Author Message
 Post subject: ClassCastException with CGLIB Lazy Initialization
PostPosted: Mon Sep 18, 2006 11:44 am 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
I am getting a ClassCastException thrown when calling a method that simply returns 'this'. The object is being proxied with the CGLIBLazyInitializer and is loaded as an item in a collection where the many end of the one to many association is the supertype of the type of the object in question.
I think the error is due to the fact that the proxy type is a direct descendent of the base class (as this is the type of the one to many association) and the return type of the method being proxied is that of the sub-class.
To summarise the static structure, CreditFacilityConfiguration has a one to many relationship with CreditFacility. CreditFacility has two sub-types; CreditLimit and CreditLine. CreditFacility has an abstract method named baseLimit(). In CreditLine the implementation of this method returns parent.baseLimit(). In CreditLimit this method returns simply 'this'. Calling the method on a CreditLimit object from CreditFacilityConfiguration causes the ClassCastException.
I have included relevent code snippets and mapping files to clarify.
I can think of a workaround in the short term which is a bit fiddly - I would change the return type of the baseLimit method to CreditFacility. I would then have to use a visitor on the returned object because I need to call methods that are only declared on CreditLimit (and I can't downcast a proxy). I'm sure this will work but I suspect that this problem may need investigating further.
It appears that a similar problem in this area was reported and fixed in version 3.0 beta 4 (HHH-136). I've had a look at the code for this and it seems to confirm that returning the proxy when the return value from the method is the same as the proxied object would cause this exception in my scenario - the key difference being that the type of the many end of the one to many is the base class and the return type of the proxied method is a subclass.
Any information or suggestions would be greatly appreciated. Thanks.

Hibernate version: 3.0.5

Code:

from CreditFacilityConfigurationImpl.java
Code:
...
public void modifyReserveFacilityConfig(final Date effectiveDate,
        final Double amount)
        throws ConfigurationException {
        LOGGER.entering(LOG_CLASSNAME, "modifyReserveFacilityConfig",
            new Object[] { effectiveDate, amount });

        CreditFacility effective = effectiveReserveFacility(effectiveDate);

        if (effective != null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Found an effective reserve facility: " +
                    new BeanWrapper(effective));
            }

            final CreditLimit baseLimit = effective.baseLimit();
...


from CreditLimitImpl.java
Code:
...
  /**
     * There is one and only one limit per facility line, therefore this method
     * will always return the current object.
     *
     * @see CreditLimit#baseLimit()
     */
    public CreditLimit baseLimit() {
        return this;
    }
...


from CreditLineImpl.java
Code:
...
    /**
     * @see CreditFacility#baseLimit()
     */
    public CreditLimit baseLimit() {
        if (LOGGER.isTraceEnabled()) {
            LOGGER.entering(LOG_CLASSNAME, "baseLimit");
        }

        final CreditLimit baseLimit = parentFacility().baseLimit();

        if (LOGGER.isTraceEnabled()) {
            LOGGER.exiting(LOG_CLASSNAME, "baseLimit",
                new BeanWrapper(baseLimit));
        }

        return baseLimit;
    }
...


Mapping documents:

from CreditFacility.hbm.xml:
Code:
...
<hibernate-mapping default-cascade="none">
    <class name="com.wcg.wsf.businesspartners.CreditFacilityImpl" table="CREDIT_FACILITY" dynamic-insert="false" dynamic-update="false">
        <id name="id" type="java.lang.Long" unsaved-value="null">
            <column name="ID" sql-type="NUMBER(19)"/>
            <generator class="native">
            </generator>
        </id>
        <set name="apportionments" order-by="PARENT_FACILITY_FK" lazy="false" fetch="select" inverse="true" cascade="all-delete-orphan">
            <key foreign-key="CREDIT_LINE_PARENT_FAC">
                <column name="PARENT_FACILITY_FK" sql-type="NUMBER(19)"/>
            </key>
            <one-to-many class="com.wcg.wsf.businesspartners.CreditLineImpl"/>
        </set>
        <many-to-one name="config" class="com.wcg.wsf.businesspartners.CreditFacilityConfigurationImpl" foreign-key="CREDIT_FACILITY_CONF_FK" lazy="proxy" fetch="select">
            <column name="CONFIG_FK" not-null="true" sql-type="NUMBER(19)"/>
        </many-to-one>
        <joined-subclass name="com.wcg.wsf.businesspartners.CreditLineImpl" table="CREDIT_LINE" dynamic-insert="false" dynamic-update="false" abstract="false">
            <key foreign-key="CREDIT_LINE_I_FKC">                   
                <column name="ID" sql-type="NUMBER(19)"/>
            </key>
        <many-to-one name="parentFacility" class="com.wcg.wsf.businesspartners.CreditFacilityImpl" cascade="none" foreign-key="CREDIT_LINE_PARENT_FAC" lazy="proxy" fetch="select">
            <column name="PARENT_FACILITY_FK" not-null="true" sql-type="NUMBER(19)"/>
        </many-to-one>
        </joined-subclass>
        <joined-subclass name="com.wcg.wsf.businesspartners.CreditLimitImpl" table="CREDIT_LIMIT" dynamic-insert="false" dynamic-update="false" abstract="false">
            <key foreign-key="CREDIT_LIMIT_I_FKC">                   
                <column name="ID" sql-type="NUMBER(19)"/>
            </key>
        </joined-subclass>
    </class>
</hibernate-mapping>


from CreditFacilityConfiguration.hbm.xml:
Code:
...
<hibernate-mapping default-cascade="none">
    <class name="com.wcg.wsf.businesspartners.CreditFacilityConfigurationImpl" table="CREDIT_FAC_CONF" dynamic-insert="false" dynamic-update="false">
        <id name="id" type="java.lang.Long" unsaved-value="null">
            <column name="ID" sql-type="NUMBER(19)"/>
            <generator class="native">
            </generator>
        </id>
        <set name="primaryFacilitySequence" order-by="PRIM_CONF_FK" lazy="false" fetch="select" inverse="false" cascade="all-delete-orphan">
            <key foreign-key="PRIM_CONF_FKC">
                <column name="PRIM_CONF_FK" sql-type="NUMBER(19)"/>
            </key>
            <one-to-many class="com.wcg.wsf.businesspartners.CreditFacilityImpl"/>
        </set>

        ...

     </class>
</hibernate-mapping>


Stack trace:
Code:
Caused by: java.lang.ClassCastException: com.wcg.wsf.businesspartners.CreditFacilityImpl$$EnhancerByCGLIB$$aba7cbb3
   at com.wcg.wsf.businesspartners.CreditFacilityImpl$$EnhancerByCGLIB$$aba7cbb3.baseLimit(<generated>)
   at com.wcg.wsf.businesspartners.CreditFacilityConfigurationImpl.modifyPrimaryFacilityConfig(CreditFacilityConfigurationImpl.java:358)


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 19, 2006 7:58 am 
Newbie

Joined: Tue Sep 19, 2006 7:49 am
Posts: 1
We are having the exact same problem with our hibernate code.

Anyone know of any workarounds or fixes that can solve this problem will be really appreciated.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 20, 2006 7:40 am 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
Having had no response as yet and having investigated the issue further I'm now pretty sure this is a bug. Unless anyone objects by close of play today I'll go ahead and raise this in the issue tracking system.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Sep 24, 2006 8:53 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
It's not a bug in hibernate code. There is one bug in your code, and one possibly incorrect assumption.

The bug is that you've declared baseLimit as final. As you can see from your exception, the object that is initially being set into baseLimit is a CGLib proxied object. When it tries to deproxy itself, it won't be allowed to, because of the final modifier. So you'll have to get rid of that, or else force a deproxying before applying the modifier.

I can't see your interface hierarchy, but if CreditFacilityImpl doesn't implement CreditLimit, then that's what's causing your cce. The CGLib proxy object will implement all interfaces of the class that it's extending (obviously), but it won't implement any interfaces of any of the subclasses. You'll have to deproxy the object before you can cast to an interface of a subclass of the mapped class.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 25, 2006 8:27 am 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
The final declaration of the local variable makes no difference to this problem; the exception occurs during the proxying of the method baseLimit() after the successful completion of the target implementation of baseLimit but before the object is ever assigned. Remember, the local variable is being declared as final which means it can only be assigned once and that is after the rhs expression has been resolved. I don't actually want baseLimit to be de-proxied.
CreditFacilityImpl does not implement CreditLimit - it is the other way round: CreditFacility is the abstract base class and CreditLimitImpl is a concrete sub-class.
What causes the problem, and I'll have to disagree with you because I'm almost 100% sure it is a problem in Hibernate code, is that when CGLibLazyInitializer handles the return value of a proxied method it checks whether the return value is the currently proxied object. If it is then instead of returning the actual object it returns the proxy. This is great in most circumstances, but in the special case that I am describing, in which the currently proxied object is a sub-class of the type of the association end and the proxied method returns "this" then the CCE occurs. See extract of code below from CGLibLazyInitializer.java from 3.1.3 release:

Code:
public Object intercept(final Object proxy, final Method method, final Object[] args,
         final MethodProxy methodProxy) throws Throwable {
      if ( constructed ) {
         
         Object result = invoke( method, args, proxy );
         if ( result == INVOKE_IMPLEMENTATION ) {
            Object target = getImplementation();
            final Object returnValue;
            if ( ReflectHelper.isPublic( persistentClass, method ) ) {
               returnValue = methodProxy.invoke( target, args );
            }
            else {
               if ( !method.isAccessible() ) method.setAccessible( true );
               try {
                  returnValue = method.invoke( target, args );
               }
               catch (InvocationTargetException ite) {
                  throw ite.getTargetException();
               }
            }
            return returnValue == target ? proxy : returnValue;
         }
         else {
            return result;
         }
         
      }
      else {
         
         // while constructor is running
         if ( method.getName().equals( "getHibernateLazyInitializer" ) ) {
            return this;
         }
         else {
            return methodProxy.invokeSuper( proxy, args );
         }
         
      }
   }

It's the "return returnValue == target ? proxy : returnValue;" that causes the problem. My return value is of type CreditLimit which is perfectly fine. It also happens to be the exact same object as "target" which is also perfectly fine (because it returns "this"). Given this situation, the above code then substitues the return value with the currently proxied object which seems like the right thing to do (and normally is) except for the fact the the proxy extends the base class CreditFacility and not CreditLimit.
Now further up the stack when an attempt is made to cast the object to the correct return type for the proxied method we get the CCE.
Having explained it again and looked at the Hibernate code again I'm convinced.
If anyone from the Hibernate team could possibly have a quick scan of this thread to confirm my conclusion that would be great - then I'll be more than happy to put a test case together and post it on JIRA.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 25, 2006 5:18 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
The "final" does make a difference, because the deproxying happens after the enhanced object is created. If you want to make the variable final, and have only the deproxied version, use this code:
Code:
CreditLimit tempLimit = parentFacility().baseLimit();
tempLimit().getSomeProperty();
final CreditLimit baseLimit = tempLimit;
That'll work, try it.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 26, 2006 10:21 am 
CGLIB Developer
CGLIB Developer

Joined: Thu Aug 28, 2003 1:44 pm
Posts: 1217
Location: Vilnius, Lithuania
Probably it is more a bug than a feature, register it and try to declare some common interface, it must help as workaround too.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 27, 2006 11:05 am 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
Yes I think this is definitely a bug. I've put together a simple test case to reproduce the problem and have submitted to JIRA issue number HHH-2110.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 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.