Skip to content

Commit 3100978

Browse files
committed
Add paging support.
1 parent abb880c commit 3100978

File tree

5 files changed

+75
-11
lines changed

5 files changed

+75
-11
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
<artifactId>jena-arq</artifactId>
2222
<version>2.11.1</version>
2323
</dependency>
24+
<dependency>
25+
<groupId>org.apache.httpcomponents</groupId>
26+
<artifactId>httpclient</artifactId>
27+
<version>4.3.3</version>
28+
</dependency>
2429
<dependency>
2530
<groupId>com.google.code.gson</groupId>
2631
<artifactId>gson</artifactId>
@@ -30,6 +35,7 @@
3035
<groupId>javax.servlet</groupId>
3136
<artifactId>javax.servlet-api</artifactId>
3237
<version>3.0.1</version>
38+
<scope>provided</scope>
3339
</dependency>
3440
</dependencies>
3541
<build>

src/org/linkeddatafragments/datasource/DataSource.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
*/
1111
public interface DataSource {
1212
/**
13-
* Gets the Basic Linked Data Fragment matching the specified triple pattern.
13+
* Gets a page of the Basic Linked Data Fragment matching the specified triple pattern.
1414
* @param subject the subject (null to match any subject)
1515
* @param predicate the predicate (null to match any predicate)
1616
* @param object the object (null to match any object)
17+
* @param offset the triple index at which to start the page
18+
* @param limit the number of triples on the page
1719
* @return the first page of the fragment
1820
*/
19-
public BasicLinkedDataFragment getFragment(Resource subject, Property predicate, RDFNode object);
21+
public BasicLinkedDataFragment getFragment(Resource subject, Property predicate, RDFNode object,
22+
long offset, long limit);
2023
}

src/org/linkeddatafragments/datasource/HdtDataSource.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
* @author Ruben Verborgh
2222
*/
2323
public class HdtDataSource implements DataSource {
24-
private final static int TRIPLES_LIMIT = 100;
2524
private final HDT datasource;
2625
private final NodeDictionary dictionary;
2726

@@ -36,7 +35,10 @@ public HdtDataSource(String hdtFile) throws IOException {
3635
}
3736

3837
@Override
39-
public BasicLinkedDataFragment getFragment(Resource subject, Property predicate, RDFNode object) {
38+
public BasicLinkedDataFragment getFragment(Resource subject, Property predicate, RDFNode object, final long offset, final long limit) {
39+
if (offset < 0) throw new IndexOutOfBoundsException("offset");
40+
if (limit < 1) throw new IllegalArgumentException("limit");
41+
4042
// look up the result from the HDT datasource
4143
final int subjectId = subject == null ? 0 : dictionary.getIntID(subject.asNode(), TripleComponentRole.SUBJECT);
4244
final int predicateId = predicate == null ? 0 : dictionary.getIntID(predicate.asNode(), TripleComponentRole.PREDICATE);
@@ -50,9 +52,29 @@ public BasicLinkedDataFragment getFragment(Resource subject, Property predicate,
5052
@Override
5153
public Model getTriples() {
5254
final Model triples = ModelFactory.createDefaultModel();
53-
result.goToStart();
54-
for (int i = 0; i < TRIPLES_LIMIT && result.hasNext(); i++)
55-
triples.add(triples.asStatement(toTriple(result.next())));
55+
56+
// try to jump directly to the offset
57+
boolean atOffset;
58+
if (result.canGoTo()) {
59+
try {
60+
result.goTo(offset);
61+
atOffset = true;
62+
}
63+
// if the offset is outside the bounds, this page has no matches
64+
catch (IndexOutOfBoundsException exception) { atOffset = false; }
65+
}
66+
// if not possible, advance to the offset iteratively
67+
else {
68+
result.goToStart();
69+
for (int i = 0; !(atOffset = i == offset) && result.hasNext(); i++)
70+
result.next();
71+
}
72+
73+
// add `limit` triples to the result model
74+
if (atOffset) {
75+
for (int i = 0; i < limit && result.hasNext(); i++)
76+
triples.add(triples.asStatement(toTriple(result.next())));
77+
}
5678
return triples;
5779
}
5880

src/org/linkeddatafragments/servlet/BasicLdfServlet.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import javax.servlet.http.HttpServletRequest;
1414
import javax.servlet.http.HttpServletResponse;
1515

16+
import org.apache.http.client.utils.URIBuilder;
1617
import org.linkeddatafragments.config.ConfigReader;
1718
import org.linkeddatafragments.datasource.BasicLinkedDataFragment;
1819
import org.linkeddatafragments.datasource.DataSource;
@@ -37,6 +38,7 @@ public class BasicLdfServlet extends HttpServlet {
3738
private final static long serialVersionUID = 1L;
3839
private final static Pattern STRINGPATTERN = Pattern.compile("^\"(.*)\"(?:@(.*)|\\^\\^<(.*)>)?$");
3940
private final static TypeMapper types = TypeMapper.getInstance();
41+
private final static long TRIPLESPERPAGE = 100;
4042

4143
private ConfigReader config;
4244
private HashMap<String, DataSource> dataSources = new HashMap<String, DataSource>();
@@ -76,18 +78,21 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
7678
final Resource subject = parseAsResource(request.getParameter("subject"));
7779
final Property predicate = parseAsProperty(request.getParameter("predicate"));
7880
final RDFNode object = parseAsNode(request.getParameter("object"));
79-
final BasicLinkedDataFragment fragment = dataSource.getFragment(subject, predicate, object);
81+
final long page = Math.max(1, parseAsInteger(request.getParameter("page")));
82+
final long limit = TRIPLESPERPAGE, offset = limit * (page - 1);
83+
final BasicLinkedDataFragment fragment = dataSource.getFragment(subject, predicate, object, offset, limit);
8084

8185
// fill the output model
8286
final Model output = fragment.getTriples();
87+
final boolean isEmpty = output.size() == 0;
8388
output.setNsPrefixes(config.getPrefixes());
8489

8590
// add dataset metadata
8691
final String hostName = request.getHeader("Host");
8792
final String datasetUrl = request.getScheme() + "://" +
8893
(hostName == null ? request.getServerName() : hostName) + request.getRequestURI();
8994
final String fragmentUrl = query == null ? datasetUrl : (datasetUrl + "?" + query);
90-
final Resource datasetId = output.createResource(datasetUrl);
95+
final Resource datasetId = output.createResource(datasetUrl + "#dataset");
9196
final Resource fragmentId = output.createResource(fragmentUrl);
9297
output.add(datasetId, RDF_TYPE, VOID_DATASET);
9398
output.add(datasetId, RDF_TYPE, HYDRA_COLLECTION);
@@ -99,6 +104,20 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
99104
final Literal total = output.createTypedLiteral(fragment.getTotalSize(), XSDDatatype.XSDinteger);
100105
output.add(fragmentId, VOID_TRIPLES, total);
101106
output.add(fragmentId, HYDRA_TOTALITEMS, total);
107+
output.add(fragmentId, HYDRA_ITEMSPERPAGE, output.createTypedLiteral(limit, XSDDatatype.XSDinteger));
108+
109+
// add pages
110+
final URIBuilder pagedUrl = new URIBuilder(fragmentUrl);
111+
pagedUrl.setParameter("page", "1");
112+
output.add(fragmentId, HYDRA_FIRSTPAGE, output.createResource(pagedUrl.toString()));
113+
if (offset > 0) {
114+
pagedUrl.setParameter("page", Long.toString(page - 1));
115+
output.add(fragmentId, HYDRA_PREVIOUSPAGE, output.createResource(pagedUrl.toString()));
116+
}
117+
if (offset + limit < fragment.getTotalSize()) {
118+
pagedUrl.setParameter("page", Long.toString(page + 1));
119+
output.add(fragmentId, HYDRA_NEXTPAGE, output.createResource(pagedUrl.toString()));
120+
}
102121

103122
// add controls
104123
final Resource triplePattern = output.createResource();
@@ -118,17 +137,27 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
118137
output.add(objectMapping, HYDRA_PROPERTY, RDF_OBJECT);
119138

120139
// serialize the output as Turtle
121-
response.setStatus(fragment.getTotalSize() == 0 ? 404 : 200);
140+
response.setStatus(isEmpty ? 404 : 200);
122141
response.setHeader("Server", "Linked Data Fragments Server");
123142
response.setContentType("text/turtle");
124143
response.setCharacterEncoding("utf-8");
125-
output.write(response.getWriter(), "Turtle");
144+
output.write(response.getWriter(), "Turtle", fragmentUrl);
126145
}
127146
catch (Exception e) {
128147
throw new ServletException(e);
129148
}
130149
}
131150

151+
/**
152+
* Parses the given value as an integer.
153+
* @param value the value
154+
* @return the parsed value
155+
*/
156+
private int parseAsInteger(String value) {
157+
try { return Integer.parseInt(value); }
158+
catch (NumberFormatException ex) { return 0; }
159+
}
160+
132161
/**
133162
* Parses the given value as an RDF resource.
134163
* @param value the value

src/org/linkeddatafragments/util/CommonResources.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ public class CommonResources {
2626
public final static Property HYDRA_PROPERTY = createProperty(HYDRA + "property");
2727
public final static Property HYDRA_COLLECTION = createProperty(HYDRA + "Collection");
2828
public final static Property HYDRA_PAGEDCOLLECTION = createProperty(HYDRA + "PagedCollection");
29+
public final static Property HYDRA_FIRSTPAGE = createProperty(HYDRA + "firstPage");
30+
public final static Property HYDRA_LASTPAGE = createProperty(HYDRA + "lastPage");
31+
public final static Property HYDRA_NEXTPAGE = createProperty(HYDRA + "nextPage");
32+
public final static Property HYDRA_PREVIOUSPAGE = createProperty(HYDRA + "previousPage");
2933

3034
private final static Property createProperty(String uri) {
3135
return ResourceFactory.createProperty(uri);

0 commit comments

Comments
 (0)