Skip to content

Commit 8052ca2

Browse files
author
mielvds
authored
Merge pull request LinkedDataFragments#58 from awoods/issue-57
Implement SPARQL datasource
2 parents cb5c8d7 + 0db5c1c commit 8052ca2

File tree

6 files changed

+479
-2
lines changed

6 files changed

+479
-2
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>
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package org.linkeddatafragments.datasource.sparql;
2+
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;
8+
import org.apache.jena.query.Query;
9+
import org.apache.jena.query.QueryExecution;
10+
import org.apache.jena.query.QueryExecutionFactory;
11+
import org.apache.jena.query.QueryFactory;
12+
import org.apache.jena.query.QuerySolution;
13+
import org.apache.jena.query.QuerySolutionMap;
14+
import org.apache.jena.query.ResultSet;
15+
import org.apache.jena.query.Syntax;
16+
import org.apache.jena.rdf.model.Literal;
17+
import org.apache.jena.rdf.model.Model;
18+
import org.apache.jena.rdf.model.ModelFactory;
19+
import org.apache.jena.rdf.model.RDFNode;
20+
21+
import org.linkeddatafragments.datasource.AbstractRequestProcessorForTriplePatterns;
22+
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
23+
import org.linkeddatafragments.fragments.ILinkedDataFragment;
24+
import org.linkeddatafragments.fragments.tpf.ITriplePatternElement;
25+
import org.linkeddatafragments.fragments.tpf.ITriplePatternFragmentRequest;
26+
27+
/**
28+
* Implementation of {@link IFragmentRequestProcessor} that processes
29+
* {@link ITriplePatternFragmentRequest}s over data stored behind a SPARQL-Query endpoint.
30+
*
31+
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
32+
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
33+
*/
34+
public class SparqlBasedRequestProcessorForTPFs
35+
extends AbstractRequestProcessorForTriplePatterns<RDFNode,String,String>
36+
{
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 } ";
42+
43+
private final String count = "SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o }";
44+
45+
private final Query query = QueryFactory.create(sparql, Syntax.syntaxSPARQL_11);
46+
private final Query countQuery = QueryFactory.create(count, Syntax.syntaxSPARQL_11);
47+
48+
/**
49+
*
50+
* @param request
51+
* @return
52+
* @throws IllegalArgumentException
53+
*/
54+
@Override
55+
protected Worker getTPFSpecificWorker(
56+
final ITriplePatternFragmentRequest<RDFNode,String,String> request )
57+
throws IllegalArgumentException
58+
{
59+
return new Worker( request );
60+
}
61+
62+
/**
63+
*
64+
*/
65+
protected class Worker
66+
extends AbstractRequestProcessorForTriplePatterns.Worker<RDFNode,String,String>
67+
{
68+
69+
/**
70+
*
71+
* @param req
72+
*/
73+
public Worker(
74+
final ITriplePatternFragmentRequest<RDFNode,String,String> req )
75+
{
76+
super( req );
77+
}
78+
79+
/**
80+
*
81+
* @param subject
82+
* @param predicate
83+
* @param object
84+
* @param offset
85+
* @param limit
86+
* @return
87+
*/
88+
@Override
89+
protected ILinkedDataFragment createFragment(
90+
final ITriplePatternElement<RDFNode,String,String> subject,
91+
final ITriplePatternElement<RDFNode,String,String> predicate,
92+
final ITriplePatternElement<RDFNode,String,String> object,
93+
final long offset,
94+
final long limit )
95+
{
96+
// FIXME: The following algorithm is incorrect for cases in which
97+
// the requested triple pattern contains a specific variable
98+
// multiple times;
99+
// e.g., (?x foaf:knows ?x ) or (_:bn foaf:knows _:bn)
100+
// see https://github.com/LinkedDataFragments/Server.Java/issues/24
101+
102+
QuerySolutionMap map = new QuerySolutionMap();
103+
if ( ! subject.isVariable() ) {
104+
map.add("s", subject.asConstantTerm());
105+
}
106+
if ( ! predicate.isVariable() ) {
107+
map.add("p", predicate.asConstantTerm());
108+
}
109+
if ( ! object.isVariable() ) {
110+
map.add("o", object.asConstantTerm());
111+
}
112+
113+
query.setOffset(offset);
114+
query.setLimit(limit);
115+
116+
Model triples = ModelFactory.createDefaultModel();
117+
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())) {
132+
qexec.execConstruct(triples);
133+
}
134+
135+
if (triples.isEmpty()) {
136+
return createEmptyTriplePatternFragment();
137+
}
138+
139+
// Try to get an estimate
140+
long size = triples.size();
141+
long estimate = -1;
142+
143+
ParameterizedSparqlString countQueryWithParams = new ParameterizedSparqlString(countQuery.serialize(), map);
144+
145+
try (QueryExecution qexec = QueryExecutionFactory.createServiceRequest(endpoint, countQueryWithParams.asQuery())) {
146+
ResultSet results = qexec.execSelect();
147+
if (results.hasNext()) {
148+
QuerySolution soln = results.nextSolution() ;
149+
Literal literal = soln.getLiteral("count");
150+
estimate = literal.getLong();
151+
}
152+
}
153+
154+
/*GraphStatisticsHandler stats = model.getGraph().getStatisticsHandler();
155+
if (stats != null) {
156+
Node s = (subject != null) ? subject.asNode() : null;
157+
Node p = (predicate != null) ? predicate.asNode() : null;
158+
Node o = (object != null) ? object.asNode() : null;
159+
estimate = stats.getStatistic(s, p, o);
160+
}*/
161+
162+
// No estimate or incorrect
163+
if (estimate < offset + size) {
164+
estimate = (size == limit) ? offset + size + 1 : offset + size;
165+
}
166+
167+
// create the fragment
168+
final boolean isLastPage = ( estimate < offset + limit );
169+
return createTriplePatternFragment( triples, estimate, isLastPage );
170+
}
171+
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+
186+
} // end of class Worker
187+
188+
189+
/**
190+
* Constructor
191+
*
192+
* @param endpointURI of SPARQL-Query service
193+
* @param username for SPARQL-Query service
194+
* @param password for SPARQL-Query service
195+
*/
196+
public SparqlBasedRequestProcessorForTPFs(URI endpointURI, String username, String password) {
197+
this.endpointURI = endpointURI;
198+
this.username = username;
199+
this.password = password;
200+
}
201+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.linkeddatafragments.datasource.sparql;
2+
3+
import java.io.File;
4+
import java.net.URI;
5+
6+
import org.linkeddatafragments.datasource.DataSourceBase;
7+
import org.linkeddatafragments.datasource.IFragmentRequestProcessor;
8+
import org.linkeddatafragments.fragments.IFragmentRequestParser;
9+
import org.linkeddatafragments.fragments.tpf.TPFRequestParserForJenaBackends;
10+
11+
/**
12+
* Experimental SPARQL-endpoint-backed data source of Basic Linked Data Fragments.
13+
*
14+
* @author <a href="mailto:[email protected]">Bart Hanssens</a>
15+
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
16+
* @author <a href="https://awoods.io">Andrew Woods</a>
17+
*/
18+
public class SparqlDataSource extends DataSourceBase {
19+
20+
/**
21+
* The request processor
22+
*
23+
*/
24+
protected final SparqlBasedRequestProcessorForTPFs requestProcessor;
25+
26+
@Override
27+
public IFragmentRequestParser getRequestParser()
28+
{
29+
return TPFRequestParserForJenaBackends.getInstance();
30+
}
31+
32+
@Override
33+
public IFragmentRequestProcessor getRequestProcessor()
34+
{
35+
return requestProcessor;
36+
}
37+
38+
/**
39+
* Constructor
40+
*
41+
* @param title
42+
* @param description
43+
*/
44+
public SparqlDataSource(String title,
45+
String description,
46+
URI endpoint,
47+
String username,
48+
String password) {
49+
super(title, description);
50+
requestProcessor = new SparqlBasedRequestProcessorForTPFs(endpoint, username, password);
51+
}
52+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.linkeddatafragments.datasource.sparql;
2+
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
6+
import org.apache.http.client.utils.URIBuilder;
7+
import org.linkeddatafragments.datasource.IDataSource;
8+
import org.linkeddatafragments.datasource.IDataSourceType;
9+
import org.linkeddatafragments.exceptions.DataSourceCreationException;
10+
11+
import com.google.gson.JsonObject;
12+
13+
/**
14+
* The type of Triple Pattern Fragment data sources that are backed by
15+
* a SPARQL endpoint.
16+
*
17+
* @author <a href="http://olafhartig.de">Olaf Hartig</a>
18+
* @author <a href="https://awoods.io">Andrew Woods</a>
19+
*/
20+
public class SparqlDataSourceType implements IDataSourceType
21+
{
22+
@Override
23+
public IDataSource createDataSource( final String title,
24+
final String description,
25+
final JsonObject settings )
26+
throws DataSourceCreationException
27+
{
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+
}
45+
46+
String password = null;
47+
if (settings.has("password")) {
48+
password = settings.getAsJsonPrimitive("password").getAsString();
49+
}
50+
51+
try {
52+
return new SparqlDataSource(title, description, endpoint, username, password);
53+
} catch (Exception ex) {
54+
throw new DataSourceCreationException(ex);
55+
}
56+
}
57+
58+
}

0 commit comments

Comments
 (0)