/*
* Licensed to Aduna under one or more contributor license agreements.
* See the NOTICE.txt file distributed with this work for additional
* information regarding copyright ownership.
*
* Aduna licenses this file to you under the terms of the Aduna BSD
* License (the "License"); you may not use this file except in compliance
* with the License. See the LICENSE.txt file distributed with this work
* for the full License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package org.openrdf.query.parser.sparql.manifest;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.aduna.io.IOUtil;
import info.aduna.iteration.Iterations;
import info.aduna.text.StringUtil;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.util.ModelUtil;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.Update;
import org.openrdf.query.impl.DatasetImpl;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.contextaware.ContextAwareConnection;
import org.openrdf.repository.contextaware.ContextAwareRepository;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.rio.RDFFormat;
import org.openrdf.sail.memory.MemoryStore;
/**
* A SPARQL 1.1 Update test, created by reading in a W3C working-group style manifest.
*
* @author Jeen Broekstra
*/
public abstract class SPARQLUpdateConformanceTest extends TestCase {
/*-----------*
* Constants *
*-----------*/
protected static final Logger logger = LoggerFactory.getLogger(SPARQLUpdateConformanceTest.class);
protected final String testURI;
protected final String requestFileURL;
/*-----------*
* Variables *
*-----------*/
protected Repository dataRep;
protected Repository expectedResultRepo;
protected URI inputDefaultGraph;
protected Map<String, URI> inputNamedGraphs;
protected URI resultDefaultGraph;
protected Map<String, URI> resultNamedGraphs;
protected final Dataset dataset;
/*--------------*
* Constructors *
*--------------*/
public SPARQLUpdateConformanceTest(String testURI, String name, String requestFile, URI defaultGraphURI,
Map<String, URI> inputNamedGraphs, URI resultDefaultGraphURI, Map<String, URI> resultNamedGraphs)
{
super(name);
this.testURI = testURI;
this.requestFileURL = requestFile;
this.inputDefaultGraph = defaultGraphURI;
this.inputNamedGraphs = inputNamedGraphs;
this.resultDefaultGraph = resultDefaultGraphURI;
this.resultNamedGraphs = resultNamedGraphs;
if (this.inputNamedGraphs.size() > 0) {
DatasetImpl ds = new DatasetImpl();
ds.addDefaultGraph(null);
ds.addDefaultRemoveGraph(null);
ds.setDefaultInsertGraph(null);
for (String ng : inputNamedGraphs.keySet()) {
URI namedGraph = new URIImpl(ng);
ds.addNamedGraph(namedGraph);
}
this.dataset = ds;
}
else {
this.dataset = null;
}
}
/*---------*
* Methods *
*---------*/
@Override
protected void setUp()
throws Exception
{
dataRep = createRepository();
URL graphURL = null;
RepositoryConnection conn = dataRep.getConnection();
try {
conn.clear();
if (inputDefaultGraph != null) {
graphURL = new URL(inputDefaultGraph.stringValue());
conn.add(graphURL, null, RDFFormat.forFileName(graphURL.toString()));
}
for (String ng : inputNamedGraphs.keySet()) {
graphURL = new URL(inputNamedGraphs.get(ng).stringValue());
conn.add(graphURL, null, RDFFormat.forFileName(graphURL.toString()),
dataRep.getValueFactory().createURI(ng));
}
}
finally {
conn.close();
}
expectedResultRepo = createRepository();
conn = expectedResultRepo.getConnection();
try {
conn.clear();
if (resultDefaultGraph != null) {
graphURL = new URL(resultDefaultGraph.stringValue());
conn.add(graphURL, null, RDFFormat.forFileName(graphURL.toString()));
}
for (String ng : resultNamedGraphs.keySet()) {
graphURL = new URL(resultNamedGraphs.get(ng).stringValue());
conn.add(graphURL, null, RDFFormat.forFileName(graphURL.toString()),
dataRep.getValueFactory().createURI(ng));
}
}
finally {
conn.close();
}
}
protected Repository createRepository()
throws Exception
{
Repository repo = newRepository();
repo.initialize();
RepositoryConnection con = repo.getConnection();
try {
con.clear();
con.clearNamespaces();
}
finally {
con.close();
}
return repo;
}
protected abstract Repository newRepository()
throws Exception;
@Override
protected void tearDown()
throws Exception
{
if (dataRep != null) {
dataRep.shutDown();
dataRep = null;
}
if (expectedResultRepo != null) {
expectedResultRepo.shutDown();
expectedResultRepo = null;
}
}
@Override
protected void runTest()
throws Exception
{
RepositoryConnection con = dataRep.getConnection();
RepositoryConnection erCon = expectedResultRepo.getConnection();
try {
String updateString = readUpdateString();
con.begin();
// con.setReadContexts((URI)null);
Update update = con.prepareUpdate(QueryLanguage.SPARQL, updateString, requestFileURL);
if (this.dataset != null) {
update.setDataset(this.dataset);
}
update.execute();
con.commit();
// check default graph
logger.info("checking default graph");
compareGraphs(Iterations.asList(con.getStatements(null, null, null, true, (Resource)null)),
Iterations.asList(erCon.getStatements(null, null, null, true, (Resource)null)));
for (String namedGraph : inputNamedGraphs.keySet()) {
logger.info("checking named graph {}", namedGraph);
URI contextURI = con.getValueFactory().createURI(namedGraph.replaceAll("\"", ""));
compareGraphs(Iterations.asList(con.getStatements(null, null, null, true, contextURI)),
Iterations.asList(erCon.getStatements(null, null, null, true, contextURI)));
}
}
catch(Exception e) {
if(con.isActive()) {
con.rollback();
}
throw e;
}
finally {
con.close();
erCon.close();
}
}
protected void compareGraphs(Iterable<? extends Statement> actual, Iterable<? extends Statement> expected)
throws Exception
{
if (!ModelUtil.equals(expected, actual)) {
StringBuilder message = new StringBuilder(128);
message.append("\n=========================================\n");
message.append(getName());
message.append("\n");
message.append(testURI);
message.append("\n=========================================\n");
message.append("Expected results: \n");
for (Statement bs : expected) {
message.append(bs);
message.append("\n");
}
message.append("=========================================\n");
message.append("Bigdata results: \n");
for (Statement bs : actual) {
message.append(bs);
message.append("\n");
}
message.append("=========================================\n");
final String queryStr = readUpdateString();
message.append("Query:\n"+queryStr);
message.append("\n=========================================\n");
// message.append("Data:\n"+readInputData(dataset));
// message.append("\n=========================================\n");
logger.error(message.toString());
fail(message.toString());
}
}
protected String readUpdateString()
throws IOException
{
InputStream stream = new URL(requestFileURL).openStream();
try {
return IOUtil.readString(new InputStreamReader(stream, "UTF-8"));
}
finally {
stream.close();
}
}
public interface Factory {
SPARQLUpdateConformanceTest createSPARQLUpdateConformanceTest(String testURI, String name,
String requestFile, URI defaultGraphURI, Map<String, URI> inputNamedGraphs,
URI resultDefaultGraphURI, Map<String, URI> resultNamedGraphs);
}
public static TestSuite suite(String manifestFileURL, Factory factory)
throws Exception
{
return suite(manifestFileURL, factory, true);
}
public static TestSuite suite(String manifestFileURL, Factory factory, boolean approvedOnly)
throws Exception
{
logger.info("Building test suite for {}", manifestFileURL);
TestSuite suite = new TestSuite(factory.getClass().getName());
// Read manifest and create declared test cases
Repository manifestRep = new SailRepository(new MemoryStore());
manifestRep.initialize();
RepositoryConnection con = manifestRep.getConnection();
ManifestTest.addTurtle(con, new URL(manifestFileURL), manifestFileURL);
suite.setName(getManifestName(manifestRep, con, manifestFileURL));
// Extract test case information from the manifest file. Note that we only
// select those test cases that are mentioned in the list.
StringBuilder query = new StringBuilder(512);
query.append(" SELECT DISTINCT testURI, testName, result, action, requestFile, defaultGraph, resultDefaultGraph ");
query.append(" FROM {} rdf:first {testURI} rdf:type {mf:UpdateEvaluationTest}; ");
if (approvedOnly) {
query.append(" dawgt:approval {dawgt:Approved}; ");
}
query.append(" mf:name {testName}; ");
query.append(" mf:action {action} ut:request {requestFile}; ");
query.append(" [ ut:data {defaultGraph} ], ");
query.append(" {testURI} mf:result {result} [ut:data {resultDefaultGraph}] ");
query.append(" USING NAMESPACE ");
query.append(" mf = <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#>, ");
query.append(" dawgt = <http://www.w3.org/2001/sw/DataAccess/tests/test-dawg#>, ");
query.append(" qt = <http://www.w3.org/2001/sw/DataAccess/tests/test-query#>, ");
query.append(" ut = <http://www.w3.org/2009/sparql/tests/test-update#>, ");
query.append(" sd = <http://www.w3.org/ns/sparql-service-description#>, ");
query.append(" ent = <http://www.w3.org/ns/entailment/> ");
TupleQuery testCaseQuery = con.prepareTupleQuery(QueryLanguage.SERQL, query.toString());
query.setLength(0);
query.append(" SELECT DISTINCT namedGraphData, namedGraphLabel ");
query.append(" FROM {graphDef} ut:graphData {} ut:graph {namedGraphData} ; ");
query.append(" rdfs:label {namedGraphLabel} ");
query.append(" USING NAMESPACE ");
query.append(" ut = <http://www.w3.org/2009/sparql/tests/test-update#> ");
TupleQuery namedGraphsQuery = con.prepareTupleQuery(QueryLanguage.SERQL, query.toString());
logger.debug("evaluating query..");
TupleQueryResult testCases = testCaseQuery.evaluate();
while (testCases.hasNext()) {
BindingSet bindingSet = testCases.next();
URI testURI = (URI)bindingSet.getValue("testURI");
String testName = bindingSet.getValue("testName").toString();
Value result = bindingSet.getValue("result");
Value action = bindingSet.getValue("action");
URI requestFile = (URI)bindingSet.getValue("requestFile");
URI defaultGraphURI = (URI)bindingSet.getValue("defaultGraph");
URI resultDefaultGraphURI = (URI)bindingSet.getValue("resultDefaultGraph");
logger.debug("found test case : {}", testName);
// Query input named graphs
namedGraphsQuery.setBinding("graphDef", action);
TupleQueryResult inputNamedGraphsResult = namedGraphsQuery.evaluate();
HashMap<String, URI> inputNamedGraphs = new HashMap<String, URI>();
if (inputNamedGraphsResult.hasNext()) {
while (inputNamedGraphsResult.hasNext()) {
BindingSet graphBindings = inputNamedGraphsResult.next();
URI namedGraphData = (URI)graphBindings.getValue("namedGraphData");
String namedGraphLabel = ((Literal)graphBindings.getValue("namedGraphLabel")).getLabel();
logger.debug(" adding named graph : {}", namedGraphLabel);
inputNamedGraphs.put(namedGraphLabel, namedGraphData);
}
}
// Query result named graphs
namedGraphsQuery.setBinding("graphDef", result);
TupleQueryResult resultNamedGraphsResult = namedGraphsQuery.evaluate();
HashMap<String, URI> resultNamedGraphs = new HashMap<String, URI>();
if (resultNamedGraphsResult.hasNext()) {
while (resultNamedGraphsResult.hasNext()) {
BindingSet graphBindings = resultNamedGraphsResult.next();
URI namedGraphData = (URI)graphBindings.getValue("namedGraphData");
String namedGraphLabel = ((Literal)graphBindings.getValue("namedGraphLabel")).getLabel();
logger.debug(" adding named graph : {}", namedGraphLabel);
resultNamedGraphs.put(namedGraphLabel, namedGraphData);
}
}
SPARQLUpdateConformanceTest test = factory.createSPARQLUpdateConformanceTest(testURI.toString(),
testName, requestFile.toString(), defaultGraphURI, inputNamedGraphs, resultDefaultGraphURI,
resultNamedGraphs);
if (test != null) {
suite.addTest(test);
}
}
testCases.close();
con.close();
manifestRep.shutDown();
logger.info("Created test suite with " + suite.countTestCases() + " test cases.");
return suite;
}
protected static String getManifestName(Repository manifestRep, RepositoryConnection con,
String manifestFileURL)
throws QueryEvaluationException, RepositoryException, MalformedQueryException
{
// Try to extract suite name from manifest file
TupleQuery manifestNameQuery = con.prepareTupleQuery(QueryLanguage.SERQL,
"SELECT ManifestName FROM {ManifestURL} rdfs:label {ManifestName}");
manifestNameQuery.setBinding("ManifestURL", manifestRep.getValueFactory().createURI(manifestFileURL));
TupleQueryResult manifestNames = manifestNameQuery.evaluate();
try {
if (manifestNames.hasNext()) {
return manifestNames.next().getValue("ManifestName").stringValue();
}
}
finally {
manifestNames.close();
}
// Derive name from manifest URL
int lastSlashIdx = manifestFileURL.lastIndexOf('/');
int secLastSlashIdx = manifestFileURL.lastIndexOf('/', lastSlashIdx - 1);
return manifestFileURL.substring(secLastSlashIdx + 1, lastSlashIdx);
}
}