Sesame 4.0 was officially released today. This first new major release of the Sesame RDF framework in over 7 years focuses on major usability improvements in the core APIs. In particular, its goals are to reduce boilerplate code in Sesame-based projects, and to facilitate easier, more flexible, and streaming access to RDF data and query results. To this end, release 4 uses several new features of the Java 8 platform, such as lambda expressions, the Stream API, and the AutoCloseable interface.

The Sesame Programmers Manual gives an overview of what these changes entail. However, to illustrate how much of a difference these changes can make for even very simple code, I will show off some of the features in more detail here.

To set the stage, here’s a simple Java program using Sesame 2, which creates a new (in-memory) Sesame database, loads a file into it, queries it using SPARQL, and then uses the result of that query to isolate a subset of the data, which it then retrieves and writes to a new RDF file on disk:

public class SimpleExample {

  public static void main(String[] args) {

    Repository rep = new SailRepository(new MemoryStore());
    try {
      rep.initialize();

      RepositoryConnection conn = rep.getConnection();
      try {
        // load a FOAF file (in Turtle format) to our in-memory database
        File foafData = new File("/path/to/example/foaf.ttl");
        conn.add(foafData, null, RDFFormat.TURTLE);

        // SPARQL query to retrieve all people with the given name "Bob".
        String queryStr = "SELECT ?x WHERE { ?x a foaf:Person ; foaf:givenName \"Bob\"; ?p ?y .} ";

        TupleQueryResult result = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryStr).evaluate();
        try {
          while (result.hasNext()) {
            BindingSet bs = result.next();
            URI x = (URI)bs.getValue("x");

            // retrieve all data with this person x as the subject, and
            // put it in a Model
            Model m = Iterations.addAll(conn.getStatements(x, null, null, true), new LinkedHashModel());
    
            // write this data to disk using the person's last name as  the filename.
            try {
              if (m.contains(x, FOAF.FAMILYN_NAME, null)) {
                  Literal familyName = m.filter(x, FOAF.FAMILY_NAME, null).objectLiteral();
              
                  File outputFile = new File("/path/to/example/" + familyName.getLabel() + ".ttl");
              
                  Rio.write(m, new FileOutputStream(outputFile), RDFFormat.TURTLE);
              }
            }
            catch (ModelException e) {
              e.printStackTrace();
            }
            catch (RDFHandlerException e) {
              e.printStackTrace();
            }
          }
        }
        finally {
          result.close();
        }

      }
      catch (RDFParseException e) {
        e.printStackTrace();
      }
      catch (IOException e) {
        e.printStackTrace();
      }
      catch (QueryEvaluationException e) {
        e.printStackTrace();
      }
      catch (MalformedQueryException e) {
        e.printStackTrace();
      }
      finally {
        conn.close();
      }
    }
    catch (RepositoryException e) {
      e.printStackTrace();
    }
  }
}

That is quite a hefty bit of code for such a relatively simple task. Of course, some of the exception handling I’m doing here is more elaborate than strictly necessary – we could simply catch the super type of all Sesame exceptions, OpenRDFException, or even just add throws Exception to our main method and be done with it. But the reason I spell it out in such detail is that I want to make a point: if you catch every possibly exception Sesame 2 throws at you, code can quickly become unwieldy. And throwing everything upward is not always possible or desirable.

Since in Sesame 2, all these exceptions are checked exceptions, you are forced, at coding time, to figure out what to do with them, even in cases where it’s absolutely clear that nothing exceptional will ever happen. For example, in the above code (line 38) a ModelException is caught. If we look at why this is potentially thrown, we can find that this is only the case if the Model from which we’re trying to retrieve the family name does not actually contain any family name. However, we already checked in our code (using an if-statement, line 30) that it does contain that, so we can be quite sure that this exception will never actually be thrown. Nevertheless, we are forced to deal with it.

If we look at the above code and count the lines that are actually doing our business versus the line that are simply boilerplate, we arrive at 10-20 lines of actual business code (creating the database, loading the data, doing the query, processing the result and writing the file). The other lines are all boilerplate code (try-catch blocks, mostly).

Sesame 4 improves on this situation in a number of ways. Here’s the same program, but done using several new Sesame 4 features.

public class SimpleExample {

  public static void main(String[] args) {

    Repository rep = new SailRepository(new MemoryStore());

    rep.initialize();
    try (RepositoryConnection conn = rep.getConnection()) {
      // load a FOAF file (in Turtle format) to our in-memory database
      File foafData = new File("/path/to/example/foaf.ttl");
      conn.add(foafData, null, RDFFormat.TURTLE);

      // SPARQL query to retrieve all people with the given name "Bob".
      String queryStr = "SELECT ?x WHERE { ?x a foaf:Person ; foaf:givenName \"Bob\"; ?p ?y .} ";

      try (TupleQueryResult result = conn.prepareTupleQuery(queryStr).evaluate()) {
        List<BindingSet> list = QueryResults.asList(result);
        for (BindingSet bs : list) {
          IRI x = (IRI)bs.getValue("x");

          // retrieve all data with this person x as the subject, and put
          // it in a Model
          Model m = QueryResults.asModel(conn.getStatements(x, null, null));

          Models.objectLiteral(m.filter(x, FOAF.FAMILY_NAME, null)).ifPresent(familyName -> {
            File outputFile = new File("/path/to/example/" + familyName.getLabel() + ".ttl");
            try {
              Rio.write(m, new FileOutputStream(outputFile), RDFFormat.TURTLE);
            }
            catch (FileNotFoundException e) {
              e.printStackTrace();
            }
          });
        }
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
}

As you can see, this is already quite a bit shorter, and more to the point, than the Sesame 2 equivalent. Because in Sesame 4 the exceptions are no longer checked, we can get rid of all the enforced catch clauses. In addition we have used a few simple new features and convenience functions to shorten other bits of code. To name a few:

  • AutoCloseable and try-with-resources (lines 8 and 16)
    Instead of explicitly wrapping all code that operates on a RepositoryConnection or a QueryResult in a try block and adding a finally-clause to ensure that connections and results are closed when we are done, we rely on the new try-with-resources feature that Java 8 allows on resources that implement AutoCloseable.
  • Use of lambda expressions (line 25-33) allows us to shorten our business logic, making the entire operation on the family name essentially a single line (yes I know the body of the lambda here is not a single line – you know what I mean though, I hope :)).
  • Use of Optional parameters in the Model API (line 25). By using Optional.ifPresent() we eliminate the need for the separate existence-check for the statement.
  • Several methods now have sensible defaults for parameters, which means you can often leave them out. This leads to shorter, more readable code. For example, preparing the query (line 16) no longer requires the QueryLanguage parameter (if you don’t specify it, we assume it’s SPARQL), and retrieving statements (getStatements, line 32) no longer requires the boolean includeInferred parameter.

With these simple measures, we have nearly halved the number of lines in this program, and overall made it easier to read (and therefore, to maintain).

So, haven’t I cheated here? After all, the original code caught all these exceptions and printed out their stack traces, and in this new code we’re simply ignoring those exceptions completely. So up to a point this new program will behave differently from the original. However, the point I’m making is that the above is valid in Sesame 4. You can choose to completely ignore most exceptions (because they are unchecked exceptions, now). Of course, if you want you still can catch these exceptions, but it’s no longer really necessary if you just want to fire-and-forget.

Sesame 4 is not just about getting shorter code, of course. A lot of the introduced functionality is to increase usability and flexibility. For example, if you find it a pain to have to open and close RepositoryConnections every time you wish to do something on a Repository, there now is a utility class that allows you to ignore this completely. For example, to load our initial RDF file without actually opening a RepositoryConnection first, we can do something like this:

Repository rep = new SailRepository(new MemoryStore());
rep.initialize();

// load a FOAF file (in Turtle format) to our in-memory database
File foafData = new File("/path/to/example/foaf.ttl");
Repositories.consume(rep, conn -> {
  try {
    conn.add(foafData, null, RDFFormat.TURTLE);
  }
  catch (IOException e) {
    e.printStackTrace();
  }
});

This isn’t necessarily a lot shorter, although it can be if you are dealing with more complex transactions: Repositories.consume can take care of committing and/or rolling back your transaction for you. However, regardless of whether it is shorter, it saves you the trouble of juggling with the RepositoryConnection object yourself.

There is more to go into, but this should give a little taster of what Sesame 4 offers. I plan to write several more tech blogs in the near future about Sesame’s new features, and how the leveraging of Java 8 really helps you to focus on the business end of your code (and not the bureaucracy/boilerplate surrounding it).

Comments are closed.