-->
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.  [ 7 posts ] 
Author Message
 Post subject: Proxies, the visitor pattern and CGLibLazyInitializer
PostPosted: Tue Jun 07, 2005 1:02 pm 
Newbie

Joined: Sun Apr 10, 2005 9:35 pm
Posts: 14
Hi there,

Hibernate: 3.0.5
Spring: 1.2.1
Tomcat: 5.0.27
JVM: 1.4.2_06
OS: Windows XP

I'm having some trouble with lazy initialisation of proxies and the visitor pattern. I'm retrieving a collection of objects and they're initialised as the base-class as you'd expect from the Hibernate docs. I'd like to use the visitor pattern to widen the instance to the correct sub-class and acquire a handle on that instance.

My problem is that although I can get the visitor pattern to execute the expected methods on the sub-class instance, the CGLibLazyInitializer is not returning me the result of my method call, but the CGLib proxy instance instead (as a result of its equivalence test - see below). The exceptions are manifesting in the view layer where I try to access a property on the sub-class:

Code:
javax.servlet.ServletException: Unable to find a value for "homeGame" in object of class "uk.co.mindfruit.cms.domain.Event$$EnhancerByCGLIB$$5e0da2a0" using operator "."

The view is expecting an instance of uk.co.mindfruit.cms.domain.Fixture instead of the base-class, Event. The basic class hierarchy is:
Code:
public class Event {
    // simple POJO impl
}

Code:
public class Fixture extends Event {
    // etc.
}


Visitor implementation

My EventVisitorAdaptor implementation looks like this:

Code:
public class EventVisitorAdaptor implements EventVisitor {

    private Session session;
   
    public EventVisitorAdaptor(Session session) {
        super();
        this.session = session;
    }
   
    public Event visitEvent(Event event) {
        // return base class
        return event;
    }
   
    public Fixture visitFixture(Fixture fixture) {
        // makes no difference to the outcome if this is .load() or .get()
       
        return (Fixture) session.load(Fixture.class, fixture.getId());
       
        // I've tried using Criteria and HQL queries to see if it makes any difference to the identity of
        // the returned object. As you'd expect, they make no difference at all.
        //
        // a)
        // return (Fixture) session.createCriteria(Fixture.class).add(Expression.eq("id", fixture.getId())).uniqueResult();
        //
        // b)
        // Query hqlQuery = session.createQuery("from Fixture as fixture where fixture.id = :fixtureId");
        // hqlQuery.setLong("fixtureId", fixture.getId().longValue());
        // return (Fixture)hqlQuery.uniqueResult();
   
    }
}

It's worth pointing out that when in the visitFixture(...) method above, using a debugger it's possible to see that the Fixture object is loaded and returned correctly.

I have added an accept(EventVisitor) method to both Event and Fixture, so that I can make a call on the instance:

Code:
public class Event {
    public Event accept(EventVisitor eventVisitor){
        return eventVisitor.visitEvent(this);
    }
}

Code:
public class Fixture extends Event {
    public Event accept(EventVisitor eventVisitor){
        return eventVisitor.visitFixture(this);
    }
}


The intention of calling .accept(eventVisitor) is so that a "widened" instance of the Fixture sub-class is returned, if appropriate, rather than the base-class proxy obtained from the collection.

My problem comes in the CGLibLazyInitializer, in the method intercept(Object, Method, Object[], MethodProxy). The code snippet of interest is:

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);
                    returnValue = method.invoke(target, args);
                }
                // the line below returns me the proxy rather than my return value as
                // target == returnValue.
                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);
            }
        }
    }


So really I'm wondering how to get a handle on the object I returned rather than the proxy. Do I have to wrap returnValue in some simple bean class simply to overcome the equivalence test?

I'm also getting the following debug, which seems to be a result of my narrowing conversion, though I'm not sure what's broken!

Code:
WARN  org.hibernate.engine.PersistenceContext  - Narrowing proxy to class uk.co.mindfruit.cms.domain.Fixture - this operation breaks ==
DEBUG uk.co.mindfruit.coreweb.domain.logic.events.PlayerEventModel  - Adding event (pollable.id:11,event.id:11,pollable:true) for date 2005-02-05
WARN  org.hibernate.engine.PersistenceContext  - Narrowing proxy to class uk.co.mindfruit.cms.domain.Fixture - this operation breaks ==
DEBUG uk.co.mindfruit.coreweb.domain.logic.events.PlayerEventModel  - Adding event (pollable.id:14,event.id:12,pollable:false) for date 2005-02-05


Please let me know if any stack-traces or logging would be of assistance. I'm at a bit of a loss what to try next, other than wrapping the return value.

The relevant part of my wrapping document follows; thanks in advance for reading this far. I hope it makes sense.

Best wishes,

Mike

Code:

    <class name="Event" table="hbm_events" >
        <id name="id" column="event_id" type="java.lang.Long">
            <generator class="native"/>
        </id>
        <property name="type" type="string"/>
        <property name="eventDate" type="date" not-null="true">
            <column name="event_date" sql-type="date"/>
        </property>
        <property name="startTime" type="time" not-null="true">
            <column name="start_time" sql-type="time"/>
        </property>
        <property name="endTime" type="time">
            <column name="end_time" sql-type="time"/>
        </property>
        <property name="title" type="string"/>
        <property name="description" type="string"/>
        <property name="lastUpdated" type="timestamp">
            <column name="last_updated" sql-type="timestamp"/>
        </property>
        <set name="eventGroups" cascade="save-update">
            <key column="event_id"/>
            <one-to-many class="uk.co.mindfruit.cms.domain.PollableEventGroup"/>
        </set>
        <many-to-one name="club"   class="uk.co.mindfruit.cms.domain.Club"   column="club_id" />
        <many-to-one name="season" class="uk.co.mindfruit.cms.domain.Season" column="season_id" cascade="save-update" />
        <many-to-one name="venue"  class="uk.co.mindfruit.cms.domain.Venue"  column="venue_id"  cascade="save-update" />
        <joined-subclass name="Fixture" table="hbm_fixtures" >
            <key column="event_id"/>
            <property name="meetTime" type="time">
                <column name="meet_time" sql-type="time"/>
            </property>
            <property name="pointsFor" type="integer">
                <column name="points_for"/>
            </property>
            <property name="pointsAgainst" type="integer">
                <column name="points_against"/>
            </property>
            <property name="homeGame" type="boolean">
                <column name="home_game"/>
            </property>
            <many-to-one name="homeTeam"    class="uk.co.mindfruit.cms.domain.Team"        column="our_team_id"  cascade="none"/>
            <many-to-one name="opposition"  class="uk.co.mindfruit.cms.domain.Team"        column="opposition_id"  cascade="none"/>
            <many-to-one name="fixtureType" class="uk.co.mindfruit.cms.domain.FixtureType" column="type_id"  cascade="none"/>
        </joined-subclass>
    </class>



Top
 Profile  
 
 Post subject:
PostPosted: Tue Jun 07, 2005 1:30 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
DO NOT narrow things!

Use polymorphism instead. That is, after all, the whole reason why OO languages have subclassing.

Whenever you feel like doing a typecast in Java, think to yourself I AM NOT DOING OO NOW AND I SHOULD SLAP MYSELF.


Top
 Profile  
 
 Post subject: Narrowing? Merely following the example on hibernate.org
PostPosted: Tue Jun 07, 2005 1:49 pm 
Newbie

Joined: Sun Apr 10, 2005 9:35 pm
Posts: 14
Hi Gavin,

Thanks for your prompt response, though I'm not sure whether I should be giving myself a slap or not, as I've implemented the visitor pattern as suggested on the hibernate.org website (http://www.hibernate.org/280.html). My query was really why the CGLibLazyInitializer wasn't returning the result of my method call, but its copy of the proxy instead.

As it happens, I've solved my immediate problem by changing the return types of the EventVisitor from an Event to an EventWrapper object. The equivalence test at line 143 of CGLibLazyInitializer now returns the result 'returnValue' (rather than the proxy) and I can get a handle on the object I'm interested in. All that without a single "instanceof" too. How is this bad OO?

If there's a more elegant way (which I'm sure there is) or a simpler way (which there may be) of fixing the error occuring in the view layer (incidentally, I'm using the OpenSessionInView interceptor), then please let me know...

Cheers

Mike


Top
 Profile  
 
 Post subject: Narrowing? Merely following the example on hibernate.org
PostPosted: Fri Sep 02, 2005 4:21 pm 
Newbie

Joined: Mon Jun 06, 2005 8:53 am
Posts: 16
Hi kenevel, could you please send an example of your working code? I'm having the same problem that you had.

I'm using the visitor pattern, and the correct visit method is called, but I get an ClassCastException when I try to work with the subclass that should be returned.

thanks.
Cristiane


Top
 Profile  
 
 Post subject: Narrowing? Merely following the example on hibernate.org
PostPosted: Fri Sep 02, 2005 4:40 pm 
Newbie

Joined: Sun Apr 10, 2005 9:35 pm
Posts: 14
Hi Christiane,

I'm not sure which part of your code is breaking. I've refactored that particular part of the model so I don't need to use the visitor pattern, so I'm afraid I've got no code to post for you.

I take it you've tried following the code through with a debugger so you can see exactly what is causing the ClassCastException to be thrown?

Mike


Top
 Profile  
 
 Post subject: Post subject: Narrowing? Merely following the example on hi
PostPosted: Fri Sep 02, 2005 4:48 pm 
Newbie

Joined: Mon Jun 06, 2005 8:53 am
Posts: 16
Aren't you using subclasses anymore? If not, what solution are you using?

Well the error is thrown when I try to Cast the object, like:

BaseProjectFunctionVisitor visitor = new BaseProjectFunctionVisitorAdaptor();

ProjectFunction pf = (ProjectFunction)baseProjFunc.accept(visitor);

I debug the code and it's executing the correct accept method.

Is that code wrong? Any idea?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Sep 02, 2005 5:06 pm 
Newbie

Joined: Sun Apr 10, 2005 9:35 pm
Posts: 14
Hi mate,
Code:
BaseProjectFunctionVisitor visitor = new BaseProjectFunctionVisitorAdaptor();
ProjectFunction pf = (ProjectFunction)baseProjFunc.accept(visitor);

The visitor pattern doesn't work, like that. I'm sure you've read

http://www.hibernate.org/280.html

on its use, but the point surely is that it removes the need for casting. Broadly, your code might wind up looking something like this:
Code:
BaseProjectFunctionVisitor visitor = new BaseProjectFunctionVisitorAdaptor();
baseProjFunc.accept(visitor);
SomeResult = visitor.getResult();


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