rdfsemantic-websesamelinked-dataopenrdf

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


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" .

Solution

  • 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.