Recovered from the Wayback Machine.
While factories in the real world tend to be messy, ugly things polluting the environment, within Java, they’re wonderful creations that hide much of the implementation detail of Java interfaces. This is particularly obvious when we take a look at porting the third example from Chapter 8 in Practical RDF to the new Jena2.
(No worries about skipping – at the end of the series I’ll post a file that contains all of the converted applications.)
The third example introduces the implementation of two concepts: one having to do with RDF and RDF/XML – bnodes, or blank nodes; the other having to do with hiding some of the implementation details when working with a pre-defined RDF vocabulary in Jena – RDF vocabulary classes.
The Java code from the book for the third example is:
import com.hp.hpl.mesa.rdf.jena.mem.ModelMem; import com.hp.hpl.mesa.rdf.jena.model.*; import com.hp.hpl.mesa.rdf.jena.vocabulary.*; import com.burningbird.postcon.vocabulary.POSTCON; import java.io.FileOutputStream; import java.io.PrintWriter;
public class pracRDFThird extends Object {
public static void main (String args[]) {
// resource names String sResource = “http://burningbird.net/articles/monsters1.htm”; String sRelResource1 = “http://burningbird.net/articles/monsters2.htm”; String sRelResource2 = “http://burningbird.net/articles/monsters3.htm”;
try { // create an empty graph Model model = new ModelMem();
// create the resource // and add the properties cascading style Resource article = model.createResource(sResource) .addProperty(POSTCON.related, model.createResource(sRelResource1)) .addProperty(POSTCON.related, model.createResource(sRelResource2));
// create the bio bnode resource // and add properties Resource bio = model.createResource() .addProperty(DC.creator, model.createLiteral(“Shelley Powers”)) .addProperty(DC.publisher, model.createLiteral(“Burningbird”)) .addProperty(DC.title, model.createLiteral(“Tale of Two Monsters: Legends”, “en”));
// attach to main resource article.addProperty(POSTCON.bio, bio);
// Print RDF/XML of model to system output model.write(new PrintWriter(System.out));
} catch (Exception e) { System.out.println(“Failed: ” + e); } } }
This application code uses the cascade style of piggy backing instantiation of objects that are then passed to the function calls that instantiate other objects and so on. This is an effective technique to use if you’re not re-using interim objects.
Note also that the code creates a new resource that does not have a specific URI because it’s a blank node:
Resource bio
= model.createResource()
As you’ll remember from the book – you do have the book, now, don’t you? – a blank node is a resource where a URI is not meaningful, or doesn’t yet exist. In this example, the bio section doesn’t have a meaningful URI, yet. Once created, the bnode resource is then attached directly to the model’s top-level resource using the predicate, ‘bio’. Later we’ll see what the bnode looks like as generated RDF/XML.
One major change with this code from the previous example covered in the last post is that it uses another class, POSTCON, for the PostCon vocabulary elements. Though not a requirement, using a vocabulary has several advantages, including code reuse and modularization, as well as hiding some of the implementation characteristics. The Jena development team used this approach with several existing and widespread vocabularies, including DC, DCTERMS, and VCARD – all of which are included with the Jena installation.
The original code for the PostCon vocabulary class is:
package com.burningbird.postcon.vocabulary;
import com.hp.hpl.mesa.rdf.jena.common.ErrorHelper; import com.hp.hpl.mesa.rdf.jena.common.PropertyImpl; import com.hp.hpl.mesa.rdf.jena.common.ResourceImpl; import com.hp.hpl.mesa.rdf.jena.model.Model; import com.hp.hpl.mesa.rdf.jena.model.Property; import com.hp.hpl.mesa.rdf.jena.model.Resource; import com.hp.hpl.mesa.rdf.jena.model.RDFException;
public class POSTCON extends Object {
// URI for vocabulary elements protected static final String uri = “http://burningbird.net/postcon/elements/1.0/”;
// return URI for vocabulary elements public static String getURI() { return uri; }
// define the property labels and objects static final String nbio = “Bio”; public static Property bio = null; static final String nrelevancy = “Relevancy”; public static Property relevancy = null; static final String npresentation = “Presentation”; public static Resource presentation = null; static final String nhistory = “history”; public static Property history = null; static final String nmovementtype = “movementType”; public static Property movementtype = null; static final String nreason = “reason”; public static Property reason = null; static final String nstatus = “currentStatus”; public static Property status = null; static final String nrelated = “related”; public static Property related = null; static final String ntype = “type”; public static Property type = null; static final String nrequires = “requires”; public static Property requires = null;
// define the resources static final String nresource = “Resource”; public static Resource resource = null; static final String nmovement = “Movement”; public static Resource movement = null;
// instantiate the properties and the resource static { try {
// instantiate the properties bio = new PropertyImpl(uri, nbio); relevancy = new PropertyImpl(uri, nrelevancy); presentation = new PropertyImpl(uri, npresentation); history = new PropertyImpl(uri, nhistory); related = new PropertyImpl(uri, nrelated); type = new PropertyImpl(uri, ntype); requires = new PropertyImpl(uri, nrequires); movementtype = new PropertyImpl(uri, nmovementtype); reason = new PropertyImpl(uri, nreason); status = new PropertyImpl(uri, nstatus);
// instantiate the resources resource = new ResourceImpl(uri+nresource); movement = new ResourceImpl(uri+nmovement);
} catch (RDFException e) { ErrorHelper.logInternalError(“POSTCON”, 1, e); } } }
What a mess – all the implementation details for the interfaces are exposed, making for extra work and minimizing readability of the code. But all of that’s changed now in Jena2.
With Jena2, rather than using a series of …Impl classes, and having to implement the RDF class interfaces directly, the actual implementation of the interfaces occurs deep down inside the guts of Jena, through factory objects. This is where this type of implementation should occur so that we can focus on developing RDF applications, rather than on Java implementation language details.
Compare the POSTCON vocabulary class in Jena1 with the new one, defined using Jena2 functionality:
package com.burningbird.postcon.vocabulary;
import com.hp.hpl.jena.rdf.model.*;
public class POSTCON {
// URI for vocabulary elements protected static final String uri = “http://burningbird.net/postcon/elements/1.0/”;
// return URI for vocabulary elements public static String getURI() { return uri; }
private static Model model = ModelFactory.createDefaultModel();
// define the property labels and objects public static final Property bio = model.createProperty(uri + “bio”); public static final Property relevancy = model.createProperty(uri + “relevancy”); public static final Property presentation = model.createProperty(uri + “presentation”); public static final Property history = model.createProperty(uri + “history”); public static final Property movementtype = model.createProperty(uri + “movementType”); public static final Property reason = model.createProperty(uri + “reason”); public static final Property status = model.createProperty(uri + “status”); public static final Property related = model.createProperty(uri + “related”); public static final Property type = model.createProperty(uri + “type”); public static final Property requires = model.createProperty(uri + “requires”);
// define the resources public static final Resource Resource = model.createResource(uri + “Resource”); public static final Resource Movement = model.createResource(uri + “Movement”); }
The difference is significant, and greatly improved. Another approach to use could be to use the ResourceFactory object directly to create the individual items:
public static final Property requires = ResourceFactory.createProperty(uri + “requires");
public static final Resource Resource = ResourceFactory.createResource(uri + “Resource");
Looking through the source code for the vocabulary classes included with Jena, I see both approaches being used. Once the new POSTCON vocabulary class has been compiled, we can look at porting the rest of the third example from the book.
Returning to the code for the third example, the only changes that need to occur now to make this work with Jena2 is to use the ModelFactory to create the model, and update the code to use the new outputstream class, as discussed in the previous posting. The updated code now becomes:
import com.burningbird.postcon.vocabulary.POSTCON;
public class pracRDFThird extends Object{
public static void main (String args[]) {
// resource names String sResource = “http://burningbird.net/articles/monsters1.htm”; String sRelResource1 = “http://burningbird.net/articles/monsters2.htm”; String sRelResource2 = “http://burningbird.net/articles/monsters3.htm”;
try { // create an empty graph Model model = ModelFactory.createDefaultModel();
// create the resource // and add the properties cascading style Resource article = model.createResource(sResource) .addProperty(POSTCON.related, model.createResource(sRelResource1)) .addProperty(POSTCON.related, model.createResource(sRelResource2));
// create the bio bnode resource // and add properties Resource bio = model.createResource() .addProperty(DC.creator, “Shelley Powers”) .addProperty(DC.publisher, “Burningbird”) .addProperty(DC.title, model.createLiteral(“Tale of Two Monsters: Legends”, “en”));
// attach to main resource article.addProperty(POSTCON.bio, bio);
// Print RDF/XML of model to system output RDFWriter writer = model.getWriter(); writer.write(model,System.out, null);
} catch (Exception e) { System.out.println(“Failed: ” + e); } } }
The resulting RDF/XML is:
<rdf:RDF
xmlns:j.0="http://burningbird.net/postcon/elements/1.0/”
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#”
xmlns:dc="http://purl.org/dc/elements/1.1/” >
<rdf:Description rdf:about="http://burningbird.net/articles/monsters1.htm">
<j.0:related rdf:resource="http://burningbird.net/articles/monsters2.htm"/>
<j.0:related rdf:resource="http://burningbird.net/articles/monsters3.htm"/>
<j.0:bio rdf:nodeID="A0″/>
</rdf:Description>
<rdf:Description rdf:nodeID="A0″>
<dc:creator>Shelley Powers</dc:creator>
<dc:publisher>Burningbird</dc:publisher>
<dc:title xml:lang="en">Tale of Two Monsters: Legends</dc:title>
</rdf:Description>
</rdf:RDF>
The blank node is given a node identifier of “AO” to differentiate it in the model. Use caution with bnode identifiers – they lose all meaning outside of the particular instance of the model; they are not true URIs, but merely a placeholder and a convenience.
Well, that was fun. Still no hassle porting the code, and since we used a vocabulary class in the original application, we’ve helped minimize the code changes in all of the applications that use the class. Golly, isn’t it nice how this stuff works, sometimes?