forked from LinkedDataFragments/Server.Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request LinkedDataFragments#58 from awoods/issue-57
Implement SPARQL datasource
- Loading branch information
Showing
6 changed files
with
479 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
...in/java/org/linkeddatafragments/datasource/sparql/SparqlBasedRequestProcessorForTPFs.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
src/main/java/org/linkeddatafragments/datasource/sparql/SparqlDataSource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
src/main/java/org/linkeddatafragments/datasource/sparql/SparqlDataSourceType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.