Skip to content

Commit

Permalink
Merge pull request LinkedDataFragments#58 from awoods/issue-57
Browse files Browse the repository at this point in the history
Implement SPARQL datasource
  • Loading branch information
mielvds authored Oct 22, 2019
2 parents cb5c8d7 + 0db5c1c commit 8052ca2
Show file tree
Hide file tree
Showing 6 changed files with 479 additions and 2 deletions.
13 changes: 11 additions & 2 deletions config-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

"datasourcetypes": {
"HdtDatasource" : "org.linkeddatafragments.datasource.hdt.HdtDataSourceType",
"JenaTDBDatasource" : "org.linkeddatafragments.datasource.tdb.JenaTDBDataSourceType"
"JenaTDBDatasource" : "org.linkeddatafragments.datasource.tdb.JenaTDBDataSourceType",
"SparqlDatasource" : "org.linkeddatafragments.datasource.sparql.SparqlDataSourceType"
},

"datasources": {
Expand All @@ -25,7 +26,15 @@
"description": "Semantic Web with a TDB back-end",
"settings": { "directory": "/tmp/tdbModels",
"graph": "http://vitro.mannlib.cornell.edu/default/vitro-kb-2" }
},
},
"vivo-sparql": {
"title": "Semantic SPARQL",
"type": "SparqlDatasource",
"description": "Semantic Web with a SPARQL back-end",
"settings": { "endpoint": "http://localhost:8080/vivo/api/sparqlQuery",
"username": "some-username",
"password": "some-password" }
},

"prefixes": {
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
Expand Down
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
<artifactId>jena-tdb</artifactId>
<version>${jenaVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-fuseki-main</artifactId>
<version>${jenaVersion}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
Expand Down Expand Up @@ -225,6 +231,34 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>reserve-network-port</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<portNames>
<portName>fuseki.test.port</portName>
</portNames>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<systemPropertyVariables>
<fuseki.port>${fuseki.test.port}</fuseki.port>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package org.linkeddatafragments.datasource.sparql;

import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.client.utils.URIBuilder;
import org.apache.jena.query.ParameterizedSparqlString;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.QuerySolutionMap;
import org.apache.jena.query.ResultSet;
import org.apache.jena.query.Syntax;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.RDFNode;

import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;

/**
* Implementation of {@link IFragmentRequestProcessor} that processes
* {@link ITriplePatternFragmentRequest}s over data stored behind a SPARQL-Query endpoint.
*
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
*/
public class SparqlBasedRequestProcessorForTPFs
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
{
private final URI endpointURI;
private final String username;
private final String password;

private final String sparql = "CONSTRUCT WHERE { ?s ?p ?o } ";

private final String count = "SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o }";

private final Query query = QueryFactory.create(sparql, Syntax.syntaxSPARQL_11);
private final Query countQuery = QueryFactory.create(count, Syntax.syntaxSPARQL_11);

/**
*
* @param request
* @return
* @throws IllegalArgumentException
*/
@Override
protected Worker getTPFSpecificWorker(
final ITriplePatternFragmentRequest<RDFNode,String,String> request )
throws IllegalArgumentException
{
return new Worker( request );
}

/**
*
*/
protected class Worker
extends AbstractRequestProcessorForTriplePatterns.Worker<RDFNode,String,String>
{

/**
*
* @param req
*/
public Worker(
final ITriplePatternFragmentRequest<RDFNode,String,String> req )
{
super( req );
}

/**
*
* @param subject
* @param predicate
* @param object
* @param offset
* @param limit
* @return
*/
@Override
protected ILinkedDataFragment createFragment(
final ITriplePatternElement<RDFNode,String,String> subject,
final ITriplePatternElement<RDFNode,String,String> predicate,
final ITriplePatternElement<RDFNode,String,String> object,
final long offset,
final long limit )
{
// FIXME: The following algorithm is incorrect for cases in which
// the requested triple pattern contains a specific variable
// multiple times;
// e.g., (?x foaf:knows ?x ) or (_:bn foaf:knows _:bn)
// see https://github.com/LinkedDataFragments/Server.Java/issues/24

QuerySolutionMap map = new QuerySolutionMap();
if ( ! subject.isVariable() ) {
map.add("s", subject.asConstantTerm());
}
if ( ! predicate.isVariable() ) {
map.add("p", predicate.asConstantTerm());
}
if ( ! object.isVariable() ) {
map.add("o", object.asConstantTerm());
}

query.setOffset(offset);
query.setLimit(limit);

Model triples = ModelFactory.createDefaultModel();

// Build the SPARQL-endpoint
URIBuilder uriBuilder = new URIBuilder(endpointURI);
addCredentials(uriBuilder);

final String endpoint;
try {
endpoint = uriBuilder.build().toString();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}

ParameterizedSparqlString queryWithParams = new ParameterizedSparqlString(query.serialize(), map);

try (QueryExecution qexec = QueryExecutionFactory.sparqlService(endpoint, queryWithParams.asQuery())) {
qexec.execConstruct(triples);
}

if (triples.isEmpty()) {
return createEmptyTriplePatternFragment();
}

// Try to get an estimate
long size = triples.size();
long estimate = -1;

ParameterizedSparqlString countQueryWithParams = new ParameterizedSparqlString(countQuery.serialize(), map);

try (QueryExecution qexec = QueryExecutionFactory.createServiceRequest(endpoint, countQueryWithParams.asQuery())) {
ResultSet results = qexec.execSelect();
if (results.hasNext()) {
QuerySolution soln = results.nextSolution() ;
Literal literal = soln.getLiteral("count");
estimate = literal.getLong();
}
}

/*GraphStatisticsHandler stats = model.getGraph().getStatisticsHandler();
if (stats != null) {
Node s = (subject != null) ? subject.asNode() : null;
Node p = (predicate != null) ? predicate.asNode() : null;
Node o = (object != null) ? object.asNode() : null;
estimate = stats.getStatistic(s, p, o);
}*/

// No estimate or incorrect
if (estimate < offset + size) {
estimate = (size == limit) ? offset + size + 1 : offset + size;
}

// create the fragment
final boolean isLastPage = ( estimate < offset + limit );
return createTriplePatternFragment( triples, estimate, isLastPage );
}

/**
* This method adds 'email' and 'password' parameters to the provided URIBuilder
* Note: This credentials approach is very specific to the VIVO (https://github.com/vivo-project/VIVO)
* application and should be refactored once another use-case/example is required.
*
* @param uriBuilder of SPARQL-Query endpoint
*/
private void addCredentials(URIBuilder uriBuilder) {
if (username != null && password != null) {
uriBuilder.addParameter("email", username);
uriBuilder.addParameter("password", password);
}
}

} // end of class Worker


/**
* Constructor
*
* @param endpointURI of SPARQL-Query service
* @param username for SPARQL-Query service
* @param password for SPARQL-Query service
*/
public SparqlBasedRequestProcessorForTPFs(URI endpointURI, String username, String password) {
this.endpointURI = endpointURI;
this.username = username;
this.password = password;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.linkeddatafragments.datasource.sparql;

import java.io.File;
import java.net.URI;

import org.linkeddatafragments.datasource.DataSourceBase;
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
import org.linkeddatafragments.fragments.IFragmentRequestParser;
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;

/**
* Experimental SPARQL-endpoint-backed data source of Basic Linked Data Fragments.
*
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
* @author <a href="https://awoods.io">Andrew Woods</a>
*/
public class SparqlDataSource extends DataSourceBase {

/**
* The request processor
*
*/
protected final SparqlBasedRequestProcessorForTPFs requestProcessor;

@Override
public IFragmentRequestParser getRequestParser()
{
return TPFRequestParserForJenaBackends.getInstance();
}

@Override
public IFragmentRequestProcessor getRequestProcessor()
{
return requestProcessor;
}

/**
* Constructor
*
* @param title
* @param description
*/
public SparqlDataSource(String title,
String description,
URI endpoint,
String username,
String password) {
super(title, description);
requestProcessor = new SparqlBasedRequestProcessorForTPFs(endpoint, username, password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.linkeddatafragments.datasource.sparql;

import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.client.utils.URIBuilder;
import org.linkeddatafragments.datasource.IDataSource;
import org.linkeddatafragments.datasource.IDataSourceType;
import org.linkeddatafragments.exceptions.DataSourceCreationException;

import com.google.gson.JsonObject;

/**
* The type of Triple Pattern Fragment data sources that are backed by
* a SPARQL endpoint.
*
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
* @author <a href="https://awoods.io">Andrew Woods</a>
*/
public class SparqlDataSourceType implements IDataSourceType
{
@Override
public IDataSource createDataSource( final String title,
final String description,
final JsonObject settings )
throws DataSourceCreationException
{
// Required: Set the SPARQL endpoint
if ( ! settings.has("endpoint")) {
throw new DataSourceCreationException("SparqlDataSource", "Missing required configuration element: 'endpoint'");
}

URI endpoint;
try {
endpoint = new URIBuilder(settings.getAsJsonPrimitive("endpoint").getAsString()).build();
} catch (URISyntaxException e) {
throw new DataSourceCreationException("SparqlDataSource", "Configuration element, 'endpoint', must be a valid URI");
}

// Set SPARQL endpoint credentials, if provided
String username = null;
if (settings.has("username")) {
username = settings.getAsJsonPrimitive("username").getAsString();
}

String password = null;
if (settings.has("password")) {
password = settings.getAsJsonPrimitive("password").getAsString();
}

try {
return new SparqlDataSource(title, description, endpoint, username, password);
} catch (Exception ex) {
throw new DataSourceCreationException(ex);
}
}

}
Loading

0 comments on commit 8052ca2

Please sign in to comment.