Java developers are fortunate because in Jena they have one of the better RDF frameworks available (see Resources). Jena provides an API for writing and reading RDF, which can be stored and persisted in various ways. If you aren't yet familiar with Jena, I strongly recommend you read Philip McCarthy's "Introduction to Jena" before you read this article.
Jena is engineered to work well with RDF data models, just as JDBC is suited for working with relational models. Database applications consist largely of code written to save Java objects, as well as code written to marshal objects from the database. Semantic Web applications written in Java code face a similar problem: they must either convert Java objects to RDF or convert RDF to objects. As a consequence, developers must write a considerable amount of code to bridge the gap between their own model (usually JavaBeans) and Jena's RDF-centric API.
This article shows you how the Jenabean Java-to-RDF binding framework (see Resources) helps simplify this process and reduce the amount of code you need to write. You'll examine some example Jena client code and compare it to Jenabean's JavaBean-based programming model. Starting with a simple example, I'll show you how to:
- Save a bean as RDF
- Bind its properties to specific RDF properties
- Relate it to other objects
- Read it back out again
The Jenabean programming model
Consider the simple example RDF in Listing 1, which is in Notation3 (N3) format for readability (see Resources):
Listing 1. Example RDF (in N3 format)
<http://www.ibm.com/developerworks/xml/library/j-jena/>
a dc:Article ;
dc:creator "Philip McCarthy"^^xsd:string ;
dc:subject "jena, rdf, java, semantic web"^^xsd:string ;
dc:title "Introduction to Jena"^^xsd:string .
|
Listing 1 declares that the article "Introduction to Jena" was written by Philip McCarthy and covers the topics jena, rdf, java, and the semantic web. The vocabulary is part of the Dublin Core metadata taxonomy (see Resources). To duplicate these RDF statements using Jena's raw Java API, you might do something like Listing 2:
Listing 2. Asserting example RDF using raw Jena API
String NS = "http://purl.org/dc/elements/1.1/";
OntModel m = createModel();
OntClass articleCls = m.createClass(NS +"Article");
Individual i = articleCls.createIndividual(
"http://www.ibm.com/developerworks/xml/library/j-jena/");
Property title = m.getProperty(NS + "title");
Literal l = m.createTypedLiteral("Introduction to Jena");
i.setPropertyValue(title,l);
Property creator = m.getProperty(NS + "creator");
l = m.createTypedLiteral("Philip McCarthy");
i.setPropertyValue(creator,l);
Property subject = m.getProperty(NS + "subject");
l = m.createTypedLiteral("jena, rdf, java, semantic web");
i.setPropertyValue(subject,l);
m.write(System.out, "N3");
|
Each value assertion in Listing 2 requires three lines to:
- Access the property
- Create a typed literal
- Assert the property relationship
This code's advantage is that it maps directly to RDF concepts in a transparent and explicit way. The situation is similar to what you'd see in JDBC client code, wherein the API applies directly to the relational model.
If you spend much time with Jena, you'll begin to realize that a gap exists between what's considered to be normal object-oriented code and your client code that interacts with the Jena API. Instead of setting properties, you're asserting edges in a graph.
Listing 3 shows the same assertions made with Jenabean:
Listing 3. Creating the same assertions using Jenabean
Model m = _
Bean2RDF writer = new Bean2RDF(m);
Article article = new Article("http://www.ibm.com/developerworks/xml/library/j-jena/");
article.setCreator("Philip McCarthy");
article.setTitle("Introduction to Jena");
article.setSubject("jena, rdf, java, semantic web");
writer.save(article);
m.write(System.out, "N3");
|
I included the m.write(...,"N3") line as encouragement. Try it for yourself with the code included with this article (see Download).
Listing 2 and Listing 3 differ significantly but result in almost identical changes to the Jena model. They both create a superset of the triples from Listing 1. Listing 2 declares a new article by calling m.createClass(...). Listing 3 accomplishes the same objective by creating a new instance of the Article class
—
new Article(...). Without Jenabean, each property
assertion requires you to access the property from the model, create a literal, and assert the new statement into the model by calling setPropertyValue(...). Using Jenabean's Bean2RDF converter, you simply call the JavaBean's setter methods. Listing 3 results in code that's natural to most Java developers.
Jenabean's primary advantage, as you've seen, is in bridging the gap between Java objects and RDF. This allows you to code Semantic Web applications in a familiar OOP style using the same beans you use in your domain model. But that's not to say that Java objects and RDF are one and the same. Objects and RDF represent data in different ways. A good binding tool must solve the following three problems.
The object-to-RDF impedance mismatch
Often developers are keen simply to replicate an ontology as a set of Java objects. I find that a one-to-one correlation between Java objects and OWL classes doesn't really exist. OWL permits multiple inheritance, allows for many classes to share the same property, and allows properties themselves to inherit from one another. Again, just like relational database management systems (RDBMS), objects and RDF are similar in some ways and different in others. That still leaves us needing to produce and consume RDF from within Java code. Many tools take the code-generation approach, which involves reading an RDF schema or OWL ontology and replicating the classes and properties verbosely in the Java language. Such tools lack the capability to let you assert common RDF properties from existing JavaBean classes. RDF binding tools must let you arbitrarily relate a JavaBean property to an RDF property URI (assuming they are type compatible).
Another problem any binding tool needs to solve is how much of an object graph to persist. Saving an object in a dense, heavily connected object graph could result in persisting the entire model. Loading presents a similar situation. The binding tool must prevent loading the full RDF model into memory as JavaBeans but also allow for this as a feature when necessary.
Cyclic relationships are a common situation in RDF and in object models. When persisting and loading, the binding tool must be able to detect and handle cycles. Obviously, it should prevent never-ending loops, and it should detect previously loaded objects and save time by reusing them.
Jenabean solves these three issues while trying to keep the process as simple as possible. It doesn't require a code-generation phase, a bytecode-generation step, or a run-time bytecode-manipulation library. It's only dependency is on Jena and its libraries. Jenabean defaults to a conservative policy of saving the object with its direct singular properties. Developers must indicate they intentionally desire a "deep" copy in order to save the object, its relatives, and so on fully. Alternatively, you can specify that a particular collection property of the instance be saved or loaded.
Creating JavaBeans for your RDF
If you are familiar with Hibernate or other binding tools, you may be wondering where the magic happens. Jenabean uses Java annotations to declare how beans are mapped to RDF. Just like other annotation-based binding layers, Jenabean is most useful when your model is being driven from Java objects. Obviously this is not necessarily always the case, but when it is, Jenabean is ready to help.
Jenabean has many features for customizing how beans are serialized as RDF, but if you
are happy with the defaults, you can begin writing and reading beans quickly. Let's create a simple example JavaBean that fulfills all the necessary requirements. Just as with the Java Persistence API (JPA) or Hibernate, you need to ensure that your objects have a unique ID. Jenabean requires you to add a single annotation —
@Id
— to at least one bean field, which acts as the unique identifier. Listing 4 shows this simple bean:
Listing 4. A simple bean ready for saving
package example;
import thewebsemantic.Id;
public class Person {
private String email;
@Id
public String getEmail() { return email;}
public void setEmail(String email) { this.email = email;}
}
|
Listing 4 provides enough information for Jenabean to save and load instances of the Person class reliably. You don't need to extend anything or write an XML descriptor file. An e-mail address is an effective ID because it is unique. Listing 5 shows how you save an instance of Person to the Jena model:
Listing 5. Saving an instance of the
Person class, with resulting RDF
Model m = ModelFactory.createOntologyModel();
Bean2RDF writer = new Bean2RDF(m);
Person p = new Person();
p.setEmail("person@example.com");
writer.save(p);
m.write(System.out, "N3");
...
<http://example/Person>
a owl:Class ;
<http://thewebsemantic.com/javaclass>
"example.Person" .
<http://example/Person/taylor_cowan@yahoo.com>
a <http://example/Person> ;
<http://example/email>
"taylor_cowan@yahoo.com"^^xsd:string .
|
Bean2RDF is a Jenabean class that writes objects as RDF. It defaults to shallow mode, which means it saves the instance and its singular properties. If a Person class hasn't been added to the model yet, it asserts the new class as an instance of owl:Class. Notice in Listing 5 that Jenabean uses the example package as the namespace for the new ontology class. The second assertion is an annotation that indicates the Java class that was used to create the individual. The Person instance along with its e-mail address is asserted as well. Jenabean helps by first creating a URI for the saved instance. It also takes notice of the e-mail property and asserts it as a string literal value.
Retrieving JavaBeans from a Jena model
Individuals represented in RDF need a URI, whereas Java developers tend to think in terms of unique IDs. Jenabean assists you by appending the namespace (in this case defaulted from the package and class name) with the declared ID field. Once it's created, you can load information back from the model using RDF2Bean:
RDF2Bean reader = new RDF2Bean(m);
Person p2 = reader.load(
Person.class,"person@example.com"); |
Jenabean also loads all instances of Person:
Collection<Person> people = reader.load(Person.class); |
These are the easiest ways to access beans in your model. Jenabean also has support for binding to a SPARQL (SPARQL Query Language for RDF) result. To summarize, Jenabean at a minimum requires bean authors to indicate which field holds a value that is unique for all instances of that type. When saved, the bean's class and properties are given default URIs based on the class's package and name. This allows you to begin creating RDF quickly from the Java tier without much trouble.
Specifying namespaces and properties
So far, I've shown how Jenabean can create default URIs for you based upon your bean's classpath and name. Jenabean also has support for declaring which namespaces you'd like to work with. You can map your beans to particular namespaces with the @Namespace annotation. For example purposes, I'll use a namespace that I know isn't already taken, Jenabean's own project URL:
@Namespace("http://jenabean.googlecode.com/")
public class Person { _
<http://jenabean.googlecode.com/Person/person@example.com>
a <http://jenabean.googlecode.com/Person> ;
|
Notice that instead of the default package, the Person class
and its properties are given a new namespace that matches the namespace I provided as an argument to the @Namespace annotation. By default, this namespace will be used for the class and its properties.
In the world of RDF, it's advisable to use common properties; otherwise, a Semantic Web
will never develop. By using well-known properties, you make your data more semantic and
familiar to others. If a spider (of the Web-crawling variety) encountered the RDF
snippet I generated from the Jenabean project URL namespace, it wouldn't be able to make anything of it. But you can modify the bean just a bit with a more common and well-known assertion. The FOAF (Friend of a Friend) language (see Resources), a common vocabulary for linking people, has a special property for e-mail addresses: foaf:mbox. All your need to do now is use the @RdfProperty annotation on the Person bean:
@Id
@RdfProperty("http://xmlns.com/foaf/0.1/mbox")
public String getEmail() { return email;}
<http://xmlns.com/foaf/0.1/mbox>
"person@example.com"^^xsd:string .
|
Now the email property asserts itself as a foaf:mbox, which will be understood as an e-mail address by other RDF agents interested in your data.
In the world of OWL and RDF, relationships of various cardinalities are expressed through multiple assertions of the same property. Jenabean makes this fairly easy using the java.util.Collection interface. Listing 6 extends the Person class to support friends (in a loose foaf:knows kind of way):
Listing 6. Extending
Person to support friend relationships
public Collection<Person> friends = new
LinkedList<Person>();
@RdfProperty("http://xmlns.com/foaf/0.1/knows")
public Collection<Person> getFriends() { return friends;}
public void setFriends(Collection<Person> friends) { this.friends = friends;}
|
This is nothing fancy — just a new field called friends of type Collection<Person>, with associated get and set methods. Now you can create a Person and several friends and relate them together by adding each friend to the friends collection. The @RdfProperty annotation is optional but important if you want to bind to existing third-party vocabularies. The annotation specifies that you'd like the "friend" property to bind to the foaf:knows RDF property within your Jena model. Listing 7 shows how to use traditional JavaBean techniques to create the friend relationships:
Listing 7. Expressing friend relationships, with resulting RDF
Model m = ModelFactory.createOntologyModel();
Bean2RDF writer = new Bean2RDF(m);
Person p = new Person();
p.setEmail("person@example.com");
Person f1 = new Person();
f1.setEmail("friend1@example.com");
Person f2 = new Person();
f2.setEmail("friend2@example.com");
p.getFriends().add(f1);
p.getFriends().add(f2);
writer.save(p); // modifies the Jena model
m.write(System.out, "N3");
...
foaf:knows
jb:friend2@example.com, jb@friend1@example.com .
|
After the simple Person bean is saved via Bean2RDF.write(...), the model contains new data conforming to the FOAF specification.
In RDF, ordering of child nodes is undefined, so you can't assume that Jena will retrieve the list of friends in any particular order. If you need ordered lists, Jenabean maps Java arrays to RDF sequences. To demonstrate, I'll give the Person class a set of telephone numbers represented as an array of Strings, as in Listing 8:
Listing 8. Example array property with resulting RDF
private String[] phoneNumbers;
public String[] getPhoneNumbers() { return phoneNumbers;}
public void setPhoneNumbers(String[] phoneNumbers) {
this.phoneNumbers = phoneNumbers;}
jb:phoneNumbers
[ a rdf:Seq ;
rdf:_1 "321-321-3210"^^xsd:string ;
rdf:_2 "321-321-3211"^^xsd:string
] ;
|
Jenabean helps you get started reading and writing RDF using the familiar JavaBean model. I've covered the basics: how to create a simple bean, save it, and read it back out. This is only a small part of the full story for writing a Semantic Web application in the Java language. Visit the Jenabean project's home page to learn more, provide feedback, or ask for help (see Resources).
| Description | Name | Size | Download method |
|---|---|---|---|
| Code samples for this article | j-jenbean.zip | 4KB | HTTP |
Information about download methods
Learn
-
"Introduction to Jena" (Philip McCarthy, developerWorks, June 2004): The article introduces Jena, HP Lab's open source Semantic Web framework for Java programmers.
-
"The future of the Web is Semantic" (Naveen Balani, developerWorks, October 2005): Explore the basics of Semantic Web technologies.
-
Jena: Visit the Jena project.
-
Jenabean project home: You'll find Jenabean documentation and examples here.
-
"Thinking XML: Introducing N-Triples" (Uche Ogbuji, developerWorks, April 2003): Introduction to the N3 RDF format used in this article.
-
Dublin Core Metadata Initiative: The Dublin Core initiative is developing interoperable online metadata standards that support a broad range of purposes and business models.
-
FOAF Vocabulary Specification:The FOAF vocabulary is useful for describing people and acquaintances.
-
Browse the technology bookstore for books on these and other technical topics.
-
developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.
Get products and technologies
-
Jenabean: Download Jenabean.
Discuss
-
Check out developerWorks blogs and get involved in the developerWorks community.
Comments (Undergoing maintenance)





