The Object Repository is an extension to the Sesame RDF Repository that allows an RDF store to function as an object store. It maps Java objects to and from RDF resources and OWL classes to Java classes in a non-intrusive manner that enables developers to work with resources stored in an RDF Repository as objects.
Sesame Repositories can be created using the console. Use the connect command to set the data directory before creating a repository using the create command. Once the repository has been created it can be accessed in Java through the RepositoryProvider's getRepositoryManager(dataDir) method, which takes the URI of the directory location that was used in the connect command of the console. Then the repository can be accessed using the getRepository(id) method of the returned RepositoryManager.
The ObjectRepository must be created through the ObjectRepositoryFactory, using the createRepository method, passing an existing Repository. Once the ObjectRepository is created it is like other Sesame RDF Repositories, with full triple access, but it returns a ObjectConnection in the getConnection method. The ObjectConnection is an extension of the RepositoryConnection and includes additional methods for working with objects. However, before objects can be used, the object classes must first be created and registered.
To create classes for the ObjectRepository add the @iri annotation to all classes and fields (or interfaces and property methods) that should be stored in the repository. Then create an empty 'META-INF/org.openrdf.concepts' file in the root directory (or JAR) of the annotated classes. Once the classes have been created, as shown in Figure 5, they can be used with new ObjectRepositories.
Figure 5. A Class Compatible with the ObjectRepository
// Document.java
import org.openrdf.repository.object.annotations.iri;
@iri(Document.NS + "Document")
public class Document {
public static final String NS = "http://meta.leighnet.ca/rdf/2009/gs#";
@iri(NS + "title") String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
To add an object to the ObjectRepository, create an ObjectConnection and call the addObject method (as shown in Figure 6). This method will recursively add all other objects referenced from annotated fields. The addObject method can either automatically create a unique identifier for the object (that might change over time), or add the object using a provided identifier, called a URI. It is recommended to use a URI for any object that might need to be referenced directly or has a conceptual identity, for all other objects, such as anonymous collections, an automatic identifier is good enough.
To retrieve an existing object, use the getObject(Class, Resource) method of the ObjectConnection. The method accepts a URI or an anonymous identifier. An anonymous identifier maybe different for different ObjectConnections and should only be used within a single ObjectConnection. A URI, however, will never change and can be used in any connection.
Removing an object is more difficult, as every property of the object will need to be removed, by setting the fields or properties to null. Furthermore, the type of the object must also be removed from the repository, this can be done using the removeDesignation method of the ObjectConnection.
Figure 6. Using an ObjectConnection
// create a Document
Document doc = new Document();
doc.setTitle("Getting Started");
// add a Document to the repository
ObjectConnection con = repository.getConnection();
ValueFactory vf = con.getValueFactory();
URI id = vf.createURI("http://meta.leighnet.ca/data/2009/getting-started");
con.addObject(id, doc);
// retrieve a Document by id
Document doc = con.getObject(Document.class, id);
// remove a Document from the repository
Document doc = con.getObject(Document.class, id);
doc.setTitle(null);
con.removeDesignation(doc, Document.class);
Objects can also be retrieved by their type using the getObjects(Class) method, which includes subclasses. More fine grained queries can be created using the @sparql annotation. This annotation should be placed on methods that have a @name annotation on their parameters and have a return type and parameters types of registered concepts or datatypes. The return type may also be a java.util.Set or Result of a concept or datatype and may also be Model and any query result, such as GraphQueryResult, TupleQueryResult, or boolean. Methods with this annotation will be overridden with an optimized object query execution. The parameters with an @name annotation will be available in the query in the variable name provided. The target object is available in the query using the variables name "this".
Dynamic queries can be constructed using the prepareObjectQuery method or one of the other prepareQuery methods. The prepareObjectQuery method returns an ObjectQuery that allows objects and their type to be assigned to variables within the query before execution.
Figure 7. Executing Queries
// retrieve all Documents
Result<Document> result = con.getObjects(Document.class);
while (result.hasNext()) {
out.println(result.next().getTitle());
}
// retrieve a Document by title using a named query
@sparql("PREFIX gs:<http://meta.leighnet.ca/rdf/2009/gs#>\n"+
"SELECT ?doc WHERE {?doc gs:title $title}")
Document findDocumentByTitle(@name("title") String title) {
return null;
}
// retrieve a Document by title using a dynamic query
ObjectQuery query = con.prepareObjectQuery(
"PREFIX gs:<http://meta.leighnet.ca/rdf/2009/gs#>\n"+
"SELECT ?doc WHERE {?doc gs:title ?title}");
query.setObject("title", "Getting Started");
Document doc = query.evaluate().singleResult();
Compatible class files can be created from RDFS/OWL files, for use with the ObjectRepository in Java, by using the provided owl-compiler.sh (or .bat) file, with main class org.openrdf.repository.object.compiler.Compiler. Use the '-h' option to review the available command line options.
When precompiled class files are not needed in advance, the ObjectRepository can compile them itself. When the AliBaba JARs are added to the console, additional repository templates are included to facilitate creating the ObjectRepository. These include object-memory and object-native (among others). When creating the repository with the console, it will prompt for an OWL Ontology file that should contain the classes and properties needed and/or reference them using owl:imports statements within the file.
Figure 8. Creating ObjectRepository from the Console
Commands end with '.' at the end of a line Type 'help.' for help > connect data. Disconnecting from default data directory Connected to data > create object-native. Please specify values for the following variables: Repository ID [native]: foaf Repository title [Native store]: FOAF Store Triple indexes [spoc,posc]: Max Query Time [0]: Default Query Language [SPARQL]: Ontology [http://www.w3.org/2002/07/owl]: http://xmlns.com/foaf/spec/index.rdf Read Schema from Repository [false]: Repository created > quit. Disconnecting from data Bye
Scripts can be streamlined by allowing the ObjectRepository to compile the ontology. Shown in Figure 9 is jrunscript (in JavaScript) that outputs a new FOAF file, demonstrating how RDF/Objects can be used without compiling Java files.
Figure 9. JRunScript and ObjectRepository
$ jrunscript -J-Djava.ext.dirs=lib:dist
js> var rm = org.openrdf.repository.manager.RepositoryProvider.getRepositoryManager("data")
js> var repo = rm.getRepository("foaf")
js> var con = repo.getConnection()
js> var vf = con.getValueFactory()
js> var foaf = "http://xmlns.com/foaf/0.1/"
js> var Person = vf.createURI(foaf, "Person")
js> var base = "http://meta.leighnet.ca/data/rdf/2009/foaf/"
js> var james = con.addDesignations(con.getObject(base+"james"), [Person])
js>
js> james.foafFirstNames.add("James")
js> james.foafSurnames.add("Leigh")
js> james.foafInterests.add("RDF")
js> var arjohn = con.addDesignations(con.getObject(base+"arjohn"), [Person])
js> arjohn.foafFirstNames.add("Arjohn")
js> james.foafKnows.add(arjohn)
js>
js> con.setNamespace("foaf", foaf)
js> con['export'](new org.openrdf.rio.rdfxml.RDFXMLWriter(java.lang.System.out), [])
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="http://meta.leighnet.ca/data/rdf/2009/foaf/james">
<rdf:type rdf:resource="http://xmlns.com/foaf/0.1/Person"/>
<foaf:firstName>James</foaf:firstName>
<foaf:surname>Leigh</foaf:surname>
<foaf:interest>RDF</foaf:interest>
<foaf:knows rdf:resource="http://meta.leighnet.ca/data/rdf/2009/foaf/arjohn"/>
</rdf:Description>
<rdf:Description rdf:about="http://meta.leighnet.ca/data/rdf/2009/foaf/arjohn">
<rdf:type rdf:resource="http://xmlns.com/foaf/0.1/Person"/>
<foaf:firstName>Arjohn</foaf:firstName>
</rdf:Description>
</rdf:RDF>
js> con.close()
In addition to the RDFS and OWL vocabulary, the object repository also supports its own vocabulary for describing messages. This vocabulary can be used to declare interface methods, implementations, and RDF triggers. These messages can be created by extending the class obj:Message and creating restrictions for its obj:target and response properties. Implementations can be created in groovy, sparql, or xslt. Show in Figure 10 is a sample of what a message might look like in turtle.
Figure 10. Sample Usage of Object Vocabulary
@prefix xsd:<http://www.w3.org/2001/XMLSchema#>.
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl:<http://www.w3.org/2002/07/owl#>.
@prefix obj:<http://www.openrdf.org/rdf/2009/object#>.
@prefix :<http://data.leighnet.ca/rdf/2009/example#>.
# Declare classes and properties for this example
:Mammal rdfs:subClassOf rdfs:Resource.
:Person rdfs:subClassOf :Mammal.
:Dog rdfs:subClassOf :Mammal.
:dateOfBirth a owl:DatatypeProperty; a owl:FunctionalProperty;
rdfs:domain :Mammal;
rdfs:range xsd:date.
# Common message that responds with the current date time
:now rdfs:subClassOf obj:Message;
rdfs:subClassOf [
owl:onProperty obj:functionalLiteralResponse;
owl:allValuesFrom xsd:dateTime];
obj:imports <java:javax.xml.datatype.DatatypeFactory>;
obj:imports <java:java.util.GregorianCalendar>;
obj:groovy """
DatatypeFactory df = DatatypeFactory.newInstance();
return df.newXMLGregorianCalendar(new GregorianCalendar());
""".
# Common message that take a date time and responds with the duration since then
# This message uses the previous message to compute a new value
:since rdfs:subClassOf obj:Message;
rdfs:subClassOf [
owl:onProperty obj:functionalLiteralResponse;
owl:allValuesFrom xsd:duration];
obj:imports <java:javax.xml.datatype.DatatypeFactory>;
obj:groovy """
DatatypeFactory df = DatatypeFactory.newInstance();
long now = now().toGregorianCalendar().getTimeInMillis();
long when = msg.getSinceWhen().toGregorianCalendar().getTimeInMillis();
return df.newDuration(now - when);
""".
:since-when a owl:DatatypeProperty; a owl:FunctionalProperty;
rdfs:domain :since;
rdfs:range xsd:dateTime.
# Message for the age of a Mammal
# This message is written in groovy and uses a more compact syntax
:age rdfs:subClassOf obj:Message;
rdfs:subClassOf [
owl:onProperty obj:functionalLiteralResponse;
owl:allValuesFrom xsd:int];
rdfs:subClassOf [
owl:onProperty obj:target;
owl:allValuesFrom :Mammal];
obj:groovy "return since(dateOfBirth).years".
# Dog's age is calculated differently than other mammals
# This message is a specialisation of the previous and overrides it for all dogs
:dog-age owl:intersectionOf (:age [owl:onProperty obj:target; owl:allValuesFrom :Dog]);
obj:preceds :age;
obj:groovy "return msg.objFunctionalLiteralResponse * 7".
# Intercepts the age message and checks if the mammal is less then a year old
:month-age owl:equivalentClass :age;
obj:preceds :age;
obj:groovy """
int year = msg.getObjFunctionalLiteralResponse();
if (year < 2) {
return since(getDateOfBirth()).getMonths();
}
return year;
""".
# Some sample data to test with
:jack a :Dog;
:dateOfBirth "2005-02-18"^^xsd:date.
:mel a :Person;
:dateOfBirth "1956-01-03"^^xsd:date.
:lucia a :Person;
:dateOfBirth "2009-10-30"^^xsd:date.
When an object repository is set to use the Ontology in Figure 10, the connection in Figure 11 can be used to evaluate the messages and calulate the age. Since an empty prefix was used in the ontology, no prefix is used when calling the messages (or properties). The annotation obj:name can be used to override the object message and property names.
Figure 11. Calling Object Messages from JavaScript
js> var con = repo.getConnection()
js> var jack = con.getObject("http://data.leighnet.ca/rdf/2009/example#jack")
js> var mel = con.getObject("http://data.leighnet.ca/rdf/2009/example#mel")
js> var lucia = con.getObject("http://data.leighnet.ca/rdf/2009/example#lucia")
js> jack.age()
28
js> mel.age()
53
js> lucia.age()
1
js> con.close()
The ObjectRepository simplifies interacting with RDF resources in OO languages on the JVM. By bridging RDF properties and object properties, creating and manipulating RDF resources is as easy as manipulating objects.