Download Stardog now to start a free 30-day evaluation.

Springtime for Stardog

by Greg Coluni, 6 September 2017 · 7 minute read

Stardog makes Knowledge Graphs fast and easy. I’ll show you how in this post using Spring.

In my previous post I made a Stardog application in 5 easy steps. I’ll expand that by using Spring Framework’s inversion of control (IoC) to configure and manage Stardog connections and execute core tasks: reasoning, proof, and full text search.

From Gradle to Spring

I started by updating our Gradle file. The first change is moving to Stardog 5.0. If you haven’t read about its new capabilities, you should do that now. Next I have to bring in the necessary ‘springframework’ jars.

dependencies {
    // Core Dependencies
    compile ('com.complexible.stardog:client-http:5.0.1')
    compile ('com.complexible.stardog:stardog-spring:5.0.1')
    compile ('com.complexible.stardog:server:5.0.1')
    // Spring
    compile ('org.springframework:spring-core:4.3.10.RELEASE')
    compile ('org.springframework:spring-beans:4.3.10.RELEASE')
    compile ('org.springframework:spring-context:4.3.10.RELEASE')
    compile ('org.springframework:spring-tx:4.3.10.RELEASE')
    compile ('org.slf4j:slf4j-api:1.6.1')
}

Next is the IoC configuration.

There are three major components to highlight. The first is the dataSource component which uses the DataSourceFactoryBean that implements the Spring interfaces for FactoryBean for a DataSource, i.e., InitializingBean and DisposableBean.

Second is the template which uses the SnarlTemplate which has access to the dataSource in order to get a connection from the pool for transactions.

Third, and finally, the importer shows how adding and removing data in bulk to and from Stardog via IoC is done.

<bean name="dataSource" class="com.complexible.stardog.ext.spring.DataSourceFactoryBean">
     <property name="to" value="testDB"/>
     <property name="username" value="admin"/>
     <property name="password" value="admin"/>
     <property name="reasoningType" value="true" />
     <property name="url" value="http://localhost:5820"/>
</bean>

<bean name="template" class="com.complexible.stardog.ext.spring.SnarlTemplate">
     <property name="dataSource" ref="dataSource"/>
</bean>

<bean name="importer" class="com.complexible.stardog.ext.spring.DataImporter">
     <property name="snarlTemplate" ref="template"/>
     <property name="format" value="N3"/>
     <property name="inputFiles">
         <list>
             <value>classpath:foaf.rdf</value>
             <value>classpath:marvel.rdf</value>
             <value>classpath:marvel_v2.rdf</value>
         </list>
     </property>
</bean> 

This is simple stuff.

Running the App

Now that I’ve configured everything, let’s see how using Spring makes life easy when communicating with Stardog. In order to use our nicely configured SnarlTemplate we must request it from Spring’s ApplicationContext.

The ApplicationContext is Spring’s advanced container that can load bean definitions, wire them together, and provide them when needed. The ClassPathXmlApplicationContext loads the bean definitions from the XML file for the ApplicationContext and then we request the bean definition we want (template).

Like magic we have our SnarlTemplate fully configured and ready to talk to Stardog.

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SnarlTemplate snarlTemplate = (SnarlTemplate) context.getBean("template");

Query Row Mapper

We will start by running a simple query and showing how to use the SnarlTemplate RowMapper. The SimpleRowMapper retuns the query result as a Map of String,String that allows for easily traversing the data.

// Query to run
  String sparql = "PREFIX foaf:<http://xmlns.com/foaf/0.1/> " +
     "select * { ?s rdf:type foaf:Person }";

// Queries the database using the SnarlTemplate and gets back a list of mapped objects
  List<Map<String, String>> results = snarlTemplate.query(sparql, new SimpleRowMapper());
  results.forEach(item -> item.forEach((k,v) -> System.out.println(v)));    

The results of the query are iterated and displayed. I know Mary Parker isn’t really part of the Marvel Universe but the reason for her will be obvious soon enough.

** Members of Marvel Universe **
http://api.stardog.com/incredibleHulk
http://api.stardog.com/captainAmerica
http://api.stardog.com/blackWidow
http://api.stardog.com/thor
http://api.stardog.com/ironMan
http://api.stardog.com/spiderMan
http://api.stardog.com/antMan
http://api.stardog.com/maryParker

ASK

Stardog has the ability to ASK the graph a question. ASK is a query form that is used to test whether or not a query pattern has a solution. No information is returned about the possible query solutions, just whether or not a solution exists.

In our model we have that :spiderMan foaf:knows :ironMan, but we do not have the inverse. Does Iron Man also know Spider-Man? There’s no reason to assume that you know everyone who knows you. So we ask two questions about whether Spider-Man and Iron Man know each other. The SnarlTemplate has a built in ask function that will create the database connection and ask the question.

// Using the SnarlTemplate, you can ask the database questions.
String askQuery1 = "PREFIX foaf:<http://xmlns.com/foaf/0.1/> " +
      "ASK { :ironMan foaf:knows :spiderMan }";
System.out.println("Does Iron Man know Spider-Man? " + snarlTemplate.ask(askQuery1));

String askQuery2 = "PREFIX foaf:<http://xmlns.com/foaf/0.1/>" +
      "ASK { :spiderMan foaf:knows :ironMan } ";
System.out.println("Does Spider-Man know Iron Man? " + snarlTemplate.ask(askQuery2));

The ASK results are as I expected.

** Ask the database questions **
Does Iron Man know Spider-Man? false
Does Spider-Man know Iron Man? true

Reasoning

Another main feature of Stardog is reasoning, in this case, logical reasoning. Stardog infers logical conclusions based on graph data, nodes and edges, and schema, including declarative rules and axioms. This makes it much easier to handle complex data and relationships by data modeling rather than writing (equally complex) code.

In this contrived example I’ve added a parent/child relationship that allows for drawing logical inferences between a parent and a child. I also added data to the graph that connects Mary Parker to Spider-Man.

:parentOf a owl:ObjectProperty ;
    owl:inverseOf :childOf .
:maryParker rdf:type foaf:Person ;
    :parentOf :spiderMan .    

I’ll run two queries, one with reasoning and the other without. The results are going to be different. We use the SnarlTemplate to get the connection before asking our question in order to turn reasoning on and off.

// Queries the database with reasoning off.
// Is Spider-Man the child of Mary Parker? No.
boolean aExistsNoReasoning = snarlTemplate.getDataSource().getConnection().get()
    .reasoning(false)
    .subject(Values.iri("http://api.stardog.com/spiderMan"))
    .predicate(Values.iri("http://api.stardog.com/childOf"))
    .object(Values.iri("http://api.stardog.com/maryParker"))
    .ask();

// Queries the database with reasoning on.
// Is Spider-Man the child of Mary Parker? Yes.
boolean aExistsReasoning = snarlTemplate.getDataSource().getConnection().get()
    .reasoning(true)
    .subject(Values.iri("http://api.stardog.com/spiderMan"))
    .predicate(Values.iri("http://api.stardog.com/childOf"))
    .object(Values.iri("http://api.stardog.com/maryParker"))
    .ask();

The results are

** Show Reasoning **
aExistsNoReasoning: false
aExistsReasoning: true

Proof

Automated reasoning is great but it’s even better when it’s transparent. People don’t trust black boxes very much. Nor should they. Stardog can give us an explanation for any inference it makes.

A proof tree presents the justification, in terms of data and schema, for any inference. Think of an explanation like this: which rules and which data make the conclusion true?

I send the same query to the reasoner, and get the explanation back, using the SnarlTemplate to get the connection before setting the connection as a type of ReasoningConnection.class in order to turn reasoning on and off.

StardogExplainer aExplanation = snarlTemplate
    .getDataSource()
    .getConnection()
    .as(ReasoningConnection.class)
    .explain(ExpressionFactory.fromStatements(
         Values.statement(
             Values.iri("http://api.stardog.com/spiderMan"),
             Values.iri("http://api.stardog.com/childOf"),
             Values.iri("http://api.stardog.com/maryParker"))));

The explanation is:

INFERRED :spiderMan :childOf :maryParker
   ASSERTED :childOf owl:inverseOf :parentOf
   ASSERTED :maryParker :parentOf :spiderMan

Read as: we infer that Spider-Man is Mary Parker’s child because we know that Mary Parker is Spider-Man’s parent and you are the child of any person who is your parent.

Semantic Search in Stardog works over all graph literal values. Backed by Lucene internally, you can use any Lucene search syntax in your query and Stardog will find matches with a specificity on the match. Here I will specify that we only want results over a score of 0.5, and no more than 2 results for matches of the search term man.

I perform the search in two different ways. First, using the Waldo API and, second, running a SPARQL query based on the LARQ syntax in Jena. To accomplish this, I use the execute method of the SnarlTemplate. The execute method is a callback method for executing a block of code outside the defined API calls. Once we get the connection from the SnarlTemplate I have to use the SearchConnection.class in order to execute the search method.

snarlTemplate.execute(new ConnectionCallback<Boolean>() {
    @Override
    public Boolean doWithConnection(Connection connection) {
        try {
              // Stardog's full text search is backed by [Lucene](http://lucene.apache.org)
              // so you can use the full Lucene search syntax in your queries.
              Searcher aSearch = connection
                 .as(SearchConnection.class)
                 .search()
                 .limit(2)
                 .query("man")
                 .threshold(0.5);

              // We can run the search and then iterate over the results
              SearchResults aSearchResults = aSearch.search();
                 try (CloseableIterator<SearchResult> resultIt = aSearchResults.iterator()) {
                    while (resultIt.hasNext()) {
                        SearchResult aHit = resultIt.next();
                        System.out.println(aHit.getHit() + " with a score of: " + aHit.getScore());
                    }
                 }
              // The SPARQL syntax is based on the LARQ syntax in Jena.  Here you will
              // see the SPARQL query that is equivalent to the search we just did via `Searcher`,
              // which we can see when we print the results.
              String aQuery = "SELECT DISTINCT ?s ?score WHERE {\n" +
                     "\t?s ?p ?l.\n" +
                     "\t( ?l ?score ) <" + SearchConnection.MATCH_PREDICATE + "> ( 'man' 0.5 2 ).\n" +
                     "}";

              SelectQuery query = connection
                 .select(aQuery);

                 try (TupleQueryResult aResult = query.execute()) {
                    System.out.println("Query results: ");
                    while (aResult.hasNext()) {
                        BindingSet result = aResult.next();
                        System.out.println(result.getValue("s").getLocalName() + " with a score of: "
                                                  + ((Literal) result.getValue("score")).doubleValue());
                    }
                 }
              } catch (StardogException e) {
                       System.out.println("Error with full text search: " + e);
                       return false;
            }
        return true;
    }
});

The results are:

API results:
antMan with a score of: 4.844814300537109
ironMan with a score of: 4.844814300537109

Query results:
ironMan with a score of: 4.844814300537109
spiderMan with a score of: 4.844814300537109

Ant-Man would be in the list if I hadn’t limited the results.

Conclusion

I’ve tweaked the Java app from my last post to use Spring Framework. In my next post, I’ll expand on more Stardog features as well as provide an example on how to use Clojure to create a Stardog client. The full version of the code we’ve discussed here is in the stardog-examples repo on Github.


comments powered by Disqus