Springtime for Stardog
Get the latest in your inbox
Get the latest in your inbox
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.
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.
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");
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
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
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
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.
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.
How to Overcome a Major Enterprise Liability and Unleash Massive Potential
Download for free