Henriette's Notes

Creating Custom Rules for Jena

In this post I will show you

  1. how to add your own custom rules to Jena,
  2. how to use rule primitives, and
  3. I will mention some things you may want to keep in mind when using rules.

Add and Activate Rules

We assume we start with the following simple triples:

:Peet :takesCourse :ComputerScience .
:Ruth :teachesCourse :ComputerScience .

for which we add the following rule to a student1.rules file:

[hasStudentRule: 
  (?student :takesCourse ?course) 
  (?lecturer :teachesCourse ?course) 
    -> (?lecturer :hasStudent ?student)] 

The hasStudentRule says that if a student takes a course and that course is presented by some lecturer, then the student is a student of that lecturer. The (?student :takesCourse ?course) (?lecturer :teachesCourse ?course) part of the rule is referred to as the premise or body of the rule and the (?lecturer :hasStudent ?student) part as the conclusion or head of the rule.

To activate the rules in Jena we have to create a model that is capable of doing inferences. In the context of Jena inferencing means that it will re-evaluate the rules against the data, which will cause additional statements to be potentially added to the model. This can be achieved by calling methods that will cause Jena to re-evaluate the model, i.e. calls like InfModel.rebind() and InfModel.validate().

Path path = Paths.get(".").toAbsolutePath().normalize();

// Load RDF data
String data = path.toFile().getAbsolutePath() + 
  "/src/main/resources/data.ttl";
Model model = ModelFactory.createDefaultModel();
model.read(data);
    
// Load rules
String rules = path.toFile().getAbsolutePath() + 
  "/src/main/resources/student.rules";
Reasoner reasoner = new GenericRuleReasoner(Rule.rulesFromURL(rules));

InfModel infModel = ModelFactory.createInfModel(reasoner, model);   
infModel.rebind();

When the hasStudentRule is activated a new statement will be added to the Jena model:

:Ruth :hasStudent :Peet .

Using Rule Primitives

Jena supports a number of builtin rule primitives that are intuitive to understand, i.e.

[pensionerRule: 
  (?person :hasAge ?age) greaterThan(?age, 60) 
    -> (?person a :Pensioner)]

which states that when ?person has an age greater than 60, ?person is considered to be a pensioner. Assuming we have the data

:Peet :hasAge 90 .

the following triple will be added to the Jena model:

:Peet a :Pensioner .

Things to Keep in Mind

There are two main things I think one needs to keep in mind with Jena rules:

  1. The purpose of rules are to manipulate the triples in the Jena model. For the most part it adds triples to the model, but it can also remove triples from the model if primitives like remove and drop are used.
  2. Adding and removing of triples can be achieved through SPARQL queries which can perform better or worse than rules. It is therefore best to check both approaches for your given use case.

Conclusion

In this post I gave a brief introduction in how to use custom rules and builtin rule primitives in Jena. This code is available at github.

Object Oriented Features that OWL lacks

At some level OWL seem to be very similar to UML, but there are important differences that you have to keep in mind. In this post I detail features you will have to do without coming from a coding or object orientation perspective when you start using OWL. These are:

  1. classes do not have attributes,
  2. classes do have class variables (but not in the way you think),
  3. you have to live without methods,
  4. there are no interfaces, and
  5. neither are there any abstract classes.

Classes do not have Attributes

Programmers are used to classes that have attributes or properties. This is also referred to as instance/member variables of the class. In OWL classes do not have properties. Rather, properties can be used to model relations that exist between classes and data types. You can however state that a class is a subclass of something that has some property. I.e.,

Class: Course
  SubClassOf: 
    hasSubject some Subject 

which states that a Course is a subclass of things that have some
Subject. I explain this here and here.

Classes do have Class Variables (but not in the way you think)

In programming class variables are values that describe the class rather than instances of the class. In OWL this is achieved trough annotations. There exist pre-defined annotation properties and you can define your own annotation properties. Interestingly annotation properties can be specified for any class, individual or axiom, as well as the ontology itself. Annotations are purely used to specify additional information and no reasoning is done over them.

There are no Methods nor Interfaces

This may seem like silly a comment, but is worth making it explicit: “OWL does not have methods”. Why not? Well, OWL is not a programming language nor a modelling language for designing software. It is a conceptual modelling language. Can you model methods in OWL? Yes, I have done it here to find software modelling heuristic violations, but it is not trivial. However, even if you can define the conceptual notion of a method, there is no way to execute methods in OWL. Again, because it is not a programming language.

What about interfaces? Well, since there are no methods in OWL, there are no interfaces either. In programming the idea is that an interface defines the signature of an interaction, usually in terms of method signatures, without specifying how the methods are actually implemented. This allows for the same interface to have different implementations.

Neither are there Abstract Classes

Abstract classes in programming are used to enable programmers to only implement a subset of the interfaces specified, thereby forcing subclasses to do the implementation. However, again since interfaces do not exist, and there are no methods, it does not make sense for OWL to support abstract classes.

What about the case where in programming an abstract class is defined that only consist of member/instance variables? In programming this has the effect that no instances of this class can be created directly. The only way an instance can be created is by creating an instance of a subclass of the abstract class. Importantly these instances created of the subclass are still instances of the abstract class. In OWL there is no way to force that individuals cannot be created of a class, but only for its subclasses. The closest to not being able to create individuals for a class is the class ‘owl:Nothing‘, but that really means the class has zero individuals, which is not what is meant by an abstract class in programming.

Conclusion

In this post I detailed some object oriented features programmers may feel OWL lacks. However, OWL is a conceptual modelling language with reasoning capability. Thinking about it causes one to realize it makes as little sense to say OWL lacks some object oriented feature as saying that Java or C# lacks reasoning capability.

Creating, Writing and Reading Jena TDB2 Datasets

Jena TDB2 can be used as an RDF datastore. Note that TDB (version 1 of Jena TDB) and TDB2 are not compatible with each other. TDB2 is per definition transactional (while TDB is not). In this post I give a simple example that

  1. create a new Jena TDB2 dataset,
  2. create a write transaction and write data to the datastore,
  3. create a read transaction and read the data from the datastore, and
  4. release resources associated with the dataset on writing and reading is done.

Create TDB2 Dataset

To create a Jena TDB2 dataset, we use the TDB2Factory. Note that the class name is TDB2Factory and not TDBFactory. We need to specify a directory where our dataset will be created. Multiple datasets cannot be written to the same directory.

Path path = Paths.get(".").toAbsolutePath().normalize();      
String dbDir = path.toFile().getAbsolutePath() + "/db/"; 
Location location = Location.create(dbDir);      
Dataset dataset = TDB2Factory.connectDataset(location); 

Create WRITE Transaction and Write

dataset.begin(ReadWrite.WRITE);
UpdateRequest updateRequest = UpdateFactory.create(
  "INSERT DATA { " 
  + " \"Grace Hopper\" .}");
UpdateProcessor updateProcessor = 
  UpdateExecutionFactory.create(updateRequest, dataset);
updateProcessor.execute();
dataset.commit(); 

Create READ Transaction and Read

dataset.begin(ReadWrite.READ);
QueryExecution qe = QueryExecutionFactory
  .create("SELECT ?s ?p ?o WHERE {?s ?p ?o .}", dataset);
for (ResultSet results = qe.execSelect(); results.hasNext();) {
  QuerySolution qs = results.next();
  String strValue = qs.get("?o").toString();
  logger.trace("value = " + strValue);
}  

Release Dataset Resources and Run Application

The dataset resources can be release calling close() on the dataset.

dataset.close();

Running the application will cause a /db directory to be created in the directory from where you run your application, which consists of the various files that represent your dataset.

Conclusion

In this post I have given a simple example creating a TDB2 dataset and writing to and reading from it. This code can be found on github.