[hibernate-issues] [Hibernate-JIRA] Commented: (HHH-3870) Hibernate proxies Groovy's getMetaClass method breaking proxies when used with Groovy

Graeme Rocher (JIRA) noreply at atlassian.com
Wed Apr 22 04:25:18 EDT 2009


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-3870?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=32971#action_32971 ] 

Graeme Rocher commented on HHH-3870:
------------------------------------

 - would there be a way for Groovy to get the metaclass of A (would it even make sense)?

Yes if the getMetaClass() call wasn't being proxied this is what would happen

 - you agree that metaClass must *not* access lazy objects if we bypass the proxy call

Agreed, it won't anyway, it works purely at the class level.

 - is there a way to know that getMetaClass is the Groovy's metaclass and not some random metaClass property? looking for the groovy Object class? What's the fqcn again? 

I guess you would have to check the return type which is a groovy.lang.MetaClass

- I'm almost certain we should still trigger initialize() when getMetaClass is called

I don't agree, I have a local custom implementation of EntityPersister that excludes the getMetaClass method from being proxied which gives me the correct behavior for Grails right now.

 - I don't fully understand why you end up with the IllegalArgumentException. Maybe the proxy should still intercept but do something different for getMetaClass. not sure what yet.

Ok let me try to illustrate an example. Say you lookup a proxied instance of A:

A a = .. // lookup A

The underlying instance is actually as instance of C. However, the proxy instance 'a' is not an instance of C because Hibernate has dynamically subclassed A. Now you try to invoke a method on 'a':

a.foo()

This results in the pseudo code:

a.getMetaClass().invokeMethod(a, "foo")

However, since the getMetaClass() method is proxied instead of returning the MetaClass for A it proxies to the underlying instance something like:

a.getHibernateLazyInitializer().getImplementation().getMetaClass().invokeMethod(a, "foo")

Now the problem is a.getHibernateLazyInitializer().getImplementation().getMetaClass() returns the MetaClass for 'C', but the proxy instance 'a' is not an instance of 'C', instead it is a dynamically subclassed instance of A. The result is that the call to invokeMethod(a, "foo") eventually delegates to something like:

java.lang.reflect.Method fooMethod = .. // lookup foo method
fooMethod.invoke(a, null)

However because the instance 'a' is not an instance of 'C' this blows up with a ClassCastException which is re-thrown inside java.lang.reflect.Method.invoke(..) as a IllegalArgumentException. Now in Java you would get something similar because in Java say you loaded 'A':

A a = .. // lookup A

And then tried to do:

((C)a).foo()

You would get a ClassCastException because 'a' is not a 'C', however the reason is more clear whilst with Groovy you get this weird IllegalArgumentException which is is hard to debug and is caused by the proxying of the getMetaClass() method. You should actually be getting a MissingMethodException and in fact in Grails with my custom EntityPersister that is what you get. Well actually this is not quite true because we have added this bit of code to Grails:
    public static void enhanceProxy ( HibernateProxy proxy ) {
        
        proxy.metaClass.propertyMissing = { String name, val ->
            if(delegate instanceof HibernateProxy) {
                def obj = GrailsHibernateUtil.unwrapProxy(delegate)
                if(val != null) {
                    obj?."$name" = value
                }
                return obj."$name"
            }
            else {
                throw new MissingPropertyException(name, delegate.class)
            }
        }

        proxy.metaClass.methodMissing = { String name, args ->
            if(delegate instanceof HibernateProxy) {
                def obj = GrailsHibernateUtil.unwrapProxy(delegate)
                return obj."$name"(*args)
            }
            else {
                throw new MissingPropertyException(name, delegate.class)
            }

        }
    }

Which will automatically delegate to the proxied instance when you experience any MissingMethodExceptions etc. That actually makes the experience with Hibernate proxies in Grails better than what you get with Java :-)

> Hibernate proxies Groovy's getMetaClass method breaking proxies when used with Groovy
> -------------------------------------------------------------------------------------
>
>                 Key: HHH-3870
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3870
>             Project: Hibernate Core
>          Issue Type: Bug
>    Affects Versions: 3.2.6, 3.3.0.CR1, 3.3.0.CR2, 3.3.0.GA, 3.3.0.SP1, 3.3.1
>            Reporter: Graeme Rocher
>            Assignee: Emmanuel Bernard
>            Priority: Critical
>
> Say you have a class hierarchy like A->B-C and you obtain a proxied instance of C
> {code}
> A a = session.load(A, 1L)
> {code}
> When Groovy invokes a method it looks up the objects metaclass by calling the getMetaClass() method on A. The pseudo code for Groovy method dispatch is something like:
> {code}
> a.getMetaClass().invokeMethod(a, "foo")
> {code}
> The problem is that the getMetaClass() method is being proxied onto the underlying instance of C so when a.getMetaClass() is called you get the MetaClass for the class C, but because of the way Hibernate creates proxies it is not an instance of C at all, but instead an dynamically created subclass of A. The result is you get an IllegalArgumentException that is hard to debug.
> In Grails we have worked around this by customizing Hibernate's proxy creation mechanism to not proxy the getMetaClass() method. The problem this mechanism is currently insanely difficult to customize and also usage of Groovy on its own outside of Grails is still broken with Hibernate (such as inside JBoss Seam)
> It would be great if one of two things could be done:
> a) Hibernate does not proxy the getMetaClass() method at all this could be done by modifying JavassistLazyInitializer and the associated cglib one as follows:
> {code}
> 	private static final MethodFilter FINALIZE_FILTER = new MethodFilter() {
> 		public boolean isHandled(Method m) {
> 			// skip finalize methods
> 			return !( m.getParameterTypes().length == 0 && (m.getName().equals( "finalize" ) || m.getName().equals( "getMetaClass" ) ));
> 		}
> 	};
> {code}
> b) The mechanism for creating proxies is made easier to customize so that the getMetaClass() method can be easily excluded via some configuration or something. However, this would still mean that out of the box Hibernate is broken when used with Groovy unless the solution a) is applied too

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        



More information about the hibernate-issues mailing list