Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package life.qbic.datamanager;

import java.util.Objects;
import life.qbic.broadcasting.Exchange;
import life.qbic.broadcasting.MessageBusSubmission;
import life.qbic.domain.concepts.SimpleEventStore;
import life.qbic.domain.concepts.TemporaryEventRepository;
import life.qbic.identity.api.UserInformationService;
import life.qbic.identity.api.UserPasswordService;
import life.qbic.identity.application.communication.EmailService;
import life.qbic.identity.application.communication.broadcasting.EventHub;
import life.qbic.identity.application.notification.NotificationService;
Expand All @@ -28,13 +28,13 @@
import life.qbic.projectmanagement.application.AppContextProvider;
import life.qbic.projectmanagement.application.OrganisationRepository;
import life.qbic.projectmanagement.application.ProjectInformationService;
import life.qbic.projectmanagement.application.concurrent.ElasticScheduler;
import life.qbic.projectmanagement.application.concurrent.VirtualThreadScheduler;
import life.qbic.projectmanagement.application.api.SampleCodeService;
import life.qbic.projectmanagement.application.authorization.acl.ProjectAccessService;
import life.qbic.projectmanagement.application.authorization.authorities.AuthorityService;
import life.qbic.projectmanagement.application.batch.BatchRegistrationService;
import life.qbic.projectmanagement.application.communication.broadcasting.MessageRouter;
import life.qbic.projectmanagement.application.concurrent.ElasticScheduler;
import life.qbic.projectmanagement.application.concurrent.VirtualThreadScheduler;
import life.qbic.projectmanagement.application.experiment.ExperimentInformationService;
import life.qbic.projectmanagement.application.measurement.MeasurementLookupService;
import life.qbic.projectmanagement.application.policy.BatchRegisteredPolicy;
Expand Down Expand Up @@ -71,6 +71,8 @@
import life.qbic.projectmanagement.application.sample.qualitycontrol.QualityControlService;
import life.qbic.projectmanagement.domain.repository.ProjectRepository;
import life.qbic.projectmanagement.infrastructure.organisations.CachedOrganisationRepository;
import life.qbic.projectmanagement.infrastructure.organisations.RorApi;
import life.qbic.projectmanagement.infrastructure.organisations.RorApi.RorApiV2;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -115,8 +117,20 @@
}

@Bean
public OrganisationRepository organisationRepository() {
return new CachedOrganisationRepository();
RorApi rorApi(
@Value("${qbic.external-service.organisation-search.ror.client-id}") String clientId,
@Value("${qbic.external-service.organisation-search.ror.organisation-api-endpoint}") String rorOrganisationEndpoint) {
Objects.requireNonNull(rorOrganisationEndpoint);
Objects.requireNonNull(clientId);
RorApiV2 rorApiV2 = new RorApiV2(rorOrganisationEndpoint, clientId);

Check warning on line 125 in datamanager-app/src/main/java/life/qbic/datamanager/AppConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Immediately return this expression instead of assigning it to the temporary variable "rorApiV2".

See more on https://sonarcloud.io/project/issues?id=qbicsoftware_data-manager-app&issues=AZsM5k2N6CspXOJcT5Cz&open=AZsM5k2N6CspXOJcT5Cz&pullRequest=1359
return rorApiV2;
}

@Bean
public OrganisationRepository organisationRepository(
RorApi rorApi) {
Objects.requireNonNull(rorApi);
return new CachedOrganisationRepository(rorApi);
}


Expand Down
2 changes: 2 additions & 0 deletions datamanager-app/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ qbic.external-service.person-search.orcid.extended-search-uri=${qbic.external-se
qbic.external-service.person-search.orcid.scope=/read-public
qbic.external-service.person-search.orcid.grant-type=client_credentials
qbic.external-service.person-search.orcid.issuer=${ORCID_SEARCH_ISSUER_URL:https://orcid.org}
qbic.external-service.organisation-search.ror.organisation-api-endpoint=${ROR_ORGANISATION_ENDPOINT:https://api.ror.org/v2/organizations/}
qbic.external-service.organisation-search.ror.client-id=${ROR_CLIENT_ID}

###############################################################################
################### ActiveMQ Artemis ##########################################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
package life.qbic.projectmanagement.infrastructure.organisations;

import static life.qbic.logging.service.LoggerFactory.logger;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import life.qbic.logging.api.Logger;
import life.qbic.projectmanagement.application.OrganisationRepository;
import life.qbic.projectmanagement.domain.Organisation;
import life.qbic.projectmanagement.infrastructure.organisations.RorApi.RorEntry;
import org.springframework.context.annotation.Profile;

/**
Expand All @@ -38,23 +27,22 @@
@Profile("production")
public class CachedOrganisationRepository implements OrganisationRepository {

private static final Logger log = logger(CachedOrganisationRepository.class);
private static final int DEFAULT_CACHE_SIZE = 50;
private static final String ROR_API_URL = "https://api.ror.org/v1/organizations/%s";
private static final String ROR_ID_PATTERN = "0[a-z|0-9]{6}[0-9]{2}$";
private final Map<String, String> iriToOrganisation = new HashMap<>();
private final int configuredCacheSize;

private boolean cacheUsedForLastRequest = false;

private final RorApi rorApi;


public CachedOrganisationRepository(int cacheSize) {
this.configuredCacheSize = cacheSize;
public CachedOrganisationRepository(RorApi rorApi) {
this.rorApi = Objects.requireNonNull(rorApi);
this.configuredCacheSize = DEFAULT_CACHE_SIZE;
}

public CachedOrganisationRepository() {
this.configuredCacheSize = DEFAULT_CACHE_SIZE;
public CachedOrganisationRepository(int cacheSize, RorApi rorApi) {
this.configuredCacheSize = cacheSize;
this.rorApi = Objects.requireNonNull(rorApi);
}

private static Optional<String> extractRorId(String text) {
Expand All @@ -78,44 +66,26 @@ private Optional<Organisation> lookupCache(String iri) {
}

private Optional<Organisation> lookupROR(String iri) {
return extractRorId(iri).map(this::findOrganisationInROR).or(Optional::empty);
return extractRorId(iri)
.flatMap(this::findOrganisationInROR);
}

private Organisation findOrganisationInROR(String rorId) {
try {
HttpClient client = HttpClient.newBuilder().version(Version.HTTP_2)
.followRedirects(Redirect.NORMAL).connectTimeout(
Duration.ofSeconds(10)).build();
HttpRequest rorQuery = HttpRequest.newBuilder().uri(URI.create(ROR_API_URL.formatted(rorId)))
.header("Content-Type", "application/json").GET().build();
var result = client.send(rorQuery, BodyHandlers.ofString());
//If a valid RoRId was provided but the ID does not exist we fail
if (result.statusCode() != 200) {
log.warn(
"Provided Organisation ROR id: %s was not found via API call to %s".formatted(rorId,
ROR_API_URL));
return null;
}
RORentry rorEntry = new ObjectMapper().configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(result.body(), RORentry.class);
updateCache(rorEntry);
private Optional<Organisation> findOrganisationInROR(String rorId) {
Optional<RorEntry> rorEntry = rorApi.find(rorId);
rorEntry.ifPresent(entry -> {
updateCache(entry);
cacheUsedForLastRequest = false;
return new Organisation(rorEntry.getId(), rorEntry.getName());
} catch (IOException | InterruptedException e) {
log.error("Finding ROR entry failed for organisation: %s".formatted(rorId), e);
/* Clean up whatever needs to be handled before interrupting */
Thread.currentThread().interrupt();
return null;
}
});
return rorEntry
.map(entry -> new Organisation(entry.getId(), entry.getDisplayedName()));
}

private void updateCache(RORentry rorEntry) {
private void updateCache(RorEntry rorEntry) {
if (iriToOrganisation.size() == configuredCacheSize) {
String firstKey = iriToOrganisation.keySet().stream().toList().get(0);
iriToOrganisation.remove(firstKey);
}
iriToOrganisation.put(rorEntry.getId(), rorEntry.getName());
iriToOrganisation.put(rorEntry.getId(), rorEntry.getDisplayedName());
}

public int cacheEntries() {
Expand Down

This file was deleted.

Loading
Loading