Skip to content

Commit

Permalink
Adding paging links in RFC GeoJSON output format, and paging HTTP hea…
Browse files Browse the repository at this point in the history
…ders for all formats
  • Loading branch information
aaime committed Aug 3, 2018
1 parent dcb524a commit 2355d41
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs3.response;

import javax.servlet.http.HttpServletResponse;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.Request;
import org.geoserver.ows.Response;
import org.geoserver.platform.Operation;
import org.geoserver.wfs.request.FeatureCollectionResponse;

/** Appends paging links as HTTP headers for GetFeature responses */
public class HttpHeaderLinksAppender extends AbstractDispatcherCallback {

@Override
public Response responseDispatched(
Request request, Operation operation, Object result, Response response) {
// is this a feature response we are about to encode?
if (result instanceof FeatureCollectionResponse) {
HttpServletResponse httpResponse = request.getHttpResponse();
FeatureCollectionResponse fcr = (FeatureCollectionResponse) result;
String contentType = response.getMimeType(result, operation);
if (fcr.getPrevious() != null) {
addLink(httpResponse, "prev", contentType, fcr.getPrevious());
}
if (fcr.getNext() != null) {
addLink(httpResponse, "next", contentType, fcr.getNext());
}
}

return response;
}

private void addLink(
HttpServletResponse httpResponse, String rel, String contentType, String href) {
String headerValue = String.format("<%s>; rel=\"%s\"; type=\"%s\"", href, rel, contentType);
httpResponse.addHeader("Link", headerValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,38 @@ protected void writeExtraFeatureProperties(
Feature feature, Operation operation, GeoJSONBuilder jw) {
String featureId = WFS3_FEATURE_ID.get();
if (featureId != null) {
writeLinks(operation, jw, featureId);
writeLinks(null, operation, jw, featureId);
}
}

@Override
protected void writeExtraCollectionProperties(
protected void writePagingLinks(
FeatureCollectionResponse response, Operation operation, GeoJSONBuilder jw) {
writeLinks(operation, jw, null);
// we have more than just paging links here
writeLinks(response, operation, jw, null);
}

private void writeLinks(Operation operation, GeoJSONBuilder jw, String featureId) {
private void writeLinks(
FeatureCollectionResponse response,
Operation operation,
GeoJSONBuilder jw,
String featureId) {
List<String> formats = getAvailableFormats(FeatureCollectionResponse.class);
GetFeatureRequest request = GetFeatureRequest.adapt(operation.getParameters()[0]);
FeatureTypeInfo featureType = getFeatureType(request);
String baseUrl = request.getBaseUrl();
jw.key("links");
jw.array();
// paging links
if (response != null) {
if (response.getPrevious() != null) {
writeLink(jw, "Previous page", MIME, "prev", response.getPrevious());
}
if (response.getNext() != null) {
writeLink(jw, "Next page", MIME, "next", response.getNext());
}
}
// alternate/self links
for (String format : formats) {
String path = "wfs3/collections/" + NCNameResourceCodec.encode(featureType) + "/items";
if (featureId != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,6 @@
</property>
</bean>

<bean id="headerLinksAppender" class="org.geoserver.wfs3.response.HttpHeaderLinksAppender"/>

</beans>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
Expand All @@ -14,11 +15,14 @@
import com.jayway.jsonpath.DocumentContext;
import java.util.List;
import java.util.Map;
import net.minidev.json.JSONArray;
import org.geoserver.data.test.MockData;
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.ows.util.ResponseUtils;
import org.hamcrest.Matchers;
import org.jsoup.nodes.Document;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;

public class FeatureTest extends WFS3TestSupport {

Expand Down Expand Up @@ -158,11 +162,82 @@ public void testSingleFeatureAsGeoJson() throws Exception {
}

@Test
public void testLimit() throws Exception {
public void testFirstPage() throws Exception {
String expectedNextURL =
"http://localhost:8080/geoserver/wfs3/collections/cite__RoadSegments/items?startIndex=3&limit=3";

String roadSegments = getEncodedName(MockData.ROAD_SEGMENTS);
DocumentContext json =
getAsJSONPath("wfs3/collections/" + roadSegments + "/items?limit=3", 200);
MockHttpServletResponse response =
getAsMockHttpServletResponse(
"wfs3/collections/" + roadSegments + "/items?limit=3", 200);
List<String> links = response.getHeaders("Link");
assertThat(links, Matchers.hasSize(1));
assertEquals(
links.get(0),
"<" + expectedNextURL + ">; rel=\"next\"; type=\"application/geo+json\"");

DocumentContext json = getAsJSONPath(response);
assertEquals(3, (int) json.read("features.length()", Integer.class));
// check the paging link is there
assertThat(json.read("$.links[?(@.rel=='prev')].href"), Matchers.empty());
assertThat(
json.read("$.links[?(@.rel=='next')].href", JSONArray.class).get(0),
equalTo(expectedNextURL));
}

@Test
public void testMiddlePage() throws Exception {
String expectedPrevURL =
"http://localhost:8080/geoserver/wfs3/collections/cite__RoadSegments/items?startIndex=2&limit=1";
String expectedNextURL =
"http://localhost:8080/geoserver/wfs3/collections/cite__RoadSegments/items?startIndex=4&limit=1";

String roadSegments = getEncodedName(MockData.ROAD_SEGMENTS);
MockHttpServletResponse response =
getAsMockHttpServletResponse(
"wfs3/collections/" + roadSegments + "/items?startIndex=3&limit=1", 200);
List<String> links = response.getHeaders("Link");
assertThat(links, Matchers.hasSize(2));
assertEquals(
links.get(0),
"<" + expectedPrevURL + ">; rel=\"prev\"; type=\"application/geo+json\"");
assertEquals(
links.get(1),
"<" + expectedNextURL + ">; rel=\"next\"; type=\"application/geo+json\"");

DocumentContext json = getAsJSONPath(response);
assertEquals(1, (int) json.read("features.length()", Integer.class));
// check the paging link is there
assertThat(
json.read("$.links[?(@.rel=='prev')].href", JSONArray.class).get(0),
equalTo(expectedPrevURL));
assertThat(
json.read("$.links[?(@.rel=='next')].href", JSONArray.class).get(0),
equalTo(expectedNextURL));
}

@Test
public void testLastPage() throws Exception {
String expectedPrevLink =
"http://localhost:8080/geoserver/wfs3/collections/cite__RoadSegments/items?startIndex=0&limit=3";

String roadSegments = getEncodedName(MockData.ROAD_SEGMENTS);
MockHttpServletResponse response =
getAsMockHttpServletResponse(
"wfs3/collections/" + roadSegments + "/items?startIndex=3&limit=3", 200);
List<String> links = response.getHeaders("Link");
assertThat(links, Matchers.hasSize(1));
assertEquals(
links.get(0),
"<" + expectedPrevLink + ">; rel=\"prev\"; type=\"application/geo+json\"");

DocumentContext json = getAsJSONPath(response);
assertEquals(2, (int) json.read("features.length()", Integer.class));
// check the paging link is there
assertThat(
json.read("$.links[?(@.rel=='prev')].href", JSONArray.class).get(0),
equalTo(expectedPrevLink));
assertThat(json.read("$.links[?(@.rel=='next')].href"), Matchers.empty());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.internal.JsonContext;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -44,9 +45,12 @@ protected String getEncodedName(QName qName) {
}

protected DocumentContext getAsJSONPath(String path, int expectedHttpCode) throws Exception {
MockHttpServletResponse response = getAsServletResponse(path);
MockHttpServletResponse response = getAsMockHttpServletResponse(path, expectedHttpCode);
return getAsJSONPath(response);
}

assertEquals(expectedHttpCode, response.getStatus());
protected DocumentContext getAsJSONPath(MockHttpServletResponse response)
throws UnsupportedEncodingException {
assertThat(
response.getContentType(),
anyOf(startsWith("application/json"), startsWith("application/geo+json")));
Expand All @@ -57,6 +61,14 @@ protected DocumentContext getAsJSONPath(String path, int expectedHttpCode) throw
return json;
}

protected MockHttpServletResponse getAsMockHttpServletResponse(
String path, int expectedHttpCode) throws Exception {
MockHttpServletResponse response = getAsServletResponse(path);

assertEquals(expectedHttpCode, response.getStatus());
return response;
}

@Override
protected void onSetUp(SystemTestData testData) {
// init xmlunit
Expand All @@ -70,8 +82,7 @@ protected void onSetUp(SystemTestData testData) {
}

protected Document getAsJSoup(String url) throws Exception {
MockHttpServletResponse response = getAsServletResponse(url);
assertEquals(200, response.getStatus());
MockHttpServletResponse response = getAsMockHttpServletResponse(url, 200);
assertEquals("text/html", response.getContentType());

LOGGER.log(Level.INFO, "Last request returned\n:" + response.getContentAsString());
Expand Down

0 comments on commit 2355d41

Please sign in to comment.