Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/paging #390

Merged
merged 36 commits into from
Jun 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1b1504b
Added basic Paging Functionality using supplied Query Parameters 'lim…
SpeckiJ May 25, 2017
d001bda
fixed License Header.
SpeckiJ May 25, 2017
b9d5a9e
fixed whitespaces to comply with mvn tests.
SpeckiJ May 26, 2017
fb9f546
Refactored Paging from `ParameterController` to `ParameterRequestMapp…
SpeckiJ May 26, 2017
5768313
Merge remote-tracking branch 'upstream/develop' into feature/paging
SpeckiJ May 26, 2017
7510010
Fixed NullPointerException on using limit and offset filters.
SpeckiJ May 26, 2017
5e0c37b
fixed page calculations.
SpeckiJ May 29, 2017
5fa1404
removed limit and offset to prevent `ORDER BY` in SQL `count(*)` whic…
SpeckiJ May 29, 2017
89972ba
removed unused import.
SpeckiJ May 29, 2017
19e0fe4
refactored to use EntityCounterWrapper to remove dependency on dao im…
SpeckiJ Jun 1, 2017
60915b7
mvn checkstyle compliance
SpeckiJ Jun 2, 2017
fadb025
Refactored counter to exclude specific DAO Implementations
SpeckiJ Jun 2, 2017
c661acb
mvn checkstyle compliance once again.
SpeckiJ Jun 2, 2017
ce9233b
fixed incorrect count on caused by backwards compatibility.
SpeckiJ Jun 2, 2017
e878b9d
Added Paging for /stations Endpoint.
SpeckiJ Jun 2, 2017
5dc31da
Added basic documentation about paging.
SpeckiJ Jun 9, 2017
4f098d4
fixed self-referencing dependency.
SpeckiJ Jun 9, 2017
8659143
Merge branch 'fix/error-on-missing-acceptlanguage-header' into featur…
ridoo Jun 12, 2017
14673a4
Merge remote-tracking branch 'upstream/develop' into feature/paging
SpeckiJ Jun 13, 2017
db944fe
Merge branch 'feature/paging' of https://github.com/speckij/series-re…
ridoo Jun 14, 2017
4eaf904
Adjusted Page calculations to offset parameter indicating Page Number…
SpeckiJ Jun 15, 2017
1658c39
refactored Backwards Compatibility to individual controllers.
SpeckiJ Jun 16, 2017
c26a852
Increased MIN and MAX Vales for Paging.
SpeckiJ Jun 16, 2017
280e7bf
updated to reflect changes.
SpeckiJ Jun 16, 2017
9be8cfd
Merge branch 'develop' into feature/paging
SpeckiJ Jun 16, 2017
6df1c22
mvn checkstyle compliance.
SpeckiJ Jun 16, 2017
f87d967
Merge branch 'feature/paging' of https://github.com/SpeckiJ/series-re…
SpeckiJ Jun 16, 2017
adbbab5
Merge branch 'feature/paging' of https://github.com/speckij/series-re…
ridoo Jun 16, 2017
33cadb7
Merge branch 'develop' into feature/paging
ridoo Jun 16, 2017
5b9cc17
do not include docs when -Dno-docs is specified
ridoo Jun 16, 2017
5a4db02
minor refactoring
ridoo Jun 16, 2017
d5bb1df
update changelog
ridoo Jun 16, 2017
83f33e0
improved error handling.
SpeckiJ Jun 16, 2017
3a9d76a
apply checkstyle rule
ridoo Jun 16, 2017
6d5eae2
add javadoc to specify backend behaviour
ridoo Jun 16, 2017
b211cec
Merge remote-tracking branch 'specki/feature/paging' into feature/paging
ridoo Jun 16, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- output includes href property

### Features
- #129 provides paging headers if backend supports counting
- #232 possibility to use `domainId` instead of database id
- #266 `AbstractValue` can have time intervals
- #304 flexible prerendering title config via placeholders
Expand Down
3 changes: 3 additions & 0 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
<p class="header">
<a href="{{ site.baseurl }}/configuration.html">Configuration</a>
</p>
<p class="header">
<a href="{{ site.baseurl }}/paging.html">Paging</a>
</p>
<p class="header">
<a href="{{ site.baseurl }}/extensions.html">Extensions</a>
</p>
Expand Down
64 changes: 64 additions & 0 deletions docs/paging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
layout: page
title: Paging
permalink: /paging
---



## Overview

{:.n52-callout .n52-callout-info}
Paging is currently not supported for the Endpoint `/geometries`. All other Endpoints are supported.

The API offers basic Paging support using the Query Parameters `offset` and `limit`. Paging is automatically enabled if at least one of the parameters is present in the query. If only one parameter is provided, the missing parameters default value will be used.

### Parameter Semantics

`limit` describes the maximum amount of Elements in one Page. Except for the last Page, all Pages will always be filled to this maximum capacity.

`offset` describes the Page Number, starting from zero.

{:.n52-callout .n52-callout-info}
It is **strongly advised** to only specify the `limit` Parameter in the client, and get the `offset` from the Response Headers returned by the API (see examples below).

### Parameter Values
The Parameter Values are subject to the following restrictions:

{:.table}
parameter | MIN_VALUE | MAX_VALUE | default |
--------------|------------|---------------------------|
`limit` | 1 | 1000000 | 10000 |
`offset` | 0 | 2147483647 | 0 |

{:.n52-callout .n52-callout-info}
Both `limit` and `offset` are integers and are programmatically limited to not exceed the value of 2147483647. If higher Numbers are provided the Request will fail and the API will throw an Error.

If an invalid `limit` (i.e. exceeding MAX_VALUE) is supplied, `limit` will default to the closest valid value (e.g. MIN_VALUE or MAX_VALUE).
If no `limit` is supplied the default value will be used.

If no offset or an invalid `offset` is supplied, `offset` will default to its default value.

### Response Headers
Paging Information is returned by the API in the Response Header. An Example of the returned Headers can be found below:

**Example Request URL**
```
http://example.com/api/stations?offset=2&limit=10
```

**Example Response Header [partial]:**
```
[...]
Link : <http://example.com/api/stations?offset=2&limit=10> rel="self"
Link : <http://example.com/api/stations?offset=3&limit=10> rel="next"
Link : <http://example.com/api/stations?offset=1&limit=10> rel="previous"
Link : <http://example.com/api/stations?offset=0&limit=10> rel="first"
Link : <http://example.com/api/stations?offset=7&limit=10> rel="last"
[...]
```
The Presence of the Links in the Response Header varies on the specific circumstances:

- The Link to `self` is always present.
- The Links to `first` and `last` are present if a valid offset was provided.
- The Links to `next` and `previous` are present if the pages exist and a valid offset was provided.
1 change: 0 additions & 1 deletion rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<groupId>${project.groupId}</groupId>
<artifactId>spi</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
package org.n52.web.ctrl;


import org.n52.io.request.IoParameters;
import org.n52.io.response.CategoryOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -37,4 +38,14 @@
@RequestMapping(UrlSettings.COLLECTION_CATEGORIES)
public class CategoriesParameterController extends ParameterRequestMappingAdapter<CategoryOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_CATEGORIES);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getCategoryCount(IoParameters.ensureBackwardsCompatibility(queryMap));
}
}
12 changes: 12 additions & 0 deletions rest/src/main/java/org/n52/web/ctrl/DatasetController.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
package org.n52.web.ctrl;

import org.n52.io.request.IoParameters;
import org.n52.io.response.dataset.DatasetOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
Expand All @@ -37,4 +38,15 @@
@RequestMapping(path = UrlSettings.COLLECTION_DATASETS, method = RequestMethod.GET)
public class DatasetController extends ParameterRequestMappingAdapter<DatasetOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_DATASETS);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getDatasetCount(queryMap);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
package org.n52.web.ctrl;


import org.n52.io.request.IoParameters;
import org.n52.io.response.FeatureOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -37,4 +38,14 @@
@RequestMapping(path = UrlSettings.COLLECTION_FEATURES)
public class FeaturesParameterController extends ParameterRequestMappingAdapter<FeatureOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_FEATURES);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getFeatureCount(IoParameters.ensureBackwardsCompatibility(queryMap));
}
}
13 changes: 13 additions & 0 deletions rest/src/main/java/org/n52/web/ctrl/GeometriesController.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
package org.n52.web.ctrl;

import org.n52.io.request.IoParameters;
import org.n52.io.response.GeometryInfo;
import org.n52.series.spi.geo.TransformingGeometryOutputService;
import org.n52.series.spi.srv.ParameterService;
Expand All @@ -43,4 +44,16 @@ public void setParameterService(ParameterService<GeometryInfo> parameterService)
super.setParameterService(new TransformingGeometryOutputService(parameterService));
}

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_GEOMETRIES);
}

@Override
protected int getElementCount(IoParameters queryMap) {
//TODO(specki): Implementation of getCount for Geometries in EntityCounter
return -1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
package org.n52.web.ctrl;

import org.n52.io.request.IoParameters;
import org.n52.io.response.OfferingOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -36,4 +37,14 @@
@RequestMapping(path = UrlSettings.COLLECTION_OFFERINGS)
public class OfferingsParameterController extends ParameterRequestMappingAdapter<OfferingOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_OFFERINGS);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getOfferingCount(IoParameters.ensureBackwardsCompatibility(queryMap));
}
}
12 changes: 7 additions & 5 deletions rest/src/main/java/org/n52/web/ctrl/ParameterController.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,22 @@ private Collection<String> checkForOverridingData(Map<String, Object> data, Map<
}

@Override
public ModelAndView getCollection(String locale, MultiValueMap<String, String> query) {
public ModelAndView getCollection(HttpServletResponse response,
String locale,
MultiValueMap<String, String> query) {
RequestUtils.overrideQueryLocaleWhenSet(locale, query);
IoParameters queryMap = QueryParameters.createFromQuery(query);
LOGGER.debug("getCollection() with query '{}'", queryMap);
OutputCollection<T> result;

if (queryMap.isExpanded()) {
Stopwatch stopwatch = Stopwatch.startStopwatch();
OutputCollection<T> result = addExtensionInfos(parameterService.getExpandedParameters(queryMap));
result = addExtensionInfos(parameterService.getExpandedParameters(queryMap));
LOGGER.debug("Processing request took {} seconds.", stopwatch.stopInSeconds());
return createModelAndView(result);
} else {
OutputCollection<T> results = parameterService.getCondensedParameters(queryMap);
return createModelAndView(results);
result = parameterService.getCondensedParameters(queryMap);
}
return createModelAndView(result);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

import javax.servlet.http.HttpServletResponse;

import org.n52.io.request.IoParameters;

import org.n52.io.request.Parameters;
import org.n52.io.request.QueryParameters;
import org.n52.io.response.ParameterOutput;
import org.n52.io.response.pagination.OffsetBasedPagination;
import org.n52.io.response.pagination.Paginated;
import org.n52.io.response.pagination.Pagination;
import org.n52.series.spi.srv.CountingMetadataService;
import org.n52.series.spi.srv.RawFormats;
import org.n52.web.common.RequestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
Expand All @@ -51,12 +61,27 @@
})
public abstract class ParameterRequestMappingAdapter<T extends ParameterOutput> extends ParameterController<T> {

@Autowired
@Qualifier("metadataService")
private CountingMetadataService counter;

@Override
@RequestMapping(path = "")
public ModelAndView getCollection(@RequestHeader(value = Parameters.HttpHeader.ACCEPT_LANGUAGE,
required = false) String locale,
public ModelAndView getCollection(HttpServletResponse response,
@RequestHeader(value = Parameters.HttpHeader.ACCEPT_LANGUAGE,
required = false) String locale,
@RequestParam MultiValueMap<String, String> query) {
return super.getCollection(locale, addHrefBase(query));
IoParameters queryMap = QueryParameters.createFromQuery(query);
if (queryMap.containsParameter(Parameters.LIMIT) || queryMap.containsParameter(Parameters.OFFSET)) {
Integer elementcount = this.getElementCount(queryMap.removeAllOf(Parameters.LIMIT)
.removeAllOf(Parameters.OFFSET));
if (0 >= elementcount) {
OffsetBasedPagination obp = new OffsetBasedPagination(queryMap.getOffset(), queryMap.getLimit());
Paginated<T> paginated = new Paginated<>(obp, elementcount.longValue());
this.addPagingHeaders(this.getCollectionPath(this.getHrefBase()), response, paginated);
}
}
return super.getCollection(null, locale, addHrefBase(query));
}

@Override
Expand Down Expand Up @@ -92,9 +117,46 @@ public Map<String, Object> getExtras(@PathVariable("item") String resourceId,
}

protected MultiValueMap<String, String> addHrefBase(MultiValueMap<String, String> query) {
String externalUrl = getExternalUrl();
String hrefBase = RequestUtils.resolveQueryLessRequestUrl(externalUrl);
query.put(Parameters.HREF_BASE, Collections.singletonList(hrefBase));
query.put(Parameters.HREF_BASE, Collections.singletonList(getHrefBase()));
return query;
}

private String getHrefBase() {
return RequestUtils.resolveQueryLessRequestUrl(getExternalUrl());
}

/**
* @param queryMap
* the query map
* @return the number of elements available, or negative number if paging is not supported.
*/
protected abstract int getElementCount(IoParameters queryMap);

protected CountingMetadataService getEntityCounter() {
return counter;
}

private HttpServletResponse addPagingHeaders(String href, HttpServletResponse response, Paginated<T> paginated) {
addLinkHeader("self", href, paginated.getCurrent(), response);
addLinkHeader("previous", href, paginated.getPrevious(), response);
addLinkHeader("next", href, paginated.getNext(), response);
addLinkHeader("first", href, paginated.getFirst(), response);
addLinkHeader("last", href, paginated.getLast(), response);
return response;
}

private void addLinkHeader(String rel, String href, Optional<Pagination> pagination, HttpServletResponse response) {
if (pagination.isPresent()) {
String header = "Link";
String value = "<"
+ href
+ "?"
+ pagination.get()
.toString()
+ "> rel=\""
+ rel
+ "\"";
response.addHeader(header, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
package org.n52.web.ctrl;


import org.n52.io.request.IoParameters;
import org.n52.io.response.PhenomenonOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -37,4 +38,14 @@
@RequestMapping(path = UrlSettings.COLLECTION_PHENOMENA)
public class PhenomenaParameterController extends ParameterRequestMappingAdapter<PhenomenonOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_PHENOMENA);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getPhenomenaCount(IoParameters.ensureBackwardsCompatibility(queryMap));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
package org.n52.web.ctrl;

import org.n52.io.request.IoParameters;
import org.n52.io.response.PlatformOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -36,4 +37,14 @@
@RequestMapping(path = UrlSettings.COLLECTION_PLATFORMS)
public class PlatformsParameterController extends ParameterRequestMappingAdapter<PlatformOutput> {

@Override
public String getCollectionPath(String hrefBase) {
UrlHelper urlhelper = new UrlHelper();
return urlhelper.constructHref(hrefBase, UrlSettings.COLLECTION_PLATFORMS);
}

@Override
protected int getElementCount(IoParameters queryMap) {
return super.getEntityCounter().getPlatformCount(queryMap);
}
}
Loading