Skip to content

Commit

Permalink
Pagination (#36)
Browse files Browse the repository at this point in the history
Add support for (optional) pagination in the http api

Combined with sorting this allows clients to only get the data they want and save bandwidth and processing time.

Fixes gardenlinux/glvd#98
  • Loading branch information
fwilhe authored Sep 12, 2024
1 parent cba4d99 commit 8407caa
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 42 deletions.
4 changes: 3 additions & 1 deletion api-examples/Get CVEs by Gardenlinux Version.bru
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ meta {
}

get {
url: {{schema_hostname_port}}/v1/cves/1592.0?sortBy=cveId
url: {{schema_hostname_port}}/v1/cves/1592.0?sortBy=cveId&pageNumber=1&pageSize=3
body: none
auth: none
}

params:query {
sortBy: cveId
pageNumber: 1
pageSize: 3
}
41 changes: 29 additions & 12 deletions src/main/java/io/gardenlinux/glvd/GlvdController.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,53 +24,70 @@ public GlvdController(@Nonnull GlvdService glvdService) {
ResponseEntity<List<SourcePackageCve>> getCveDistro(
@PathVariable final String gardenlinuxVersion,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
return ResponseEntity.ok().body(glvdService.getCveForDistribution(gardenlinuxVersion, sortBy, sortOrder));
return ResponseEntity.ok().body(glvdService.getCveForDistribution(gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)));
}

@GetMapping("/cves/{gardenlinuxVersion}/packages/{packageList}")
ResponseEntity<List<SourcePackageCve>> getCvePackages(
@PathVariable final String gardenlinuxVersion, @PathVariable final String packageList) {
var cveForPackages = glvdService.getCveForPackages(gardenlinuxVersion, packageList);
@PathVariable final String gardenlinuxVersion,
@PathVariable final String packageList,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
var cveForPackages = glvdService.getCveForPackages(gardenlinuxVersion, packageList, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize));
return ResponseEntity.ok().body(cveForPackages);
}

@GetMapping("/packages/{sourcePackage}")
ResponseEntity<List<SourcePackageCve>> packageWithVulnerabilities(
@PathVariable final String sourcePackage,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
return ResponseEntity.ok(glvdService.getPackageWithVulnerabilities(sourcePackage, sortBy, sortOrder));
return ResponseEntity.ok(glvdService.getPackageWithVulnerabilities(sourcePackage, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)));
}

@GetMapping("/packages/{sourcePackage}/{sourcePackageVersion}")
ResponseEntity<List<SourcePackageCve>> packageWithVulnerabilitiesByVersion(
@PathVariable final String sourcePackage,
@PathVariable final String sourcePackageVersion,
@RequestParam(defaultValue = "cveId") final String sortBy
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
return ResponseEntity.ok(glvdService.getPackageWithVulnerabilitiesByVersion(sourcePackage, sourcePackageVersion, sortBy));
return ResponseEntity.ok(glvdService.getPackageWithVulnerabilitiesByVersion(sourcePackage, sourcePackageVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)));
}

@GetMapping("/distro/{gardenlinuxVersion}")
ResponseEntity<List<SourcePackage>> packagesForDistro(
@PathVariable final String gardenlinuxVersion,
@RequestParam(defaultValue = "sourcePackageName") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
return ResponseEntity.ok(glvdService.getPackagesForDistro(gardenlinuxVersion, sortBy, sortOrder));
return ResponseEntity.ok(glvdService.getPackagesForDistro(gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)));
}

@GetMapping("/distro/{gardenlinuxVersion}/{cveId}")
ResponseEntity<List<SourcePackageCve>> packagesByVulnerability(
@PathVariable final String gardenlinuxVersion,
@PathVariable final String cveId,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize
) {
return ResponseEntity.ok(glvdService.getPackagesByVulnerability(gardenlinuxVersion, cveId, sortBy, sortOrder));
return ResponseEntity.ok(glvdService.getPackagesByVulnerability(gardenlinuxVersion, cveId, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)));
}

}
68 changes: 55 additions & 13 deletions src/main/java/io/gardenlinux/glvd/GlvdService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package io.gardenlinux.glvd;

import io.gardenlinux.glvd.db.*;
import io.gardenlinux.glvd.db.SourcePackage;
import io.gardenlinux.glvd.db.SourcePackageCve;
import io.gardenlinux.glvd.db.SourcePackageCveRepository;
import io.gardenlinux.glvd.db.SourcePackageRepository;
import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

Expand All @@ -16,33 +24,67 @@ public class GlvdService {
@Nonnull
private final SourcePackageRepository sourcePackageRepository;

Logger logger = LoggerFactory.getLogger(GlvdService.class);

public GlvdService(@Nonnull SourcePackageCveRepository sourcePackageCveRepository, @Nonnull SourcePackageRepository sourcePackageRepository) {
this.sourcePackageCveRepository = sourcePackageCveRepository;
this.sourcePackageRepository = sourcePackageRepository;
}

public List<SourcePackageCve> getCveForDistribution(String gardenlinuxVersion, String sortBy, String sortOrder) {
return sourcePackageCveRepository.findByGardenlinuxVersion(gardenlinuxVersion, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
private Pageable determinePageAndSortFeatures(SortAndPageOptions sortAndPageOptions) {
var sort = Sort.by(Sort.Direction.valueOf(sortAndPageOptions.sortOrder()), sortAndPageOptions.sortBy());
if (!StringUtils.isEmpty(sortAndPageOptions.pageNumber()) && !StringUtils.isEmpty(sortAndPageOptions.pageSize())) {
try {
var num = Integer.parseInt(sortAndPageOptions.pageNumber());
var size = Integer.parseInt(sortAndPageOptions.pageSize());
return PageRequest.of(num, size, sort);
} catch (NumberFormatException e) {
// fall through, don't page
logger.warn("Could not parse paging parameters", e);
}
}

return Pageable.unpaged(sort);
}

// native query does not support sorting
private Pageable determinePageAndSortFeatures2(SortAndPageOptions sortAndPageOptions) {
if (!StringUtils.isEmpty(sortAndPageOptions.pageNumber()) && !StringUtils.isEmpty(sortAndPageOptions.pageSize())) {
try {
var num = Integer.parseInt(sortAndPageOptions.pageNumber());
var size = Integer.parseInt(sortAndPageOptions.pageSize());
return PageRequest.of(num, size);
} catch (NumberFormatException e) {
// fall through, don't page
logger.warn("Could not parse paging parameters", e);
}
}

return Pageable.unpaged();
}

public List<SourcePackageCve> getCveForDistribution(String gardenlinuxVersion, SortAndPageOptions sortAndPageOptions) {
return sourcePackageCveRepository.findByGardenlinuxVersion(gardenlinuxVersion, determinePageAndSortFeatures(sortAndPageOptions));
}

public List<SourcePackageCve> getCveForPackages(String gardenlinuxVersion, String packages) {
return sourcePackageCveRepository.findBySourcePackageNameInAndGardenlinuxVersion("{"+packages+"}", gardenlinuxVersion);
public List<SourcePackageCve> getCveForPackages(String gardenlinuxVersion, String packages, SortAndPageOptions sortAndPageOptions) {
return sourcePackageCveRepository.findBySourcePackageNameInAndGardenlinuxVersion("{" + packages + "}", gardenlinuxVersion, determinePageAndSortFeatures2(sortAndPageOptions));
}

public List<SourcePackage> getPackagesForDistro(String gardenlinuxVersion, String sortBy, String sortOrder) {
return sourcePackageRepository.findByGardenlinuxVersion(gardenlinuxVersion, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
public List<SourcePackage> getPackagesForDistro(String gardenlinuxVersion, SortAndPageOptions sortAndPageOptions) {
return sourcePackageRepository.findByGardenlinuxVersion(gardenlinuxVersion, determinePageAndSortFeatures(sortAndPageOptions));
}

public List<SourcePackageCve> getPackageWithVulnerabilities(String sourcePackage, String sortBy, String sortOrder) {
return sourcePackageCveRepository.findBySourcePackageName(sourcePackage, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
public List<SourcePackageCve> getPackageWithVulnerabilities(String sourcePackage, SortAndPageOptions sortAndPageOptions) {
return sourcePackageCveRepository.findBySourcePackageName(sourcePackage, determinePageAndSortFeatures(sortAndPageOptions));
}

public List<SourcePackageCve> getPackageWithVulnerabilitiesByVersion(String sourcePackage, String sourcePackageVersion, String sortBy) {
return sourcePackageCveRepository.findBySourcePackageNameAndSourcePackageVersion(sourcePackage, sourcePackageVersion, Sort.by(Sort.Direction.DESC, sortBy));
public List<SourcePackageCve> getPackageWithVulnerabilitiesByVersion(String sourcePackage, String sourcePackageVersion, SortAndPageOptions sortAndPageOptions) {
return sourcePackageCveRepository.findBySourcePackageNameAndSourcePackageVersion(sourcePackage, sourcePackageVersion, determinePageAndSortFeatures(sortAndPageOptions));
}

public List<SourcePackageCve> getPackagesByVulnerability(String gardenlinuxVersion, String cveId, String sortBy, String sortOrder) {
return sourcePackageCveRepository.findByCveIdAndGardenlinuxVersion(cveId, gardenlinuxVersion, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
public List<SourcePackageCve> getPackagesByVulnerability(String gardenlinuxVersion, String cveId, SortAndPageOptions sortAndPageOptions) {
return sourcePackageCveRepository.findByCveIdAndGardenlinuxVersion(cveId, gardenlinuxVersion, determinePageAndSortFeatures(sortAndPageOptions));
}

}
4 changes: 4 additions & 0 deletions src/main/java/io/gardenlinux/glvd/SortAndPageOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.gardenlinux.glvd;

public record SortAndPageOptions(String sortBy, String sortOrder, String pageNumber, String pageSize) {
}
18 changes: 14 additions & 4 deletions src/main/java/io/gardenlinux/glvd/UiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ public String getPackagesForDistro(
@RequestParam(name = "gardenlinuxVersion", required = true) String gardenlinuxVersion,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize,
Model model) {
var packages = glvdService.getPackagesForDistro(gardenlinuxVersion, sortBy, sortOrder);
var packages = glvdService.getPackagesForDistro(gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize));
model.addAttribute("packages", packages);
model.addAttribute("gardenlinuxVersion", gardenlinuxVersion);
return "getPackagesForDistro";
Expand All @@ -33,9 +35,11 @@ public String getCveForDistribution(
@RequestParam(name = "gardenlinuxVersion", required = true) String gardenlinuxVersion,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize,
Model model
) {
var sourcePackageCves = glvdService.getCveForDistribution(gardenlinuxVersion, sortBy, sortOrder);
var sourcePackageCves = glvdService.getCveForDistribution(gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize));
model.addAttribute("sourcePackageCves", sourcePackageCves);
model.addAttribute("gardenlinuxVersion", gardenlinuxVersion);
return "getCveForDistribution";
Expand All @@ -45,9 +49,13 @@ public String getCveForDistribution(
public String getCveForPackages(
@RequestParam(name = "gardenlinuxVersion", required = true) String gardenlinuxVersion,
@RequestParam(name = "packages", required = true) String packages,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize,
Model model
) {
var sourcePackageCves = glvdService.getCveForPackages(gardenlinuxVersion, packages);
var sourcePackageCves = glvdService.getCveForPackages(gardenlinuxVersion, packages, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize));
model.addAttribute("sourcePackageCves", sourcePackageCves);
model.addAttribute("gardenlinuxVersion", gardenlinuxVersion);
model.addAttribute("packages", packages);
Expand All @@ -60,9 +68,11 @@ public String getPackagesByVulnerability(
@RequestParam(name = "cveId", required = true) String cveId,
@RequestParam(defaultValue = "cveId") final String sortBy,
@RequestParam(defaultValue = "ASC") final String sortOrder,
@RequestParam(required = false) final String pageNumber,
@RequestParam(required = false) final String pageSize,
Model model
) {
var sourcePackageCves = glvdService.getPackagesByVulnerability(gardenlinuxVersion, cveId, sortBy, sortOrder);
var sourcePackageCves = glvdService.getPackagesByVulnerability(gardenlinuxVersion, cveId, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize));
model.addAttribute("sourcePackageCves", sourcePackageCves);
model.addAttribute("gardenlinuxVersion", gardenlinuxVersion);
model.addAttribute("cveId", cveId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@

public interface SourcePackageCveRepository extends JpaRepository<SourcePackageCve, String> {

List<SourcePackageCve> findBySourcePackageName(@Param("source_package_name") String source_package_name, Sort sort);
List<SourcePackageCve> findBySourcePackageNameAndSourcePackageVersion(@Param("source_package_name") String source_package_name, @Param("source_package_version") String source_package_version, Sort sort);
List<SourcePackageCve> findByCveIdAndGardenlinuxVersion(@Param("cve_id") String cve_id, @Param("gardenlinux_version") String gardenlinux_version, Sort sort);
List<SourcePackageCve> findBySourcePackageName(@Param("source_package_name") String source_package_name, Pageable pageable);
List<SourcePackageCve> findBySourcePackageNameAndSourcePackageVersion(@Param("source_package_name") String source_package_name, @Param("source_package_version") String source_package_version, Pageable pageable);
List<SourcePackageCve> findByCveIdAndGardenlinuxVersion(@Param("cve_id") String cve_id, @Param("gardenlinux_version") String gardenlinux_version, Pageable pageable);

List<SourcePackageCve> findByGardenlinuxVersion(@Param("gardenlinux_version") String gardenlinux_version, Sort sort);
List<SourcePackageCve> findByGardenlinuxVersion(@Param("gardenlinux_version") String gardenlinux_version, Pageable pageable);

// would be nice if we did not need a native query here
// is this (the in-array search for packages) possible in any other way with spring data jpa?
// fixme: does not support sorting, cf https://github.com/spring-projects/spring-data-jpa/issues/2504#issuecomment-1527743003
// pagination seems to work ok
@Query(value = """
SELECT * FROM sourcepackagecve
WHERE source_package_name = ANY(:source_package_names ::TEXT[]) AND gardenlinux_version = :gardenlinux_version
""", nativeQuery = true)
List<SourcePackageCve> findBySourcePackageNameInAndGardenlinuxVersion(@Param("source_package_names") String source_package_names, @Param("gardenlinux_version") String gardenlinux_version);
List<SourcePackageCve> findBySourcePackageNameInAndGardenlinuxVersion(@Param("source_package_names") String source_package_names, @Param("gardenlinux_version") String gardenlinux_version, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.gardenlinux.glvd.db;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface SourcePackageRepository extends JpaRepository<SourcePackage, String> {
List<SourcePackage> findByGardenlinuxVersion(@Param("gardenlinux_version") String gardenlinux_version, Sort by);
List<SourcePackage> findByGardenlinuxVersion(@Param("gardenlinux_version") String gardenlinux_version, Pageable pageable);
}
Loading

0 comments on commit 8407caa

Please sign in to comment.