Skip to content

Commit 0db5c1c

Browse files
author
Andrew Woods
committed
Implement SPARQL datasource
Resolves: LinkedDataFragments#57
1 parent 3b20b53 commit 0db5c1c

File tree

6 files changed

+144
-44
lines changed

6 files changed

+144
-44
lines changed

config-example.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
"datasourcetypes": {
55
"HdtDatasource" : "org.linkeddatafragments.datasource.hdt.HdtDataSourceType",
6-
"JenaTDBDatasource" : "org.linkeddatafragments.datasource.tdb.JenaTDBDataSourceType"
6+
"JenaTDBDatasource" : "org.linkeddatafragments.datasource.tdb.JenaTDBDataSourceType",
7+
"SparqlDatasource" : "org.linkeddatafragments.datasource.sparql.SparqlDataSourceType"
78
},
89

910
"datasources": {
@@ -25,7 +26,15 @@
2526
"description": "Semantic Web with a TDB back-end",
2627
"settings": { "directory": "/tmp/tdbModels",
2728
"graph": "http://vitro.mannlib.cornell.edu/default/vitro-kb-2" }
28-
},
29+
},
30+
"vivo-sparql": {
31+
"title": "Semantic SPARQL",
32+
"type": "SparqlDatasource",
33+
"description": "Semantic Web with a SPARQL back-end",
34+
"settings": { "endpoint": "http://localhost:8080/vivo/api/sparqlQuery",
35+
"username": "some-username",
36+
"password": "some-password" }
37+
},
2938

3039
"prefixes": {
3140
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",

pom.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@
7373
<artifactId>jena-tdb</artifactId>
7474
<version>${jenaVersion}</version>
7575
</dependency>
76+
<dependency>
77+
<groupId>org.apache.jena</groupId>
78+
<artifactId>jena-fuseki-main</artifactId>
79+
<version>${jenaVersion}</version>
80+
<scope>test</scope>
81+
</dependency>
7682
<dependency>
7783
<groupId>org.apache.httpcomponents</groupId>
7884
<artifactId>httpclient</artifactId>
@@ -225,6 +231,34 @@
225231
</execution>
226232
</executions>
227233
</plugin>
234+
<plugin>
235+
<groupId>org.codehaus.mojo</groupId>
236+
<artifactId>build-helper-maven-plugin</artifactId>
237+
<version>3.0.0</version>
238+
<executions>
239+
<execution>
240+
<goals>
241+
<goal>reserve-network-port</goal>
242+
</goals>
243+
<phase>process-resources</phase>
244+
<configuration>
245+
<portNames>
246+
<portName>fuseki.test.port</portName>
247+
</portNames>
248+
</configuration>
249+
</execution>
250+
</executions>
251+
</plugin>
252+
<plugin>
253+
<groupId>org.apache.maven.plugins</groupId>
254+
<artifactId>maven-surefire-plugin</artifactId>
255+
<version>3.0.0-M3</version>
256+
<configuration>
257+
<systemPropertyVariables>
258+
<fuseki.port>${fuseki.test.port}</fuseki.port>
259+
</systemPropertyVariables>
260+
</configuration>
261+
</plugin>
228262
<plugin>
229263
<groupId>org.apache.maven.plugins</groupId>
230264
<artifactId>maven-gpg-plugin</artifactId>

src/main/java/org/linkeddatafragments/datasource/sparql/SparqlBasedRequestProcessorForTPFs.java

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.linkeddatafragments.datasource.sparql;
22

3-
import java.io.File;
4-
import org.apache.jena.query.Dataset;
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
6+
import org.apache.http.client.utils.URIBuilder;
7+
import org.apache.jena.query.ParameterizedSparqlString;
58
import org.apache.jena.query.Query;
69
import org.apache.jena.query.QueryExecution;
710
import org.apache.jena.query.QueryExecutionFactory;
@@ -14,7 +17,6 @@
1417
import org.apache.jena.rdf.model.Model;
1518
import org.apache.jena.rdf.model.ModelFactory;
1619
import org.apache.jena.rdf.model.RDFNode;
17-
import org.apache.jena.tdb.TDBFactory;
1820

1921
import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
2022
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
@@ -24,18 +26,19 @@
2426

2527
/**
2628
* Implementation of {@link IFragmentRequestProcessor} that processes
27-
* {@link ITriplePatternFragmentRequest}s over data stored in Jena TDB.
29+
* {@link ITriplePatternFragmentRequest}s over data stored behind a SPARQL-Query endpoint.
2830
*
2931
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
3032
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
3133
*/
3234
public class SparqlBasedRequestProcessorForTPFs
3335
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
3436
{
35-
private final Dataset tdb;
36-
private final String defaultGraph;
37-
private final String sparql = "CONSTRUCT WHERE { ?s ?p ?o } " +
38-
"ORDER BY ?s ?p ?o";
37+
private final URI endpointURI;
38+
private final String username;
39+
private final String password;
40+
41+
private final String sparql = "CONSTRUCT WHERE { ?s ?p ?o } ";
3942

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

@@ -96,13 +99,6 @@ protected ILinkedDataFragment createFragment(
9699
// e.g., (?x foaf:knows ?x ) or (_:bn foaf:knows _:bn)
97100
// see https://github.com/LinkedDataFragments/Server.Java/issues/24
98101

99-
Model model;
100-
if (defaultGraph == null) {
101-
model = tdb.getDefaultModel();
102-
} else {
103-
model = tdb.getNamedModel(defaultGraph);
104-
}
105-
106102
QuerySolutionMap map = new QuerySolutionMap();
107103
if ( ! subject.isVariable() ) {
108104
map.add("s", subject.asConstantTerm());
@@ -119,7 +115,20 @@ protected ILinkedDataFragment createFragment(
119115

120116
Model triples = ModelFactory.createDefaultModel();
121117

122-
try (QueryExecution qexec = QueryExecutionFactory.create(query, model, map)) {
118+
// Build the SPARQL-endpoint
119+
URIBuilder uriBuilder = new URIBuilder(endpointURI);
120+
addCredentials(uriBuilder);
121+
122+
final String endpoint;
123+
try {
124+
endpoint = uriBuilder.build().toString();
125+
} catch (URISyntaxException e) {
126+
throw new RuntimeException(e);
127+
}
128+
129+
ParameterizedSparqlString queryWithParams = new ParameterizedSparqlString(query.serialize(), map);
130+
131+
try (QueryExecution qexec = QueryExecutionFactory.sparqlService(endpoint, queryWithParams.asQuery())) {
123132
qexec.execConstruct(triples);
124133
}
125134

@@ -131,7 +140,9 @@ protected ILinkedDataFragment createFragment(
131140
long size = triples.size();
132141
long estimate = -1;
133142

134-
try (QueryExecution qexec = QueryExecutionFactory.create(countQuery, model, map)) {
143+
ParameterizedSparqlString countQueryWithParams = new ParameterizedSparqlString(countQuery.serialize(), map);
144+
145+
try (QueryExecution qexec = QueryExecutionFactory.createServiceRequest(endpoint, countQueryWithParams.asQuery())) {
135146
ResultSet results = qexec.execSelect();
136147
if (results.hasNext()) {
137148
QuerySolution soln = results.nextSolution() ;
@@ -158,20 +169,33 @@ protected ILinkedDataFragment createFragment(
158169
return createTriplePatternFragment( triples, estimate, isLastPage );
159170
}
160171

172+
/**
173+
* This method adds 'email' and 'password' parameters to the provided URIBuilder
174+
* Note: This credentials approach is very specific to the VIVO (https://github.com/vivo-project/VIVO)
175+
* application and should be refactored once another use-case/example is required.
176+
*
177+
* @param uriBuilder of SPARQL-Query endpoint
178+
*/
179+
private void addCredentials(URIBuilder uriBuilder) {
180+
if (username != null && password != null) {
181+
uriBuilder.addParameter("email", username);
182+
uriBuilder.addParameter("password", password);
183+
}
184+
}
185+
161186
} // end of class Worker
162187

163188

164189
/**
165190
* Constructor
166191
*
167-
* @param tdbdir directory used for TDB backing
192+
* @param endpointURI of SPARQL-Query service
193+
* @param username for SPARQL-Query service
194+
* @param password for SPARQL-Query service
168195
*/
169-
public SparqlBasedRequestProcessorForTPFs(File tdbdir) {
170-
this(tdbdir, null);
171-
}
172-
173-
public SparqlBasedRequestProcessorForTPFs(File tdbdir, String defaultGraph) {
174-
this.defaultGraph = defaultGraph;
175-
this.tdb = TDBFactory.createDataset(tdbdir.getAbsolutePath());
196+
public SparqlBasedRequestProcessorForTPFs(URI endpointURI, String username, String password) {
197+
this.endpointURI = endpointURI;
198+
this.username = username;
199+
this.password = password;
176200
}
177201
}

src/main/java/org/linkeddatafragments/datasource/sparql/SparqlDataSource.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package org.linkeddatafragments.datasource.sparql;
22

33
import java.io.File;
4+
import java.net.URI;
45

56
import org.linkeddatafragments.datasource.DataSourceBase;
67
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
78
import org.linkeddatafragments.fragments.IFragmentRequestParser;
89
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;
910

1011
/**
11-
* Experimental Jena TDB-backed data source of Basic Linked Data Fragments.
12+
* Experimental SPARQL-endpoint-backed data source of Basic Linked Data Fragments.
1213
*
1314
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
1415
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
16+
* @author <a href="https://awoods.io">Andrew Woods</a>
1517
*/
1618
public class SparqlDataSource extends DataSourceBase {
1719

@@ -38,10 +40,13 @@ public IFragmentRequestProcessor getRequestProcessor()
3840
*
3941
* @param title
4042
* @param description
41-
* @param tdbdir directory used for TDB backing
4243
*/
43-
public SparqlDataSource(String title, String description, File tdbdir, String graph) {
44+
public SparqlDataSource(String title,
45+
String description,
46+
URI endpoint,
47+
String username,
48+
String password) {
4449
super(title, description);
45-
requestProcessor = new SparqlBasedRequestProcessorForTPFs( tdbdir , graph);
50+
requestProcessor = new SparqlBasedRequestProcessorForTPFs(endpoint, username, password);
4651
}
4752
}

src/main/java/org/linkeddatafragments/datasource/sparql/SparqlDataSourceType.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.linkeddatafragments.datasource.sparql;
22

3-
import java.io.File;
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
45

6+
import org.apache.http.client.utils.URIBuilder;
57
import org.linkeddatafragments.datasource.IDataSource;
68
import org.linkeddatafragments.datasource.IDataSourceType;
79
import org.linkeddatafragments.exceptions.DataSourceCreationException;
@@ -10,9 +12,10 @@
1012

1113
/**
1214
* The type of Triple Pattern Fragment data sources that are backed by
13-
* a Jena TDB instance.
15+
* a SPARQL endpoint.
1416
*
1517
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
18+
* @author <a href="https://awoods.io">Andrew Woods</a>
1619
*/
1720
public class SparqlDataSourceType implements IDataSourceType
1821
{
@@ -22,17 +25,31 @@ public IDataSource createDataSource( final String title,
2225
final JsonObject settings )
2326
throws DataSourceCreationException
2427
{
25-
final String dname = settings.getAsJsonPrimitive("directory").getAsString();
26-
final File dir = new File( dname );
28+
// Required: Set the SPARQL endpoint
29+
if ( ! settings.has("endpoint")) {
30+
throw new DataSourceCreationException("SparqlDataSource", "Missing required configuration element: 'endpoint'");
31+
}
32+
33+
URI endpoint;
34+
try {
35+
endpoint = new URIBuilder(settings.getAsJsonPrimitive("endpoint").getAsString()).build();
36+
} catch (URISyntaxException e) {
37+
throw new DataSourceCreationException("SparqlDataSource", "Configuration element, 'endpoint', must be a valid URI");
38+
}
39+
40+
// Set SPARQL endpoint credentials, if provided
41+
String username = null;
42+
if (settings.has("username")) {
43+
username = settings.getAsJsonPrimitive("username").getAsString();
44+
}
2745

28-
// Set the defaultGraph, if provided
29-
String graph = null;
30-
if (settings.has("graph")) {
31-
graph = settings.getAsJsonPrimitive("graph").getAsString();
46+
String password = null;
47+
if (settings.has("password")) {
48+
password = settings.getAsJsonPrimitive("password").getAsString();
3249
}
3350

3451
try {
35-
return new SparqlDataSource(title, description, dir, graph);
52+
return new SparqlDataSource(title, description, endpoint, username, password);
3653
} catch (Exception ex) {
3754
throw new DataSourceCreationException(ex);
3855
}

src/test/java/org/linkeddatafragments/test/datasource/SparqlDataSourceTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.gson.JsonObject;
44

5+
import org.apache.jena.fuseki.main.FusekiServer;
56
import org.apache.jena.query.Dataset;
67
import org.apache.jena.rdf.model.Model;
78
import org.apache.jena.tdb.TDBFactory;
@@ -22,18 +23,19 @@
2223
import org.linkeddatafragments.datasource.DataSourceFactory;
2324
import org.linkeddatafragments.datasource.DataSourceTypesRegistry;
2425
import org.linkeddatafragments.datasource.sparql.SparqlDataSourceType;
25-
import org.linkeddatafragments.datasource.tdb.JenaTDBDataSourceType;
2626
import org.linkeddatafragments.util.TriplePatternElementParser;
2727
import org.linkeddatafragments.util.TriplePatternElementParserForJena;
2828

2929
/**
3030
*
3131
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
32+
* @author <a href="https://awoods.io">Andrew Woods</a>
3233
*/
3334
public class SparqlDataSourceTest extends DataSourceTest<RDFNode,String,String>
3435
{
3536
private static File jena;
3637
private static Dataset dataset;
38+
private static FusekiServer fuseki;
3739

3840
/**
3941
*
@@ -59,7 +61,7 @@ public static void setUpClass() throws Exception {
5961
}
6062

6163
String tmpdir = System.getProperty("java.io.tmpdir");
62-
jena = new File(tmpdir, "ldf-jena-test");
64+
jena = new File(tmpdir, "ldf-sparql-test");
6365
jena.mkdir();
6466

6567
dataset = TDBFactory.createDataset(jena.getAbsolutePath());
@@ -68,12 +70,19 @@ public static void setUpClass() throws Exception {
6870
InputStream in = ClassLoader.getSystemResourceAsStream("demo.nt");
6971
RDFDataMgr.read(model, in, Lang.NTRIPLES);
7072

73+
// Dynamically-generated port comes from pom.xml configuration: build-helper-maven-plugin
74+
int fusekiPort = Integer.parseInt(System.getProperty("fuseki.port"));
75+
76+
// Create Fuseki, loaded with the test dataset
77+
fuseki = FusekiServer.create().setPort(fusekiPort).add("/ds", dataset).build();
78+
fuseki.start();
79+
7180
// Everything is in place, now create the LDF datasource
72-
JsonObject config = createConfig("jena tdb test", "jena tdb test",
81+
JsonObject config = createConfig("sparql test", "sparql test",
7382
typeName);
7483

7584
JsonObject settings = new JsonObject();
76-
settings.addProperty("directory", jena.getAbsolutePath());
85+
settings.addProperty("endpoint", "http://localhost:" + fusekiPort + "/ds");
7786
config.add("settings", settings);
7887

7988
setDatasource(DataSourceFactory.create(config));
@@ -85,6 +94,8 @@ public static void setUpClass() throws Exception {
8594
*/
8695
@AfterClass
8796
public static void tearDownClass() throws Exception {
97+
fuseki.stop();
98+
8899
TDBFactory.release(dataset);
89100
File[] files = jena.listFiles();
90101
for (File f : files) {

0 commit comments

Comments
 (0)