Skip to content

Commit

Permalink
feat: #4 integrate with Beacon
Browse files Browse the repository at this point in the history
  • Loading branch information
brunopacheco1 committed Apr 9, 2024
1 parent fa0697b commit d1d8554
Show file tree
Hide file tree
Showing 16 changed files with 108,618 additions and 15 deletions.
2 changes: 1 addition & 1 deletion _http/beacon.http
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Content-Type: application/json
"query":{
"filters":[
{
"id":"NCIT:C16352",
"id":"UBERON:0005352",
"scope": "individual"
}
],
Expand Down
20 changes: 19 additions & 1 deletion _http/discovery.http
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,29 @@ Content-Type: application/json
}, {
"facetGroup": "ckan",
"facet": "theme",
"value": "http://purl.org/zonmw/covid19/10003"
"value": "http://purl.org/zonmw/covid19/10006"
}, {
"facetGroup": "ckan",
"facet": "tags",
"value": "COVID-19"
}, {
"facetGroup": "beacon",
"facet": "cellosaurus",
"value": "NCIT:C16352"
}]
}

###

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

{
"query": "COVID",
"facets": [{
"facetGroup": "beacon",
"facet": "cellosaurus",
"value": "NCIT:C16352"
}]
}

Expand Down
19 changes: 14 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.genomicdatainfrastructure</groupId>
<artifactId>gdi-userportal-dataset-discovery-service</artifactId>
Expand All @@ -25,6 +24,7 @@
<surefire.version>3.0.0-M7</surefire.version>
<quarkus-wiremock.version>1.3.0</quarkus-wiremock.version>
<jacoco-maven-plugin.version>0.8.11</jacoco-maven-plugin.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -77,10 +77,19 @@
<artifactId>quarkus-openapi-generator</artifactId>
<version>${quarkus-openapi-generator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
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.github.genomicdatainfrastructure.discovery.services.BeaconIndividualsSearchService;
import io.github.genomicdatainfrastructure.discovery.services.RetrieveDatasetService;
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity;
Expand All @@ -17,7 +17,7 @@
public class DatasetQueryApiImpl implements DatasetQueryApi {

private final SecurityIdentity identity;
private final DatasetsSearchService datasetsSearchService;
private final BeaconIndividualsSearchService datasetsSearchService;
private final RetrieveDatasetService retrievedDatasetService;

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0

package io.github.genomicdatainfrastructure.discovery.services;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import io.github.genomicdatainfrastructure.discovery.model.Facet;
import io.github.genomicdatainfrastructure.discovery.model.FacetGroup;
import io.github.genomicdatainfrastructure.discovery.model.ValueLabel;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.api.BeaconQueryApi;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconFilteringTermsResponse;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconFilteringTermsResponseContent;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconResource;
import io.quarkus.cache.CacheResult;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.ArrayList;

@ApplicationScoped
public class BeaconFilteringTermsService {

private static final String DEFAULT_SCOPE = "individual";

private final BeaconQueryApi beaconQueryApi;

public BeaconFilteringTermsService(
@RestClient BeaconQueryApi beaconQueryApi
) {
this.beaconQueryApi = beaconQueryApi;
}

@CacheResult(cacheName = "beacon-facet-group-cache")
public FacetGroup listFilteringTerms(String authorization) {
var filteringTerms = beaconQueryApi.listFilteringTerms(authorization);

var nonNullFilteringTermsResponse = ofNullable(filteringTerms)
.map(BeaconFilteringTermsResponse::getResponse)
.filter(it -> it.getFilteringTerms() != null && it.getResources() != null)
.orElseGet(BeaconFilteringTermsResponseContent::new);

var facetsMappedByName = nonNullFilteringTermsResponse.getResources().stream()
.filter(it -> it.getName() != null && !it.getName().isBlank())
.collect(toMap(
BeaconResource::getName,
it -> Facet.builder()
.key(it.getId())
.label(it.getName())
.values(new ArrayList<>())
.build()
));

nonNullFilteringTermsResponse.getFilteringTerms().stream()
.filter(it -> it.getLabel() != null && !it.getLabel().isBlank())
.filter(it -> it.getId() != null && !it.getId().isBlank())
.filter(it -> it.getScopes() != null && it.getScopes().contains(DEFAULT_SCOPE))
.filter(it -> facetsMappedByName.containsKey(it.getType()))
.forEach(filteringTerm -> {
var facet = facetsMappedByName.get(filteringTerm.getType());
var values = facet.getValues();
values.add(ValueLabel.builder()
.value(filteringTerm.getId())
.label(filteringTerm.getLabel())
.build());
});

return FacetGroup.builder()
.key("beacon")
.label("Data")
.facets(List.copyOf(facetsMappedByName.values()))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0

package io.github.genomicdatainfrastructure.discovery.services;

import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.toCollection;

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.model.FacetGroup;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.api.BeaconQueryApi;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsRequest;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsRequestMeta;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsRequestQuery;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsRequestQueryFilter;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsRequestQueryPagination;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsResponse;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconIndividualsResponseContent;
import io.github.genomicdatainfrastructure.discovery.remote.beacon.model.BeaconResultSet;
import io.github.genomicdatainfrastructure.discovery.remote.keycloak.api.KeycloakQueryApi;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@ApplicationScoped
public class BeaconIndividualsSearchService {

private static final String BEACON_FACET_GROUP = "beacon";
private static final String BEACON_IDP_ALIAS = "LSAAI";
private static final String BEARER_PATTERN = "Bearer %s";
private static final String BEACON_DATASET_TYPE = "dataset";
private static final String CKAN_FACET_GROUP = "ckan";
private static final String CKAN_IDENTIFIER_FIELD = "identifier";
private static final String SCOPE = "individual";
private static final String INCLUDE_RESULTSET_RESPONSES = "HIT";
private static final String REQUESTED_GRANULARITY = "count";

private final BeaconQueryApi beaconQueryApi;
private final KeycloakQueryApi keycloakQueryApi;
private final DatasetsSearchService datasetsSearchService;
private final BeaconFilteringTermsService beaconFilteringTermsService;

@Inject
public BeaconIndividualsSearchService(
@RestClient BeaconQueryApi beaconQueryApi,
@RestClient KeycloakQueryApi keycloakQueryApi,
DatasetsSearchService datasetsSearchService,
BeaconFilteringTermsService beaconFilteringTermsService
) {
this.beaconQueryApi = beaconQueryApi;
this.keycloakQueryApi = keycloakQueryApi;
this.datasetsSearchService = datasetsSearchService;
this.beaconFilteringTermsService = beaconFilteringTermsService;
}

public DatasetsSearchResponse search(DatasetSearchQuery query, String accessToken) {
if (accessToken == null) {
return datasetsSearchService.search(query, accessToken);
}
var beaconAuthorization = retrieveAuthorization(accessToken);
var beaconResponse = queryOnBeacon(beaconAuthorization, query);
var enhancedQuery = enhanceQueryFacets(query, beaconResponse);
var datasetsReponse = datasetsSearchService.search(enhancedQuery, accessToken);
return enhanceDatasetsResponse(beaconAuthorization, datasetsReponse, beaconResponse);
}

private String retrieveAuthorization(String accessToken) {
var keycloakAuthorization = BEARER_PATTERN.formatted(accessToken);
var response = keycloakQueryApi.retriveIdpTokens(BEACON_IDP_ALIAS, keycloakAuthorization);
return BEARER_PATTERN.formatted(response.getAccessToken());
}

private List<BeaconResultSet> queryOnBeacon(
String beaconAuthorization,
DatasetSearchQuery query
) {
var beaconFilters = ofNullable(query.getFacets()).orElseGet(List::of)
.stream()
.filter(it -> BEACON_FACET_GROUP.equals(it.getFacetGroup()))
.map(DatasetSearchQueryFacet::getValue)
.filter(not(String::isBlank))
.map(it -> BeaconIndividualsRequestQueryFilter.builder()
.id(it)
.scope(SCOPE)
.build())
.toList();

var beaconQuery = BeaconIndividualsRequest.builder()
.meta(new BeaconIndividualsRequestMeta())
.query(BeaconIndividualsRequestQuery.builder()
.includeResultsetResponses(INCLUDE_RESULTSET_RESPONSES)
.requestedGranularity(REQUESTED_GRANULARITY)
.testMode(false)
.pagination(new BeaconIndividualsRequestQueryPagination())
.filters(beaconFilters)
.build())
.build();

var response = beaconQueryApi.listIndividuals(beaconAuthorization, beaconQuery);

return ofNullable(response)
.map(BeaconIndividualsResponse::getResponse)
.map(BeaconIndividualsResponseContent::getResultSets)
.orElseGet(List::of)
.stream()
.filter(it -> BEACON_DATASET_TYPE.equals(it.getSetType()) &&
it.getResultsCount() > 0
)
.toList();
}

private DatasetSearchQuery enhanceQueryFacets(
DatasetSearchQuery query,
List<BeaconResultSet> resultSets
) {
var enhancedFacets = resultSets.stream()
.map(BeaconResultSet::getId)
.map(it -> DatasetSearchQueryFacet.builder()
.facetGroup(CKAN_FACET_GROUP)
.facet(CKAN_IDENTIFIER_FIELD)
.value(it)
.build())
.collect(toCollection(ArrayList::new));

if (query.getFacets() != null) {
enhancedFacets.addAll(query.getFacets());
}

return query.toBuilder()
.facets(enhancedFacets)
.build();
}

private DatasetsSearchResponse enhanceDatasetsResponse(
String beaconAuthorization,
DatasetsSearchResponse datasetsReponse,
List<BeaconResultSet> resultSets
) {
var facetGroupCount = new HashMap<String, Integer>();
facetGroupCount.put("beacon", resultSets.size());
if (datasetsReponse.getFacetGroupCount() != null) {
facetGroupCount.putAll(datasetsReponse.getFacetGroupCount());
}

var facetGroups = new ArrayList<FacetGroup>();
facetGroups.add(beaconFilteringTermsService.listFilteringTerms(beaconAuthorization));
if (datasetsReponse.getFacetGroups() != null) {
facetGroups.addAll(datasetsReponse.getFacetGroups());
}

return datasetsReponse.toBuilder()
.facetGroupCount(facetGroupCount)
.facetGroups(facetGroups)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@ApplicationScoped
public class DatasetsSearchService {

private static final String FACETS_QUERY = "[\"access_rights\",\"theme\",\"tags\",\"spatial_uri\",\"organization\",\"publisher_name\",\"res_format\"]";
private static final String SELECTED_FACETS = "[\"access_rights\",\"theme\",\"tags\",\"spatial_uri\",\"organization\",\"publisher_name\",\"res_format\"]";

private final CkanQueryApi ckanQueryApi;

Expand All @@ -27,15 +27,18 @@ public DatasetsSearchService(
}

public DatasetsSearchResponse search(DatasetSearchQuery query, String accessToken) {
var facetsQuery = CkanFacetsQueryBuilder.buildFacetQuery(query.getFacets());

var response = ckanQueryApi.packageSearch(
query.getQuery(),
CkanFacetsQueryBuilder.buildFacetQuery(query.getFacets()),
facetsQuery,
query.getSort(),
query.getRows(),
query.getStart(),
FACETS_QUERY,
SELECTED_FACETS,
accessToken
);

return PackagesSearchResponseMapper.from(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ private PackagesSearchResponseMapper() {
}

public static DatasetsSearchResponse from(PackagesSearchResponse response) {
var count = count(response.getResult());

return DatasetsSearchResponse.builder()
.count(count(response.getResult()))
.count(count)
.facetGroups(facetGroups(response.getResult()))
.results(results(response.getResult()))
.facetGroupCount(Map.of("ckan", count))
.build();
}

Expand All @@ -58,7 +61,7 @@ private static List<FacetGroup> facetGroups(PackagesSearchResult result) {
private static FacetGroup facetGroup(Map<String, CkanFacet> facets) {
return FacetGroup.builder()
.key("ckan")
.label("Metadata")
.label("DCAT-AP")
.facets(facets.entrySet().stream()
.map(PackagesSearchResponseMapper::facet)
.toList())
Expand Down
Loading

0 comments on commit d1d8554

Please sign in to comment.