Skip to content

Commit

Permalink
Adding endpoints to list download statistics and export them as CSV a…
Browse files Browse the repository at this point in the history
…nd TSV files
  • Loading branch information
fmendezh committed May 20, 2021
1 parent d1c610a commit 435bc66
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 0 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<woodstox.version>4.4.1</woodstox.version>
<zookeeper.version>3.4.14</zookeeper.version>
<owasp-java-html-sanitizer.version>20200713.1</owasp-java-html-sanitizer.version>
<super-csv.version>2.4.0</super-csv.version>

<!-- Test -->
<junit.version>5.6.3</junit.version>
Expand Down Expand Up @@ -583,6 +584,13 @@
<version>${elasticsearch.version}</version>
</dependency>

<!-- CSV exports -->
<dependency>
<groupId>net.sf.supercsv</groupId>
<artifactId>super-csv</artifactId>
<version>${super-csv.version}</version>
</dependency>

<!-- Caching -->
<dependency>
<groupId>org.cache2k</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
import java.net.URI;
import java.util.UUID;

import org.apache.ibatis.type.LocalDateTimeTypeHandler;
import org.apache.ibatis.type.LocalDateTypeHandler;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -221,6 +223,7 @@ ConfigurationCustomizer mybatisConfigCustomizer() {
configuration
.getTypeAliasRegistry()
.registerAlias("IDigBioCollectionDto", IDigBioCollectionDto.class);

};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.common.search.Facet;
import org.gbif.api.model.occurrence.Download;
import org.gbif.api.model.occurrence.DownloadStatistics;

import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -81,4 +82,19 @@ List<Facet.Count> getDownloadsByDataset(
@Nullable @Param("publishingCountry") String publishingCountry,
@Nullable @Param("datasetKey") UUID datasetKey,
@Nullable @Param("publishingOrgKey") UUID publishingOrgKey);

List<DownloadStatistics> getDownloadStatistics(
@Nullable @Param("fromDate") Date fromDate,
@Nullable @Param("toDate") Date toDate,
@Nullable @Param("publishingCountry") String publishingCountry,
@Nullable @Param("datasetKey") UUID datasetKey,
@Nullable @Param("publishingOrgKey") UUID publishingOrgKey,
@Nullable @Param("page") Pageable page);

long countDownloadStatistics(
@Nullable @Param("fromDate") Date fromDate,
@Nullable @Param("toDate") Date toDate,
@Nullable @Param("publishingCountry") String publishingCountry,
@Nullable @Param("datasetKey") UUID datasetKey,
@Nullable @Param("publishingOrgKey") UUID publishingOrgKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
<result property="count" column="total_records" />
</resultMap>

<resultMap id="DOWNLOAD_STATISTICS" type="org.gbif.api.model.occurrence.DownloadStatistics" autoMapping="true"/>

<sql id="OCCURRENCE_DOWNLOAD_FIELDS">
key,doi,license,filter,status,download_link,size,total_records,notification_addresses,created_by,send_notification,format,created,modified,erase_after
</sql>
Expand Down Expand Up @@ -259,4 +261,37 @@
GROUP BY year_month
ORDER BY year_month DESC;
</select>

<select id="getDownloadStatistics" resultMap="DOWNLOAD_STATISTICS" parameterType="map">
SELECT year_month, dataset_key, SUM(number_downloads) AS number_downloads, SUM(total_records) AS total_records
FROM download_statistics
<if test="publishingOrgKey != null">
JOIN dataset d ON d.key = dataset_key AND d.publishing_organization_key = #{publishingOrgKey,jdbcType=OTHER}
</if>
<where>
<if test="publishingCountry != null">AND publishing_organization_country = #{publishingCountry,jdbcType=OTHER}</if>
<if test="datasetKey != null">AND dataset_key = #{datasetKey,jdbcType=OTHER}</if>
<if test="fromDate != null"><![CDATA[AND year_month >= #{fromDate,jdbcType=TIMESTAMP}]]></if>
<if test="toDate != null"><![CDATA[AND year_month < #{toDate,jdbcType=TIMESTAMP}]]></if>
</where>
GROUP BY dataset_key, year_month
ORDER BY year_month DESC
<if test="page != null" >
LIMIT #{page.limit} OFFSET #{page.offset}
</if>
</select>

<select id="countDownloadStatistics" resultType="Long" parameterType="map">
SELECT COUNT(*)
FROM download_statistics
<if test="publishingOrgKey != null">
JOIN dataset d ON d.key = dataset_key AND d.publishing_organization_key = #{publishingOrgKey,jdbcType=OTHER}
</if>
<where>
<if test="publishingCountry != null">AND publishing_organization_country = #{publishingCountry,jdbcType=OTHER}</if>
<if test="datasetKey != null">AND dataset_key = #{datasetKey,jdbcType=OTHER}</if>
<if test="fromDate != null"><![CDATA[AND year_month >= #{fromDate,jdbcType=TIMESTAMP}]]></if>
<if test="toDate != null"><![CDATA[AND year_month < #{toDate,jdbcType=TIMESTAMP}]]></if>
</where>
</select>
</mapper>
12 changes: 12 additions & 0 deletions registry-ws/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
</properties>

<dependencies>
<!-- Tools -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- Spring dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -76,6 +82,12 @@
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>

<!-- CSV exports -->
<dependency>
<groupId>net.sf.supercsv</groupId>
<artifactId>super-csv</artifactId>
</dependency>

<!-- GBIF dependencies -->
<dependency>
<groupId>org.gbif.registry</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.gbif.registry.ws.export;

import org.gbif.api.model.common.export.ExportFormat;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.occurrence.DownloadStatistics;
import org.gbif.api.service.registry.OccurrenceDownloadService;
import org.gbif.api.vocabulary.Country;

import java.io.Writer;
import java.util.Date;
import java.util.UUID;


import lombok.Builder;
import lombok.Data;
import lombok.SneakyThrows;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.ParseInt;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.CsvContext;

@Data
@Builder
public class CsvWriter<T> {

private final String[] header;

private final String[] fields;

private final CellProcessor[] processors;

private final Iterable<T> pager;

private final ExportFormat preference;


private CsvPreference csvPreference() {
if (ExportFormat.CSV == preference) {
return CsvPreference.STANDARD_PREFERENCE;
} else if (ExportFormat.TSV == preference) {
return CsvPreference.TAB_PREFERENCE;
}
throw new IllegalArgumentException("Export format not supported " + preference);
}

@SneakyThrows
public void export(Writer writer) {
try (ICsvBeanWriter beanWriter = new CsvBeanWriter(writer, csvPreference())) {
beanWriter.writeHeader(header);
for (T o : pager) {
beanWriter.write(o, fields, processors);
}
writer.flush();
}
}

/**
* Creates and CsvWriter/exporter DownloadStatistics.
*/
public static CsvWriter<DownloadStatistics> downloadStatisticsTsvWriter(Iterable<DownloadStatistics> pager,
ExportFormat preference) {

return CsvWriter.<DownloadStatistics>builder()
.fields(new String[]{"datasetKey", "totalRecords", "numberDownloads", "year", "month"})
.header(new String[]{"dataset_key", "total_records", "number_downloads", "year", "month"})
.processors(new CellProcessor[]{new UUIDProcessor(),
new Optional(new ParseInt()),
new Optional(new ParseInt()),
new Optional(new ParseInt()),
new Optional(new ParseInt())})
.preference(preference)
.pager(pager)
.build();
}

/**
* Null aware UUID processor.
*/
private static class UUIDProcessor implements CellProcessor {
@Override
public String execute(Object value, CsvContext csvContext) {
return value != null ? ((UUID) value).toString() : "";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,29 @@
import org.gbif.api.annotation.Trim;
import org.gbif.api.model.common.DOI;
import org.gbif.api.model.common.GbifUser;
import org.gbif.api.model.common.export.ExportFormat;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.common.paging.PagingResponse;
import org.gbif.api.model.common.search.Facet;
import org.gbif.api.model.occurrence.Download;
import org.gbif.api.model.occurrence.DownloadStatistics;
import org.gbif.api.model.registry.DatasetOccurrenceDownloadUsage;
import org.gbif.api.model.registry.PostPersist;
import org.gbif.api.model.registry.PrePersist;
import org.gbif.api.service.common.IdentityAccessService;
import org.gbif.api.service.registry.OccurrenceDownloadService;
import org.gbif.api.util.iterables.Iterables;
import org.gbif.api.vocabulary.Country;
import org.gbif.api.vocabulary.License;
import org.gbif.registry.doi.DoiIssuingService;
import org.gbif.registry.doi.DownloadDoiDataCiteHandlingService;
import org.gbif.registry.persistence.mapper.DatasetOccurrenceDownloadMapper;
import org.gbif.registry.persistence.mapper.OccurrenceDownloadMapper;
import org.gbif.registry.ws.export.CsvWriter;
import org.gbif.registry.ws.provider.PartialDate;
import org.gbif.ws.WebApplicationException;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
Expand All @@ -47,6 +52,7 @@
import java.util.UUID;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;

Expand All @@ -56,8 +62,13 @@
import org.slf4j.MarkerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand Down Expand Up @@ -343,6 +354,57 @@ public Map<Integer, Map<Integer, Long>> getDownloadsByDataset(
publishingOrgKey));
}

@GetMapping("statistics")
@Override
public PagingResponse<DownloadStatistics> getDownloadStatistics(
@PartialDate Date fromDate,
@PartialDate Date toDate,
Country publishingCountry,
@RequestParam(value = "datasetKey", required = false) UUID datasetKey,
@RequestParam(value = "publishingOrgKey", required = false) UUID publishingOrgKey,
Pageable page
) {
String country = Optional.ofNullable(publishingCountry).map(Country::getIso2LetterCode).orElse(null);
return new PagingResponse<>(page,
occurrenceDownloadMapper.countDownloadStatistics(fromDate,
toDate,
country,
datasetKey,
publishingOrgKey),
occurrenceDownloadMapper.getDownloadStatistics(fromDate,
toDate,
country,
datasetKey,
publishingOrgKey,
page));
}

@GetMapping("statistics/export")
public void getDownloadStatistics(
HttpServletResponse response,
@RequestParam(value = "format", defaultValue = "TSV") ExportFormat format,
@PartialDate Date fromDate,
@PartialDate Date toDate,
Country publishingCountry,
@RequestParam(value = "datasetKey", required = false) UUID datasetKey,
@RequestParam(value = "publishingOrgKey", required = false) UUID publishingOrgKey) throws
IOException {

String headerValue = String.format("attachment; filename=\"download_statistics.%s\"",
format.name().toLowerCase());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, headerValue);


CsvWriter.downloadStatisticsTsvWriter(Iterables.downloadStatistics(this,
fromDate,
toDate,
publishingCountry,
datasetKey,
publishingOrgKey),
format)
.export(response.getWriter());
}

/** Aggregates the download statistics in tree structure of month grouped by year. */
private Map<Integer, Map<Integer, Long>> groupByYear(List<Facet.Count> counts) {
Map<Integer, Map<Integer, Long>> yearsGrouping = new TreeMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.gbif.registry.ws.export;

import org.gbif.api.model.common.export.ExportFormat;
import org.gbif.api.model.occurrence.DownloadStatistics;

import java.io.StringWriter;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CsvWriterTest {

@Test
public void downloadStatisticsTest() {

List<DownloadStatistics> stats = Arrays.asList(
new DownloadStatistics(UUID.randomUUID(), 10, 10, LocalDate.of(2020,1,1)),
new DownloadStatistics(UUID.randomUUID(), 10, 10, LocalDate.of(2021, 2,1)));

StringWriter writer = new StringWriter();

CsvWriter.downloadStatisticsTsvWriter(stats, ExportFormat.TSV)
.export(writer);

String export = writer.toString();
String[] lines = export.split("\\n");

//Number of lines is header + list.size
assertEquals(stats.size() + 1, lines.length);

//Each line has 4 tabs
assertEquals((stats.size() + 1) * 4, export.chars().filter(ch -> ch == '\t').count());

//Year test
assertEquals("2020", lines[1].split("\\t")[3]);
assertEquals("2021", lines[2].split("\\t")[3]);

//Month test
assertEquals("1", lines[1].split("\\t")[4]);
assertEquals("2", lines[2].split("\\t")[4]);
}
}

0 comments on commit 435bc66

Please sign in to comment.