package de.unikiel.inf.comsys.neo4j.inference;
/*
* #%L
* neo4j-sparql-extension
* %%
* Copyright (C) 2014 Niclas Hoyer
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import de.unikiel.inf.comsys.neo4j.inference.rules.Rules;
import de.unikiel.inf.comsys.neo4j.inference.rules.Rule;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.QueryResultHandlerException;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.TupleQueryResultHandlerException;
import org.openrdf.query.resultio.QueryResultParseException;
import org.openrdf.query.resultio.TupleQueryResultParser;
import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLParserFactory;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.repository.sail.SailRepositoryConnection;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFParseException;
import org.openrdf.sail.memory.MemoryStore;
@RunWith(Parameterized.class)
public class SPARQLInferenceTest {
private SailRepository repo;
private SailRepositoryConnection conn;
private ValueFactory vf;
private final String name;
private final String comment;
private final String data;
private final String expected;
private final String queryString;
private TupleQuery query;
private TupleQuery nonInfQuery;
private TupleQueryResultParser parser;
private static InputStream getResource(String name) {
if (name == null) {
return null;
}
String pref = "file://";
if (name.startsWith(pref)) {
name = name.substring(pref.length());
}
return SPARQLInferenceTest.class.getResourceAsStream(name);
}
private static String getResourceAsString(String name) throws IOException {
InputStream in = getResource(name);
if (in == null) {
return null;
}
StringWriter writer = new StringWriter();
IOUtils.copy(in, writer, "UTF-8");
return writer.toString();
}
@Parameters(name = "{0} - {1}")
public static Iterable<Object[]> data()
throws RepositoryException, IOException, RDFParseException,
MalformedQueryException, QueryEvaluationException {
SailRepository repository = new SailRepository(new MemoryStore());
repository.initialize();
SailRepositoryConnection conn = repository.getConnection();
conn.add(
getResource("/inference/kiel/manifest.ttl"),
"file:///inference/kiel/",
RDFFormat.TURTLE);
TupleQuery q = conn.prepareTupleQuery(
QueryLanguage.SPARQL,
getResourceAsString("/inference/queryeval.sparql"));
TupleQueryResult r = q.evaluate();
BindingSet b;
String name;
InputStream data;
String query;
InputStream result;
String comment;
ArrayList<Object[]> tests = new ArrayList<>();
while(r.hasNext()) {
b = r.next();
String datastr = b.getValue("data").stringValue();
String resultstr = b.getValue("result").stringValue();
name = b.getValue("name").stringValue();
data = getResource(datastr);
query = getResourceAsString(b.getValue("query").stringValue());
result = getResource(resultstr);
if (b.hasBinding("comment")) {
comment = b.getValue("comment").stringValue();
} else {
comment = "";
}
if (data != null && query != null && result != null) {
Object[] test = {name, comment, datastr, query, resultstr};
tests.add(test);
}
}
return tests;
}
public SPARQLInferenceTest(
String name, String comment, String data, String query,
String expected) throws RepositoryException {
this.name = name;
this.comment = comment;
this.data = data;
this.queryString = query;
this.expected = expected;
this.query = null;
this.parser = null;
}
@Before
public void before()
throws RepositoryException, IOException, RDFParseException,
MalformedQueryException, QueryResultParseException,
QueryResultHandlerException {
repo = new SailRepository(new MemoryStore());
repo.initialize();
conn = repo.getConnection();
vf = conn.getValueFactory();
conn.add(getResource(data), "file://", RDFFormat.TURTLE);
SPARQLResultsXMLParserFactory factory =
new SPARQLResultsXMLParserFactory();
parser = factory.getParser();
parser.setValueFactory(vf);
List<Rule> rules;
rules = Rules.fromOntology(getResource(data));
QueryRewriter rewriter = new QueryRewriter(conn, rules);
query = (TupleQuery) rewriter.rewrite(QueryLanguage.SPARQL, queryString);
nonInfQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
System.out.println("== QUERY (" + this.name + ") ==");
System.out.println(nonInfQuery);
System.out.println("== REWRITTEN QUERY (" + this.name + ") ==");
System.out.println(query);
}
@After
public void after() throws RepositoryException {
conn.close();
repo.shutDown();
}
private class QueryResult {
private final Multiset<BindingSet> expect;
private final Multiset<BindingSet> actual;
private final Multiset<BindingSet> noninf;
public QueryResult(
Multiset<BindingSet> expected, Multiset<BindingSet> actual,
Multiset<BindingSet> noninf) {
this.expect = expected;
this.actual = actual;
this.noninf = noninf;
}
public Multiset<BindingSet> getExpected() {
return expect;
}
public Multiset<BindingSet> getActual() {
return actual;
}
public Multiset<BindingSet> getNonInferred() {
return noninf;
}
public Multiset<BindingSet> getDiff() {
return Multisets.difference(expect, actual);
}
@Override
public String toString() {
return getActual() + "\n" + getExpected();
}
}
private QueryResult runQuery()
throws IOException, QueryEvaluationException,
QueryResultParseException, TupleQueryResultHandlerException,
QueryResultHandlerException {
TestResultHandler noninf = new TestResultHandler();
TestResultHandler actual = new TestResultHandler();
TestResultHandler expect = new TestResultHandler();
parser.setQueryResultHandler(expect);
parser.parseQueryResult(getResource(expected));
nonInfQuery.evaluate(noninf);
query.evaluate(actual);
Multiset<BindingSet> noninfset = noninf.getSolutions();
Multiset<BindingSet> expectset = expect.getSolutions();
Multiset<BindingSet> actualset = actual.getSolutions();
return new QueryResult(expectset, actualset, noninfset);
}
@org.junit.Rule
public ErrorCollector collector = new ErrorCollector();
@Test
public void subset()
throws QueryEvaluationException, TupleQueryResultHandlerException,
IOException, QueryResultParseException,
QueryResultHandlerException {
QueryResult q = this.runQuery();
assertTrue(
q.getNonInferred() + " should be a subset of " + q.getActual(),
Multisets.containsOccurrences(
q.getActual(),
q.getNonInferred()
));
}
@Test
public void missing()
throws QueryEvaluationException, TupleQueryResultHandlerException,
IOException, QueryResultParseException,
QueryResultHandlerException {
QueryResult q = this.runQuery();
Multiset<BindingSet> missing = q.getExpected();
missing.removeAll(q.getActual());
for (BindingSet b : missing) {
collector.addError(new Throwable("Missing " + b + " in result set"));
}
}
@Test
public void additional()
throws QueryEvaluationException, TupleQueryResultHandlerException,
IOException, QueryResultParseException,
QueryResultHandlerException {
QueryResult q = this.runQuery();
Multiset<BindingSet> additional = q.getActual();
additional.removeAll(q.getExpected());
for (BindingSet b : additional) {
collector.addError(new Throwable(b + " shouldn't be in result set"));
}
}
}