Go to most recent revision | Compare with Previous | Blame | View Log
----------------------------------------
Object Repository
----------------
James Leigh
----------------
Feb 2012
Object Repository
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. The Object Repository may also
optionally be configured with a BLOB store, to store information-resources.
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 and information-resources. 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.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
may be 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 public or protected methods that have a @Bind 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. Public and protected methods with this annotation will be
overridden with an optimized object query execution. The parameters with
an @Bind 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());
}
import org.openrdf.annotations.Sparql;
import org.openrdf.annotations.Bind;
// 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}")
public Document findDocumentByTitle(@Bind("title") String title) {
return null;
}
// retrieve a Document by title using a named query
ValueFactory vf = con.getRepository().getValueFactory();
URI myQueryID = vf.createURI("http://meta.leighnet.ca/rdf/2011/my-query");
NamedQuery named = con.getRepository().createNamedQuery(myQueryID,
"PREFIX gs:<http://meta.leighnet.ca/rdf/2009/gs#>\n"+
"SELECT ?doc WHERE {?doc gs:title ?title}");
ObjectQuery query = con.prepareObjectQuery(named.getQueryString());
query.setObject("title", "Getting Started");
Document doc = query.evaluate(Document.class).singleResult();
// 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(Document.class).singleResult();
+--
Implementing Inverse Properties
To simulate inverse properties in Java use named SPARQL queries for the Java getter and setter.
<<Figure 8. Inverse Property>>
+--
@Iri(FOAF + "Person")
public interface Person {
@Iri(FOAF + "depiction")
Image getDepiction();
@Iri(FOAF + "depiction")
void setDepiction(Image depiction);
}
@Iri(FOAF + "Image")
public interface Image {
@Sparql(PREFIX + "SELECT ?person { ?person foaf:depiction $this }")
Person getDepicts();
@Sparql(PREFIX + "DELETE { ?p foaf:depiction $this }\n"+
"INSERT { $person foaf:depiction $this } WHERE {} ")
void setDepicts(@Bind("person") Person person);
}
+--
Collections
In RDF the most natural collection is an unordered set, or non-functional
property. Sets are triples that share the same subject and predicate.
these non-functional properties should have a java.util.Set property type in
Java.
Ordered collections in RDF include rdf:List, rdfs:Container, rdf:Seq, rdf:Alt,
and rdf:Bag. AliBaba provides a java.util.List interface for each of these
resource types. However, all ordered collections in the RDF store must include
an rdf:type on the root node. Often RDF formats that include syntax sugar for
rdf:List do not include a rdf:type and may not be readable in AliBaba. To add
the missing add the triple rdf:type rdf:List using the add statement method of
the ObjectConnection. Other Java collections that implement java.util.List are
mapped to rdfs:Container when merged into the store.
Most RDF stores (like SQL databases) are not optimized for generic ordered
collections. Developers will find unordered collections have significantly
reduce I/O and better performance. If the elements of an ordered collection
will only exist in (at most) one ordered collection, it is recommended instead
to use a typed unordered collection (functional property) and included a
functional index member property on the elements. The elements can then be
sorted in memory when necessary.
Figure 9 show an example of using an unordered collection with an explicit
result order. The method getOrderedChildren() will order the nodes in the RDF
store (often in memory), while the method getSortedChildren() will sort them in
Java. In both cases calling java.util.List#add(Object) has no effect on the RDF
store.
<<Figure 9. Unordered Collection with element index>>
+--
import org.openrdf.annotations.Iri;
@Iri(NS + "Node")
public interface Node {
@Iri(NS + "child")
Set<Node> getChildren();
@Iri(NS + "child")
void setChildren(Set<Node> children);
@Iri(NS + "position")
Integer getPosition();
@Iri(NS + "position")
void setPosition(Integer position);
@Sparql(PREFIX
+ "SELECT ?child { $this ex:child ?child . ?child ex:position ?position }\n"
+ "ORDER BY ?position")
List<Node> getOrderedChildren();
List<Node> getSortedChildren();
}
public abstract class NodeSupport implements Node {
public List<Node> getSortedChildren() {
Set<Node> live = getChildren();
List<Node> memory = new ArrayList<Node>(live);
Collections.sort(memory, new Comparator<Node>() {
public int compare(Node o1, Node o2) {
Integer p1 = o1.getPosition();
Integer p2 = o2.getPosition();
if (p1 == p2)
return 0;
if (p1 == null)
return -1;
if (p2 == null)
return 1;
return p1.compareTo(p2);
}
});
return memory;
}
}
+--
Aspect-Oriented Programming
AliBaba allows any method call (including getters and setters) to be used as
join-points for cross cutting concerns. Figure 11 show the concept interface
Person (with an @Iri annotation) which defines a join-point
setDepiction(Image):void and the abstract behaviour class PersonSupport
(implements a concept interface) defines an aspect for that join-point by using
the same method name, parameter types, and return type. Other classes may
implement their own aspects using the same join-point and would be executed
serially until all are executed or a non-null (nor 0 nor false) response is given.
For more control over the execution order of aspects, AliBaba
provides the annotation @Precedes, which can be placed on a behaviour class
with a list of other behaviour classes, who's aspects should not be executed
before the aspects of this annotated behaviour class. Aspects can also intercept
method executions by using the annotation @ParameterTypes when declaring the
aspect. The annotation should list the parameter types of the join-point, while
the aspect parameter type is one of ObjectMessage, BooleanMessage, ByteMessage,
CharacterMessage, DoubleMessage, FloatMessage, IntegerMessage, LongMessage,
ShortMessage, and VoidMessage corresponding to the object or primitive return type of the
aspect/join-point. When the aspect is executed the parameters of the method
call and the return type (thus far) are available through one of the previously
listed message interfaces. Figure 11 shows an example of an aspect, which
conditionally changes the response of a method call to ensure it is never null.
<<Figure 11. Intercept method call>>
+--
@Iri(FOAF + "Person")
public interface Person {
@Iri(FOAF + "depiction")
Image getDepiction();
@Iri(FOAF + "depiction")
void setDepiction(Image depiction);
}
public abstract class PersonSupport implements Person, RDFObject {
@ParameterTypes({})
public Image getDepiction(ObjectMessage msg) throws RepositoryException {
Image depiction = (Image) msg.proceed();
if (depiction == null) {
return (Image) getObjectConnection().getObject(DEFAULT_IMAGE_URI);
}
return depiction;
}
void setDepiction(Image depiction) {
// With @ParameterTypes is not used call automatically proceeds
System.out.println("setDepiction called with: " + depiction);
}
}
+--
Information Resources (BLOBs)
The method getBlobObject can be used to retrive an FileObject interface of an
information resource by URI. The FileObject interface includes methods to open
an InputStream and an OutputStream and will be stored by URI to the configured
directory. Changes to the blobs will be isolated from other connections until
the changes are committed (if not in autoCommit mode).
The delegate BlobStore can be set using the setBlobStore method of the
ObjectRepository before use. BlobStores are created using the BlobStoreFactory
openBlobStore(File) method.
Generating Concepts
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 12. 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
Rollback if multiple states observed (enforce snapshot)? (false|true) [false]:
Rollback if outdated state observed (enforce serializable)? (false|true) [false]:
Changeset namespace [urn:trx:localhost:]:
Archive all removed data (false|true) [false]:
If not, archive transactions with removed triples less than [100]:
Minimum recent transactions [100]:
Maximum recent transactions [1000]:
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 13 is jrunscript (in
JavaScript) that outputs a new FOAF file, demonstrating how RDF/Objects
can be used without compiling Java files.
<<Figure 13. 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> con.setAutoCommit(false)
js> var Person = "http://xmlns.com/foaf/0.1/Person"
js> var base = "http://example.com/person/"
js> var james = con.addDesignation(con.getObject(base+"james"), Person)
js>
js> james.foafFirstName.add("James")
js> james.foafSurname.add("Leigh")
js> james.foafInterest.add("RDF")
js> var arjohn = con.addDesignation(con.getObject(base+"arjohn"), Person)
js> arjohn.foafFirstName.add("Arjohn")
js> james.foafKnows.add(arjohn)
js>
js> con.setNamespace("foaf", "http://xmlns.com/foaf/0.1/")
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()
+--
Message Vocabulary
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 and implementations. These messages
can be created by extending the class msg:Message and creating restrictions for
its msg:target and response properties (msg:object msg:objectSet msg:literal
msg:literalSet). Implementations can be created in ECMA script, sparql, or
xslt. Parameters (message properties) and the variable "this" is available in
the implementations as (prefix + initcap local part) of the property URI or
just the local part if no prefix is defined. msg:script code also has the
method proceed() available to execute other implementations of the message. The
last parameter (alpha sorted) is the body input of an msg:xslt message. Show in
Figure 14 is a sample of what a message might look like in turtle.
<<Figure 14. 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 msg:<http://www.openrdf.org/rdf/2011/messaging#>.
@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
:GetCurrentTime rdfs:subClassOf msg:Message;
rdfs:subClassOf [owl:onProperty msg:literal; owl:allValuesFrom xsd:dateTime];
msg:imports <java:javax.xml.datatype.DatatypeFactory>;
msg:imports <java:java.util.GregorianCalendar>;
msg:script """
var 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 parameter when and the previous message to compute a new value
:GetDurationSince rdfs:subClassOf msg:Message;
rdfs:subClassOf [owl:onProperty msg:literal; owl:allValuesFrom xsd:duration];
msg:imports <java:javax.xml.datatype.DatatypeFactory>;
msg:script """
var df = DatatypeFactory.newInstance();
var now = this.GetCurrentTime().toGregorianCalendar().getTimeInMillis();
var since = when.toGregorianCalendar().getTimeInMillis();
return df.newDuration(now - since);
""".
:when a owl:DatatypeProperty; a owl:FunctionalProperty;
rdfs:domain :GetDurationSince;
rdfs:range xsd:dateTime.
# Message for the age of a Mammal
# This message is written in ECMA script and uses a compact syntax
:GetCurrentAge rdfs:subClassOf msg:Message;
rdfs:subClassOf [owl:onProperty msg:target; owl:allValuesFrom :Mammal];
rdfs:subClassOf [owl:onProperty msg:literal; owl:allValuesFrom xsd:int];
msg:script "return this.GetDurationSince(this.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
# The proceed() method is used to call and return the response from intercepted methods
:GetCurrentDogAge owl:intersectionOf (:GetCurrentAge [owl:onProperty msg:target; owl:allValuesFrom :Dog]);
msg:script "return proceed() * 7".
# Intercepts the age message and checks if the mammal is less then a year old
:GetCurrentAgeInMonths owl:equivalentClass :GetCurrentAge;
msg:script """
var year = proceed();
if (year < 2) {
var duration = this.GetDurationSince(this.dateOfBirth);
return duration.years * 12 + duration.months;
}
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 14, the connection in Figure 15 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 messages (or properties). If the schema changes at runtime the method ObjectConnection#recompileSchemaOnClose() should be called to compile the changes within the ObjectConnection#close() method.
<<Figure 15. 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]: mammals
Repository title [Native store]: Mammal Store
Rollback if multiple states observed (enforce snapshot)? (false|true) [false]:
Rollback if outdated state observed (enforce serializable)? (false|true) [false]:
Changeset namespace [urn:trx:localhost:]:
Archive all removed data (false|true) [false]:
If not, archive transactions with removed triples less than [100]:
Minimum recent transactions [100]:
Maximum recent transactions [1000]:
Triple indexes [spoc,posc]:
Max Query Time [0]:
Default Query Language [SPARQL]:
Ontology [http://www.w3.org/2002/07/owl]:
Read Schema from Repository [false]: true
Repository created
> open mammals.
Opened repository 'mammals'
mammals> load mammals.ttl.
Loading data...
Data has been added to the repository (3048 ms)
mammals> quit.
Closing repository 'mammals'...
Disconnecting from data
Bye
+--
<<Figure 16. Calling Object Messages from JavaScript>>
+--
$ jrunscript -J-Djava.ext.dirs=lib:dist
js> var rm = org.openrdf.repository.manager.RepositoryProvider.getRepositoryManager("data")
js> var repo = rm.getRepository("mammals")
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.GetCurrentAge()
35
js> mel.GetCurrentAge()
54
js> lucia.GetCurrentAge()
13
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.