Henriette's Notes

Home » Semantic technologies » RDF Stores

Category Archives: RDF Stores

Getting started with Ontotext GraphDB and Jena

In my previous post I explained how you can create an GraphDB repository and how you can update and query your repository using RDF4J. In this post I provide an example of how you can update and query a GraphDB repository using Jena. However, even though the code works, there are some pitfalls.

A Quick Example

First you need to add Jena as a Maven dependency:

<dependency>
 <groupId>org.apache.jena</groupId>
 <artifactId>apache-jena-libs</artifactId>
 <version>3.7.0</version>
 <type>pom</type>
</dependency>  

The Java code is straightforward:

package org.graphdb.jena.tutorial;

import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.update.UpdateExecutionFactory;
import org.apache.jena.update.UpdateFactory;
import org.apache.jena.update.UpdateProcessor;
import org.apache.jena.update.UpdateRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class SimpleInsertQueryExample {
  private static Logger logger = LoggerFactory.getLogger(SimpleInsertQueryExample.class);
  // Why This Failure marker
  private static final Marker WTF_MARKER = MarkerFactory.getMarker("WTF");
  
  // GraphDB 
  private static final String PERSONDATA_REPO_QUERY = 
      "http://localhost:7200/repositories/PersonData";
  private static final String PERSONDATA_REPO_UPDATE = 
      "http://localhost:7200/repositories/PersonData/statements";


  private static String strInsert;
  private static String strQuery;
  
  static {
    strInsert = 
        "INSERT DATA {"
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://dbpedia.org/ontology/birthDate>"
         + " \"1906-12-09\"^^<http://www.w3.org/2001/XMLSchema#date> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://dbpedia.org/ontology/birthPlace> "
         + "<http://dbpedia.org/resource/New_York_City> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://dbpedia.org/ontology/deathDate>"
         + " \"1992-01-01\"^^<http://www.w3.org/2001/XMLSchema#date> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://dbpedia.org/ontology/deathPlace> "
         + "<http://dbpedia.org/resource/Arlington_County,_Virginia> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://purl.org/dc/terms/description>"
         + " \"American computer scientist and United States Navy officer.\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> "
         + "<http://dbpedia.org/ontology/Person> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://xmlns.com/foaf/0.1/gender> \"female\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://xmlns.com/foaf/0.1/givenName> \"Grace\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> "
         + "<http://xmlns.com/foaf/0.1/name> \"Grace Hopper\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper>"
         + " <http://xmlns.com/foaf/0.1/surname> \"Hopper\" ."        
         + "}";
    
    strQuery = 
        "SELECT ?name WHERE {?s <http://xmlns.com/foaf/0.1/name> ?name .}";
  }   
  
  private static void insert() {
    UpdateRequest updateRequest = UpdateFactory.create(strInsert);
    UpdateProcessor updateProcessor = UpdateExecutionFactory
        .createRemote(updateRequest, 
        PERSONDATA_REPO_UPDATE);
    updateProcessor.execute();
  }
  
  private static void query() {
    QueryExecution queryExecution = QueryExecutionFactory
        .sparqlService(PERSONDATA_REPO_QUERY, strQuery);
    for (ResultSet results = queryExecution.execSelect(); results.hasNext();) {
      QuerySolution qs = results.next();
      String strName = qs.get("?name").toString();
      logger.trace("name = " + strName);
    }    
    queryExecution.close();
  }

  
  public static void main(String[] args) {
    try {
      insert(); 
      query();      
    } catch (Throwable t) {
      logger.error(WTF_MARKER, t.getMessage(), t);
    }   
  }  
}

Some Pitfalls

The example I provided will insert RDF data into GraphDB and query it successfully. However, the data is inserted into the repository in the absence of a transaction. The transaction API of Jena is based on a Dataset. Historically Ontotext provided a Jena adapter with which a Jena Dataset could be created. However, based on my question on Stack Overflow in this regard, the Jena adapter is no longer supported by Ontotext. Hence, currently it is not clear to me how to enable transactions when using Jena to access GraphDB. So, if you know how to address this, please be so kind as to leave a comment with your insight!

Conclusion

In this post I provided a quick example of how you can access GraphDB using Jena. However, this example does not support transactions, and therefore you may want to look at rather using RDF4J with GraphDB. You can find this code at github.

Getting started with Ontotext GraphDB and RDF4J

In this post I will explain how to quickly get started with the free version of Ontotext GraphDB and RDF4J. Ontotext GraphDB is an RDF datastore and RDF4J is a Java framework for accessing RDF datastores (not just GraphDB). I will explain

  1. how to install and start GraphDB, as well as how to use the workbench to add a repository, and
  2. how to do SPARQL queries against GraphDB using RDF4J.

Install and start GraphDB and create a Repository

To gain access to the free version of GraphDB you have to email Ontotext. They will respond with an email with links to a desktop and stand-alone server version of GraphDB. You want to download the stand-alone server version. This is a graphdb-free-VERSION-dist.zip file, that you can extract somewhere on your filesystem, which I will refer to here as $GRAPHDB_ROOT. To start GraphDB, go to $GRAPHDB_ROOT/bin and run ./graphdb.

To access the workbench you can go to http://localhost:7200. To create a new repository, in the left-hand side menu navigate to Setup>Repositories. Click the Create new repository button. For our simple example we will use PersonData as an Repository ID. The rest of the settings we leave as-is. At the bottom of the page you can press the Create button.

Accessing a GraphDB Repository using RDF4J

To access our PersonData repository we will use RDF4J. Since GraphDB is based on the RDF4J libraries, we only need to include the GraphDB dependencies since these already include RDF4J. Thus, in our pom.xml file we only need to add the following:

 <dependency>
   <groupId>com.ontotext.graphdb</groupId>
   <artifactId>graphdb-free-runtime</artifactId>
   <version>8.5.0</version>
 </dependency>

In our example Java code we first insert some RDF data and then do a query based on the added data. For inserting data we start a transaction and commit it, or, if it fails we do a rollback. For querying the data we iterate through the TupleQueryResult, retrieving values for the binding variables we are interested in (i.e. name in this case). In line with the TupleQueryResult documentation, we close the TupleQueryResult once we are done.

package org.graphdb.rdf4j.tutorial;
package org.graphdb.rdf4j.tutorial;

import org.eclipse.rdf4j.model.impl.SimpleLiteral;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.Update;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.http.HTTPRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class SimpleInsertQueryExample {
  private static Logger logger = 
    LoggerFactory.getLogger(SimpleInsertQueryExample.class);
  // Why This Failure marker
  private static final Marker WTF_MARKER = 
    MarkerFactory.getMarker("WTF");
  
  // GraphDB 
  private static final String GRAPHDB_SERVER = 
    "http://localhost:7200/";
  private static final String REPOSITORY_ID = "PersonData";

  private static String strInsert;
  private static String strQuery;
  
  static {
    strInsert = 
        "INSERT DATA {"
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://dbpedia.org/ontology/birthDate> \"1906-12-09\"^^<http://www.w3.org/2001/XMLSchema#date> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://dbpedia.org/ontology/birthPlace> <http://dbpedia.org/resource/New_York_City> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://dbpedia.org/ontology/deathDate> \"1992-01-01\"^^<http://www.w3.org/2001/XMLSchema#date> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://dbpedia.org/ontology/deathPlace> <http://dbpedia.org/resource/Arlington_County,_Virginia> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://purl.org/dc/terms/description> \"American computer scientist and United States Navy officer.\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://dbpedia.org/ontology/Person> ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://xmlns.com/foaf/0.1/gender> \"female\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://xmlns.com/foaf/0.1/givenName> \"Grace\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://xmlns.com/foaf/0.1/name> \"Grace Hopper\" ."
         + "<http://dbpedia.org/resource/Grace_Hopper> <http://xmlns.com/foaf/0.1/surname> \"Hopper\" ."        
         + "}";
    
    strQuery = 
        "SELECT ?name FROM DEFAULT WHERE {" +
        "?s <http://xmlns.com/foaf/0.1/name> ?name .}";
  }  
  
  private static RepositoryConnection getRepositoryConnection() {
    Repository repository = new HTTPRepository(
      GRAPHDB_SERVER, REPOSITORY_ID);
    repository.initialize();
    RepositoryConnection repositoryConnection = 
      repository.getConnection();
    return repositoryConnection;
  }
  
  private static void insert(
    RepositoryConnection repositoryConnection) {
    
    repositoryConnection.begin();    
    Update updateOperation = repositoryConnection
      .prepareUpdate(QueryLanguage.SPARQL, strInsert);
    updateOperation.execute();
    
    try {
      repositoryConnection.commit();
    } catch (Exception e) {
      if (repositoryConnection.isActive())
        repositoryConnection.rollback();
    }
  }

  private static void query(
    RepositoryConnection repositoryConnection) {
    
    TupleQuery tupleQuery = repositoryConnection
      .prepareTupleQuery(QueryLanguage.SPARQL, strQuery);
    TupleQueryResult result = null;
    try {
      result = tupleQuery.evaluate();
      while (result.hasNext()) {
        BindingSet bindingSet = result.next();

        SimpleLiteral name = 
          (SimpleLiteral)bindingSet.getValue("name");
        logger.trace("name = " + name.stringValue());
      }
    }
    catch (QueryEvaluationException qee) {
      logger.error(WTF_MARKER, 
        qee.getStackTrace().toString(), qee);
    } finally {
      result.close();
    }    
  }  
  
  public static void main(String[] args) {
    RepositoryConnection repositoryConnection = null;
    try {   
      repositoryConnection = getRepositoryConnection();
      
      insert(repositoryConnection);
      query(repositoryConnection);      
      
    } catch (Throwable t) {
      logger.error(WTF_MARKER, t.getMessage(), t);
    } finally {
      repositoryConnection.close();
    }
  }  
}

Conclusion

In this brief post I gave a quick example of how you can setup a simple GraphDB repository and query it using SPARQL. You can find sample code on github.

Creating Custom Rule Primitives for Jena

In this post I will show you

  1. how to add your own custom rule primitive,
  2. how to inform Jena of your custom rule primitive, and
  3. I will discuss things you have to keep in mind when writing a custom
    primitive.

Adding a Custom Rule Primitive

A powerful feature of Jena is that it allows you to create your own custom builtin rule primitives. Building on our student example of the previous post, assume we want to calculate the final mark for a student given their test result, exam result and project result. We assume we have the following data

:Peet :hasTestResult 77 .
:Peet :hasExamResult 86 .
:Peet :hasProjectResult 91 .

for which we add the following rule

[calcStudentFinalMarkRule: 
  (?student :hasTestResult ?testResult) 
  (?student :hasExamResult ?examResult) 
  (?student :hasProjectResult ?projectResult) 
  calcFinalMark(?testResult, ?examResult, 
   ?projectResult, ?finalMark)
      -> (?student :hasFinalMark ?finalMark)]

The meat of the implementation is the doUserRequiredAction(args, length,
context)
method, which consists of the following steps:

  1. check that we have the correct number of parameters,
  2. retrieve the input parameters,
  3. verify the typing of input parameters,
  4. doing the actual calculation,
  5. creating a node for the output parameter, and
  6. binding the node to the output parameter.
private boolean doUserRequiredAction(Node[] args, 
  int length, RuleContext context) {
  
  // Check we received the correct number of parameters
  checkArgs(length, context);

  boolean success = false;
  
  // Retrieve the input arguments
  Node studentTestResult = getArg(0, args, context);
  Node studentExamResult = getArg(1, args, context);
  Node studentProjectResult = getArg(2, args, context);

  // Verify the typing of the parameters
  if (studentTestResult.isLiteral() && 
    studentExamResult.isLiteral() && 
    studentProjectResult.isLiteral()) {
    Node finalMark = null;
    if (studentTestResult.getLiteralValue() 
        instanceof Number && 
      studentExamResult.getLiteralValue() 
        instanceof Number &&
      studentProjectResult.getIndexingValue() 
        instanceof Number) {
    
      Number nvStudentTestResult = 
        (Number)studentTestResult.getLiteralValue();
      Number nvStudentExamResult = 
        (Number)studentExamResult.getLiteralValue();
      Number nvStudentProjectResult = 
        (Number)studentProjectResult.getLiteralValue();
    
      // Doing the calculation
      int nFinalMark = 
        (nvStudentTestResult.intValue() * 20)/100 + 
        (nvStudentExamResult.intValue() * 50)/100 +
        (nvStudentProjectResult.intValue() * 30)/100;
      
      // Creating a node for the output parameter
      finalMark = Util.makeIntNode(nFinalMark);  
      
      // Binding the output parameter to the node
      BindingEnvironment env = context.getEnv();    
      success = env.bind(args[3], finalMark);
    } 
  }   
  return success;
}

Registering a Custom Primitive with Jena

Our code for load our rules and activating it is similar to my previous post, except that you have to make a call to register the custom primitive:

// Load RDF data
String data = path.toFile().getAbsolutePath() + 
  "/src/main/resources/data2.ttl";
Model model = ModelFactory.createDefaultModel();
model.read(data);
      
// Register custom primitive
BuiltinRegistry.theRegistry.register(new CalcFinalMark());
      
// Load rules
String rules = path.toFile().getAbsolutePath() + 
  "/src/main/resources/student2.rules";
Reasoner reasoner = new GenericRuleReasoner(Rule.rulesFromURL(rules));
     
InfModel infModel = ModelFactory.createInfModel(reasoner, model);  
infModel.rebind();   

Things to Keep in Mind

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

  1. A primitive is suppose to be a elementary building block. Being able to create your own primitives may tempt you to add all sorts of interesting processing besides the manipulation of triples, but I strongly advice against that. Arbitrary processing in your builtin primitive can degrade performance of inferencing.
  2. Do not assume that you have control over when a rule will be triggered. Exactly when a rule will be triggered is dependent on when the Jena InfModel implementation decides to re-evaluate the rules, which is dependent on internal caching of the InfModel implementation and how it deals with modifications made to the model. Even though I believe InfModel implementations will avoid arbitrarily re-evaluating rules, I still think it is conceivable that under some circumstance the same rule may be triggered more than once for the same data. Furthermore, the Jena documentation of 3.6.0 states that the InfModel interface is still in flux, which could mean that even if a rule is only triggered once for given data currently, due to unforeseen changes it may be triggered more than once in future updates of Jena.

Conclusion

In this post I gave an example of how you can develop a custom rule primitive for Jena. The code for this example can be found at github.