How can I "nest" objects with OpenRDF Alibaba with assigned resource IRIs?

156 Views Asked by At

I am trying out OpenRDF Alibaba (associated with Sesame) as a tool to map Java objects to RDF triples and back again. Currently, I'm looking at how it handles object graphs.

I have two objects, Inner and Outer. Outer has a reference to Inner. When I persist an Outer-instance, it seems that the Inner-instance is aways represented as b-node, even if I've persisted the Inner-instance with an assigned IRI previously.

What do I have to do to be able to successfully assign the Inner-instance's IRI myself, instead of getting b-nodes created?

Extra credit question: how can I make the resource IRI a property on the Java object, rather than having it be parallel to but disconnected from the object it identifies?


Code:

Inner:

package alibabaeval.domain;

import org.openrdf.annotations.Iri;

@Iri("http://example.com/innerType")
public class Inner {

    @Iri("http://example.com/innerType/data")
    private String data;

    public Inner(String data) {
        this.data = data;
    }


    // if this is missing, an unhelpful ClassCastException will be thrown on retrieval
    public Inner() {
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

Outer:

package alibabaeval.domain;

import org.openrdf.annotations.Iri;

@Iri("http://example.com/outerType")
public class Outer {

    @Iri("http://example.com/outerType/data")
    private String outerData;

    @Iri("http://example.com/outerType/innerObject")
    private Inner innerObject;

    public Outer(String outerData) {
        this.outerData = outerData;
    }

    // if this is missing, an unhelpful ClassCastException will be thrown on retrieval
    public Outer() {
    }

    public String getOuterData() {
        return outerData;
    }

    public void setOuterData(String outerData) {
        this.outerData = outerData;
    }

    public Inner getInnerObject() {
        return innerObject;
    }

    public void setInnerObject(Inner innerObject) {
        this.innerObject = innerObject;
    }
}

Test Program:

package alibabaeval;

import org.junit.Test;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.QueryLanguage;
import org.openrdf.repository.Repository;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.config.ObjectRepositoryFactory;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFWriter;
import org.openrdf.rio.Rio;
import org.openrdf.sail.memory.MemoryStore;

import alibabaeval.domain.Inner;
import alibabaeval.domain.Outer;

public class AlibabaEval {

    public static void main(String[] args) throws Exception {
        Repository store = new SailRepository(new MemoryStore());
        store.initialize();

        // wrap in an object repository
        ObjectRepositoryFactory factory = new ObjectRepositoryFactory();
        ObjectRepository repository = factory.createRepository(store);

        // add a stuff to the repository
        ObjectConnection con = repository.getConnection();
        ValueFactory vf = con.getValueFactory();

        Inner inner = new Inner("some inner data");
        URI innerId = vf.createURI("http://example.com/inners/inner1");
        con.addObject(innerId, inner);

        URI outerId = vf.createURI("http://example.com/outers/outer1");
        Outer outer = new Outer("some outer data");
        outer.setInnerObject(inner);
        con.addObject(outerId, outer);

        // look at the triples that were created
        System.out.println("\n\n\nGenerated triples:");
        RDFWriter writer = Rio.createWriter(RDFFormat.NTRIPLES, System.out);
        con.prepareGraphQuery(QueryLanguage.SPARQL, "CONSTRUCT { ?s ?p ?o } WHERE {?s ?p ?o }").evaluate(writer);

        // close everything down
        con.close();
        repository.shutDown();
    }
}

Output:

I created only two object instances, and persisted them both separately. Alibaba seemed to ignore that, and created a second copy of the Inner-instance as a b-node for the reference from the Outer-instance.

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/me/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-jdk14/1.7.7/25d160723ea37a6cb84e87cd70773ff02997e857/slf4j-jdk14-1.7.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/me/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-log4j12/1.7.12/485f77901840cf4e8bf852f2abb9b723eb8ec29/slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.JDK14LoggerFactory]
Jan 08, 2016 6:00:21 PM org.openrdf.repository.object.managers.helpers.Scanner scan
INFO: Scanning C:\workspace\AlibabaTest\bin for concepts
Jan 08, 2016 6:00:22 PM org.openrdf.repository.object.ObjectRepository compileSchema
INFO: Compiling schema
Jan 08, 2016 6:00:22 PM org.openrdf.repository.object.composition.ClassResolver setBaseClassRoles
WARNING: Concept will only be mergable: class alibabaeval.domain.Inner
Jan 08, 2016 6:00:22 PM org.openrdf.repository.object.composition.ClassResolver setBaseClassRoles
WARNING: Concept will only be mergable: class alibabaeval.domain.Outer



Generated triples:
<http://example.com/inners/inner1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/innerType> .
<http://example.com/inners/inner1> <http://example.com/innerType/data> "some inner data" .
<http://example.com/outers/outer1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/outerType> .
_:node1a8hqu4aqx1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/innerType> .
_:node1a8hqu4aqx1 <http://example.com/innerType/data> "some inner data" .
<http://example.com/outers/outer1> <http://example.com/outerType/innerObject> _:node1a8hqu4aqx1 .
<http://example.com/outers/outer1> <http://example.com/outerType/data> "some outer data" .
2

There are 2 best solutions below

0
Jeen Broekstra On BEST ANSWER

The problem is that the id for Inner gets added to the store, but your actual Inner POJO does not get updated. To fix this, simply add a line inner = con.getObject(Inner.class, innerId); after the call to addObject.

FWIW the additional roundtrip penalty for this is not particularly severe as Alibaba does recent-access object caching - so it will not need to go all the way to the persistence layer for this lookup.

As for how to get the identifying resource back from the object itself: if you make sure your POJO implements the RDFObject interface, you can call getResource to retrieve the associated id. Your implementation of the getResource method can simply return null, by the way, as Alibaba will override the implementation in its generated object.

0
Kaypro II On

Now that I'm a little more familiar with Alibaba and after taking another read of the documentation, I see a better answer.

Alibaba will use the value returned by RDFObject.getResource() on a detached object. Here's an implementation of inner that does this:

package alibabaeval.domain;

import org.openrdf.annotations.Iri;
import org.openrdf.model.Resource;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.RDFObject;

@Iri("http://example.com/#innerType")
public class Inner implements RDFObject {

    @Iri("http://example.com/innerType/data")
    private String data;

    private Resource detachedId;

    public Inner() {

    }

    @Override
    public ObjectConnection getObjectConnection() {
        // don't really care about this one right now
        return null;
    }

    @Override
    public Resource getResource() {
        // only run on detached object, is hidden by a proxy on managed objects
        return detachedId;
    }

    public Resource getDetachedId() {
        return detachedId;
    }

    public void setDetachedId(Resource detachedId) {
        this.detachedId = detachedId;
    }

    public void setDetachedId(String detachedId) {
        this.detachedId = new URIImpl(detachedId);
    }

    public void setResource(Resource resource) {
        detachedId = resource;
    }

    public Inner(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

This allows you to do this:

    Inner inner = new Inner("some inner data");
    inner.setDetachedId("http://example.com/inners/inner1");

    URI outerId = vf.createURI("http://example.com/outers/outer1");
    Outer outer = new Outer("some outer data");
    outer.setInnerObject(inner);
    con.addObject(outerId, outer);

And get these triples:

<http://example.com/inners/inner1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/#innerType> .
<http://example.com/inners/inner1> <http://example.com/innerType/data> "some inner data" .
<http://example.com/outers/outer1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/#outerType> .
<http://example.com/outers/outer1> <http://example.com/outerType/data> "some outer data" .
<http://example.com/outers/outer1> <http://example.com/outerType/innerObject> <http://example.com/inners/inner1> .

This technique also works with the single-parameter ObjectConnection.addObject(Object instance) method.

This also only works for detached objects. Instances managed by Alibaba will proxy the RDFObject.getResource() method, so you no longer have control over what it returns.