mhgrove/Empire

lazily-fetched properties that are interfaces are not proxied correctly

delocalizer opened this issue · 3 comments

The proxy returned by RdfGenerator#getProxyOrDbObject does not seem to have correct class hierarchy for properties that are interfaces.

== example ==
(see my comment in #106 about the workaround annotation of setters with @OneToMany)

Child.java:

package au.edu.qimr.grafli.generated;

import com.clarkparsia.empire.SupportsRdfId;
import com.clarkparsia.empire.annotation.*;
import java.util.*;
import javax.persistence.*;

@javax.persistence.Entity // Fully-qualified to distinguish from PROV-O 'Entity'
@RdfsClass("ex:Child")
@Namespaces({"ex", "http://example.org/"})
public interface Child extends SupportsRdfId {

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
    @RdfProperty("ex:isChildOf")
    public List getIsChildOf();
    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
    public void setIsChildOf(final List parents);
}

test data:

@prefix rdfs:    .
@prefix ex:  .

ex:Parent rdfs:subClassOf ex:Person .
ex:Child rdfs:subClassOf ex:Person .

ex:p1 a ex:Parent ;
    ex:isParentOf ex:c1 ;
    ex:isParentOf ex:c2 ;
.
ex:p2 a ex:Parent ;
    ex:isParentOf ex:c1 ;
    ex:isParentOf ex:c2 ;
.
ex:c1 a ex:Child ;
    ex:isChildOf ex:p1 ;
    ex:isChildOf ex:p2 ;
.
ex:c2 a ex:Child ;
    ex:isChildOf ex:p1 ;
    ex:isChildOf ex:p2 ;
.

Parent.java:

package au.edu.qimr.grafli.generated;

import com.clarkparsia.empire.SupportsRdfId;
import com.clarkparsia.empire.annotation.*;
import java.util.*;
import javax.persistence.*;

@javax.persistence.Entity // Fully-qualified to distinguish from PROV-O 'Entity'
@RdfsClass("ex:Parent")
@Namespaces({"ex", "http://example.org/"})
public interface Parent extends SupportsRdfId{

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
    @RdfProperty("ex:isParentOf")
    public List getIsParentOf();
    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
    public void setIsParentOf(final List children);
}

Test it:

/**
 * em is an Empire entity manager
 */
groovy:000> child1 = em.find(Child, "http://example.org/c1")
===> http://example.org/c1
groovy:000> parent1 = child1.isChildOf[0]
===> http://example.org/p1
groovy:000> parent1 instanceof Parent
===> false
groovy:000> parent1.class
===> class com.clarkparsia.empire.SupportsRdfId_$$_javassist_0
groovy:000> parent1.class.interfaces
===> [interface com.clarkparsia.empire.SupportsRdfId, interface com.clarkparsia.empire.EmpireGenerated, interface javassist.util.proxy.ProxyObject]

My expectation is that the proxy should also have Parent as an interface.

The issue is that in RdfGenerator#getProxyOrDbObject, the block that adds interfaces to the proxy:

            aFactory.setInterfaces(ObjectArrays.concat(theClass.getInterfaces(), EmpireGenerated.class));
            if (!theClass.isInterface()) {
                aFactory.setSuperclass(theClass);
            }

should be something like this so that the property class itself is an interface:

                        if (!theClass.isInterface()) {
                                aFactory.setInterfaces(ObjectArrays.concat(theClass.getInterfaces(), EmpireGenerated.class));
                                aFactory.setSuperclass(theClass);
                        } else {
                                aFactory.setInterfaces(ObjectArrays.concat(theClass, ObjectArrays.concat(theClass.getInterfaces(), EmpireGenerated.class)));
                        }

with that change, one gets:

groovy:000> child1 = em.find(Child, "http://example.org/c1")
===> http://example.org/c1
groovy:000> parent1 = child1.isChildOf[0]
===> http://example.org/p1
groovy:000> parent1 instanceof Parent
===> true
groovy:000> parent1.class
===> class au.edu.qimr.grafli.generated.Parent_$$_javassist_0
groovy:000> parent1.class.interfaces
===> [interface au.edu.qimr.grafli.generated.Parent, interface com.clarkparsia.empire.SupportsRdfId, interface com.clarkparsia.empire.EmpireGenerated, interface javassist.util.proxy.ProxyObject]

see c011e1b

I would like to write test cases for this issue and #106 before putting in fix pull request; any pointers on how to make a TC using some classes and data that I define? I've had a bit of a look at the existing tests but any general tips would be appreciated

thanks!

It looks like you've pretty much got the test case covered between Child, Parent, and the data snippet. the groovy scripts are probably the reasonable body of the test case. beyond the obvious tests that i write, many are ad hoc re-creations of issues reported on the list and there's no real pattern to them.

one day i'd like to put a bit more test util in place to make it easier.

fixed w/ #108