Skip to content

Commit

Permalink
feat: #3 integrate with CKAN
Browse files Browse the repository at this point in the history
  • Loading branch information
brunopacheco1 committed Apr 5, 2024
1 parent 02f043b commit 25ff86c
Show file tree
Hide file tree
Showing 16 changed files with 2,562 additions and 24 deletions.
2 changes: 1 addition & 1 deletion _http/ckan.http
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

GET https://ckan-test.healthdata.nl/api/3/action/package_search?facet.field=["organization","theme","conforms_to","has_version","access_rights","language","publisher_name","res_format","provenance"]&rows=1&fq=id:e1b3eff9-13eb-48b0-b180-7ecb76b84454
GET https://ckan-test.healthdata.nl/api/3/action/package_search?fq=&sort=score+desc%2C+metadata_modified+desc&rows=10&start=0&facet.field=%5B%22organization%22%2C%22theme%22%2C%22conforms_to%22%2C%22has_version%22%2C%22access_rights%22%2C%22language%22%2C%22publisher_name%22%2C%22res_format%22%2C%22provenance%22%5D

###
GET https://ckan-test.healthdata.nl/api/3/action/package_show?id=e1b3eff9-13eb-48b0-b180-7ecb76b84454
12 changes: 12 additions & 0 deletions _http/discovery.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024 PNED G.I.E.
#
# SPDX-License-Identifier: Apache-2.0

POST http://localhost:8080/api/v1/datasets/search
Content-Type: application/json

{
}

###
GET http://localhost:8080/api/v1/datasets/dummy

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.github.genomicdatainfrastructure.discovery.api;

import io.github.genomicdatainfrastructure.discovery.model.DatasetSearchQuery;
import io.github.genomicdatainfrastructure.discovery.model.DatasetsSearchResponse;
import io.github.genomicdatainfrastructure.discovery.model.RetrievedDataset;
import io.github.genomicdatainfrastructure.discovery.services.DatasetsSearchService;
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class DatasetQueryApiImpl implements DatasetQueryApi {

private final SecurityIdentity identity;
private final DatasetsSearchService datasetsSearchService;

@Override
public DatasetsSearchResponse datasetSearch(DatasetSearchQuery datasetSearchQuery) {
return datasetsSearchService.search(accessToken(), datasetSearchQuery);
}

@Override
public RetrievedDataset retrieveDataset(String id) {
return RetrievedDataset.builder()
.build();
}

private String accessToken() {
if (identity.isAnonymous()) {
return null;
}
var principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
return principal.getRawToken();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0

package io.github.genomicdatainfrastructure.discovery.api;

import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;

import io.github.genomicdatainfrastructure.discovery.model.ErrorResponse;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.java.Log;
import java.util.logging.Level;

@Log
@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Exception> {

@Override
public Response toResponse(Exception exception) {
log.log(Level.SEVERE, exception, exception::getMessage);

var errorResponse = ErrorResponse.builder()
.title("Not expected exception")
.status(INTERNAL_SERVER_ERROR.getStatusCode())
.detail(exception.getMessage())
.build();

return Response
.status(INTERNAL_SERVER_ERROR)
.entity(errorResponse)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.github.genomicdatainfrastructure.discovery.services;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.github.genomicdatainfrastructure.discovery.model.DatasetSearchQuery;
import io.github.genomicdatainfrastructure.discovery.model.DatasetSearchQueryFacet;
import io.github.genomicdatainfrastructure.discovery.model.DatasetsSearchResponse;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.api.CkanQueryApi;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;

import static java.util.Optional.ofNullable;

@ApplicationScoped
public class DatasetsSearchService {

private static final String FACETS_QUERY = "[\"organization\",\"theme\",\"conforms_to\",\"has_version\",\"access_rights\",\"language\",\"publisher_name\",\"res_format\",\"provenance\"]";

private final CkanQueryApi ckanQueryApi;

@Inject
public DatasetsSearchService(
@RestClient CkanQueryApi ckanQueryApi
) {
this.ckanQueryApi = ckanQueryApi;
}

public DatasetsSearchResponse search(String accessToken, DatasetSearchQuery query) {
var response = ckanQueryApi.packageSearch(
query.getQuery(),
getFacetsQuery(query.getFacets()),
query.getSort(),
query.getRows(),
query.getStart(),
FACETS_QUERY,
accessToken
);
return PackagesSearchResponseMapper.from(response);
}

private String getFacetsQuery(List<DatasetSearchQueryFacet> facets) {
var nonNullFacets = ofNullable(facets)
.orElseGet(List::of);

return nonNullFacets.stream()
.map(facet -> String.format("facet.field=%s", facet))
.reduce((a, b) -> String.format("%s&%s", a, b))
.orElse("");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.github.genomicdatainfrastructure.discovery.services;

import io.github.genomicdatainfrastructure.discovery.model.DatasetsSearchResponse;
import io.github.genomicdatainfrastructure.discovery.model.Facet;
import io.github.genomicdatainfrastructure.discovery.model.FacetGroup;
import io.github.genomicdatainfrastructure.discovery.model.SearchedDataset;
import io.github.genomicdatainfrastructure.discovery.model.ValueLabel;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.model.CkanFacet;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.model.CkanOrganization;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.model.CkanPackage;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.model.PackagesSearchResponse;
import io.github.genomicdatainfrastructure.discovery.remote.ckan.model.PackagesSearchResult;
import java.util.List;
import java.util.Map;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;

import static java.util.Optional.ofNullable;

public class PackagesSearchResponseMapper {

private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS"
);

private PackagesSearchResponseMapper() {
// Utility class
}

public static DatasetsSearchResponse from(PackagesSearchResponse response) {
return DatasetsSearchResponse.builder()
.count(count(response.getResult()))
.facetGroups(facetGroups(response.getResult()))
.results(results(response.getResult()))
.build();
}

private static Integer count(PackagesSearchResult result) {
return ofNullable(result)
.map(PackagesSearchResult::getCount)
.orElse(null);
}

private static List<FacetGroup> facetGroups(PackagesSearchResult result) {
return ofNullable(result)
.map(PackagesSearchResult::getSearchFacets)
.map(PackagesSearchResponseMapper::facetGroup)
.map(List::of)
.orElseGet(List::of);
}

private static FacetGroup facetGroup(Map<String, CkanFacet> facets) {
return FacetGroup.builder()
.key("dcat")
.label("Metadata")
.facets(facets.entrySet().stream()
.map(PackagesSearchResponseMapper::facet)
.toList())
.build();
}

private static Facet facet(Map.Entry<String, CkanFacet> entry) {
var key = entry.getKey();
var facet = entry.getValue();
var values = ofNullable(facet.getItems())
.orElseGet(List::of)
.stream().map(value -> ValueLabel.builder()
.value(value.getName())
.label(value.getDisplayName())
.build())
.toList();

return Facet.builder()
.key(key)
.label(facet.getTitle())
.values(values)
.build();
}

private static List<SearchedDataset> results(PackagesSearchResult result) {
var nonNullDatasets = ofNullable(result)
.map(PackagesSearchResult::getResults)
.orElseGet(List::of);

return nonNullDatasets.stream()
.map(PackagesSearchResponseMapper::result)
.toList();
}

private static SearchedDataset result(CkanPackage dataset) {
var catalogue = ofNullable(dataset.getOrganization())
.map(CkanOrganization::getTitle)
.orElse(null);

return SearchedDataset.builder()
.id(dataset.getId())
.identifier(dataset.getIdentifier())
.title(dataset.getName())
.description(dataset.getNotes())
.themes(values(dataset.getTheme()))
.catalogue(catalogue)
.modifiedAt(parse(dataset.getMetadataModified()))
.build();
}

private static LocalDateTime parse(String date) {
return ofNullable(date)
.map(it -> LocalDateTime.parse(it, DATE_FORMATTER))
.orElse(null);
}

private static List<ValueLabel> values(List<String> values) {
return ofNullable(values)
.orElseGet(List::of)
.stream()
.map(it -> ValueLabel.builder()
.value(it)
.label(it)
.build())
.toList();
}
}
15 changes: 9 additions & 6 deletions src/main/openapi/ckan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,19 @@ paths:
components:
schemas:
PackagesSearchResponse:
type: object
properties:
help:
type: string
success:
type: boolean
result:
$ref: "#/components/schemas/PackagesSearchResult"
PackagesSearchResult:
type: object
properties:
count:
type: integer
description: The number of results found
results:
type: array
items:
Expand All @@ -115,7 +123,6 @@ components:
type: object
additionalProperties:
$ref: "#/components/schemas/CkanFacet"
description: Aggregated information about facet
CkanFacet:
type: object
properties:
Expand Down Expand Up @@ -159,10 +166,8 @@ components:
$ref: "#/components/schemas/CkanOrganization"
metadata_created:
type: string
format: date-time
metadata_modified:
type: string
format: date-time
url:
type: string
language:
Expand Down Expand Up @@ -220,10 +225,8 @@ components:
type: string
created:
type: string
format: date-time
last_modified:
type: string
format: date-time
required:
- id
- name
Expand Down
10 changes: 8 additions & 2 deletions src/main/openapi/discovery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,25 @@ components:
- label
FacetGroup:
properties:
key:
type: string
title: Key
label:
type: string
title: label
title: Label
facets:
type: array
items:
$ref: "#/components/schemas/Facet"
title: Facets
Facet:
properties:
key:
type: string
title: Key
label:
type: string
title: label
title: Label
values:
type: array
items:
Expand Down
12 changes: 9 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
# SPDX-License-Identifier: Apache-2.0
quarkus.swagger-ui.always-include=true
quarkus.native.additional-build-args=-march=compatibility
%dev.quarkus.rest-client.logging.scope=request-response
%dev.quarkus.rest-client.logging.body-limit=10000
%dev.quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG
quarkus.keycloak.devservices.realm-path=quarkus-realm.json
quarkus.keycloak.devservices.port=32794
quarkus.wiremock.devservices.port=4000
Expand All @@ -16,6 +13,8 @@ quarkus.openapi-generator.codegen.spec.discovery_yaml.additional-model-type-anno
quarkus.openapi-generator.codegen.spec.discovery_yaml.base-package=io.github.genomicdatainfrastructure.discovery
quarkus.openapi-generator.codegen.spec.discovery_yaml.import-mappings.File=org.jboss.resteasy.reactive.multipart.FileUpload
quarkus.openapi-generator.codegen.spec.discovery_yaml.type-mappings.File=FileUpload
quarkus.openapi-generator.codegen.spec.discovery_yaml.type-mappings.DateTime=LocalDateTime
quarkus.openapi-generator.codegen.spec.discovery_yaml.import-mappings.LocalDateTime=java.time.LocalDateTime
quarkus.openapi-generator.codegen.spec.ckan_yaml.enable-security-generation=false
quarkus.openapi-generator.codegen.spec.ckan_yaml.base-package=io.github.genomicdatainfrastructure.discovery.remote.ckan
quarkus.openapi-generator.codegen.spec.ckan_yaml.additional-model-type-annotations=@lombok.Data;@lombok.NoArgsConstructor;@lombok.AllArgsConstructor;@lombok.Builder
Expand All @@ -31,3 +30,10 @@ quarkus.openapi-generator.codegen.spec.keycloak_yaml.generate-part-filename=fals
quarkus.rest-client.ckan_yaml.url=http://localhost:4000
quarkus.rest-client.keycloak_yaml.url=http://localhost:4000
quarkus.rest-client.beacon_yaml.url=http://localhost:4000
quarkus.rest-client.read-timeout=1000
%dev.quarkus.oidc.auth-server-url=https://keycloak-test.healthdata.nl/realms/ckan
%dev.quarkus.oidc.client-id=ckan
%dev.quarkus.rest-client.ckan_yaml.url=https://ckan-test.healthdata.nl/
%dev.quarkus.rest-client.logging.scope=request-response
%dev.quarkus.rest-client.logging.body-limit=100000000
%dev.quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG

This file was deleted.

Loading

0 comments on commit 25ff86c

Please sign in to comment.