From 9593d19162d29721800ec9873e50eab522e4908e Mon Sep 17 00:00:00 2001 From: Daniel Hahne Date: Tue, 25 Feb 2025 11:03:41 +0100 Subject: [PATCH] Check Availability of gICS and gPAS During Startup --- .github/test/tc-agent/compose.yaml | 4 +-- ...=> GicsFhirConsentedPatientsProvider.java} | 35 +++++++++++++++---- .../configuration/GicsFhirConfiguration.java | 24 +++++++++++-- ...GpasFhirDeIdentificationConfiguration.java | 18 ++++++++++ ...rConsentedPatientsProviderFetchAllIT.java} | 16 ++++----- ...FhirConsentedPatientsProviderFetchIT.java} | 18 +++++----- .../GicsFhirConfigurationTest.java | 4 +-- .../care/smith/fts/util/FhirClientUtils.java | 12 +++++++ .../java/care/smith/fts/util/FhirUtils.java | 3 ++ 9 files changed, 104 insertions(+), 30 deletions(-) rename trust-center-agent/src/main/java/care/smith/fts/tca/consent/{FhirConsentedPatientsProvider.java => GicsFhirConsentedPatientsProvider.java} (69%) rename trust-center-agent/src/test/java/care/smith/fts/tca/consent/{FhirConsentedPatientsProviderFetchAllIT.java => GicsFhirConsentedPatientsProviderFetchAllIT.java} (96%) rename trust-center-agent/src/test/java/care/smith/fts/tca/consent/{FhirConsentedPatientsProviderFetchIT.java => GicsFhirConsentedPatientsProviderFetchIT.java} (96%) create mode 100644 util/src/main/java/care/smith/fts/util/FhirClientUtils.java diff --git a/.github/test/tc-agent/compose.yaml b/.github/test/tc-agent/compose.yaml index 6e912687..913e00ce 100644 --- a/.github/test/tc-agent/compose.yaml +++ b/.github/test/tc-agent/compose.yaml @@ -16,8 +16,8 @@ services: depends_on: keystore: condition: service_healthy - gics: # CI_ONLY - condition: service_healthy # CI_ONLY + # gics: # CI_ONLY + # condition: service_healthy # CI_ONLY gpas: # CI_ONLY condition: service_healthy # CI_ONLY healthcheck: diff --git a/trust-center-agent/src/main/java/care/smith/fts/tca/consent/FhirConsentedPatientsProvider.java b/trust-center-agent/src/main/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProvider.java similarity index 69% rename from trust-center-agent/src/main/java/care/smith/fts/tca/consent/FhirConsentedPatientsProvider.java rename to trust-center-agent/src/main/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProvider.java index c3260df0..8047f70a 100644 --- a/trust-center-agent/src/main/java/care/smith/fts/tca/consent/FhirConsentedPatientsProvider.java +++ b/trust-center-agent/src/main/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProvider.java @@ -1,6 +1,7 @@ package care.smith.fts.tca.consent; import static care.smith.fts.tca.consent.GicsFhirUtil.filterOuterBundle; +import static care.smith.fts.util.FhirClientUtils.fetchCapabilityStatement; import static care.smith.fts.util.MediaTypes.APPLICATION_FHIR_JSON; import static care.smith.fts.util.RetryStrategies.defaultRetryStrategy; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -10,6 +11,7 @@ import care.smith.fts.util.tca.ConsentFetchRequest; import care.smith.fts.util.tca.ConsentRequest; import io.micrometer.core.instrument.MeterRegistry; +import java.net.ConnectException; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.hl7.fhir.r4.model.*; @@ -17,11 +19,12 @@ import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.Exceptions; import reactor.core.publisher.Mono; /** This class provides functionalities for handling FHIR consents using an HTTP client. */ @Slf4j -public class FhirConsentedPatientsProvider implements ConsentedPatientsProvider { +public class GicsFhirConsentedPatientsProvider implements ConsentedPatientsProvider { private final WebClient gicsClient; private final MeterRegistry meterRegistry; @@ -30,7 +33,7 @@ public class FhirConsentedPatientsProvider implements ConsentedPatientsProvider * * @param gicsClient the WebClient used for HTTP requests */ - public FhirConsentedPatientsProvider(WebClient gicsClient, MeterRegistry meterRegistry) { + public GicsFhirConsentedPatientsProvider(WebClient gicsClient, MeterRegistry meterRegistry) { this.gicsClient = gicsClient; this.meterRegistry = meterRegistry; } @@ -76,8 +79,7 @@ private Mono doFetch( .headers(h -> h.setContentType(APPLICATION_FHIR_JSON)) .headers(h -> h.setAccept(List.of(APPLICATION_FHIR_JSON, APPLICATION_JSON))) .retrieve() - .onStatus( - r -> r.equals(HttpStatus.NOT_FOUND), FhirConsentedPatientsProvider::handleGicsNotFound) + .onStatus(r -> r.equals(HttpStatus.NOT_FOUND), this::handleGicsNotFound) .bodyToMono(Bundle.class) .doOnNext(b -> log.trace("body(n: {})", b.getEntry().size())) .retryWhen(defaultRetryStrategy(meterRegistry, helper.requestName())) @@ -86,10 +88,31 @@ private Mono doFetch( .map(bundle -> helper.processResponse(bundle, req, requestUrl, paging)); } - private static Mono handleGicsNotFound(ClientResponse r) { + private Mono handleGicsNotFound(ClientResponse r) { log.trace("response headers: {}", r.headers().asHttpHeaders()); return r.bodyToMono(OperationOutcome.class) - .doOnNext(re -> log.info("{}", re)) + .onErrorResume( + serializationError -> + fetchCapabilityStatement(gicsClient) + .flatMap(capabilityStatement -> Mono.just(new OperationOutcome())) + .onErrorResume( + e -> { + OperationOutcome outcome = new OperationOutcome(); + if (Exceptions.unwrap(e) instanceof ConnectException) { + log.error("Unable to connect to gICS", e); + outcome + .addIssue() + .setSeverity(OperationOutcome.IssueSeverity.ERROR) + .setDiagnostics("No connection to gICS"); + } else { + log.error("Unexpected error while connecting to gICS", e); + outcome + .addIssue() + .setSeverity(OperationOutcome.IssueSeverity.ERROR) + .setDiagnostics("Unexpected error while connecting to gICS"); + } + return Mono.just(outcome); + })) .flatMap( b -> { log.info("issue: {}", b.getIssueFirstRep()); diff --git a/trust-center-agent/src/main/java/care/smith/fts/tca/consent/configuration/GicsFhirConfiguration.java b/trust-center-agent/src/main/java/care/smith/fts/tca/consent/configuration/GicsFhirConfiguration.java index 30c83e48..f0ff3619 100644 --- a/trust-center-agent/src/main/java/care/smith/fts/tca/consent/configuration/GicsFhirConfiguration.java +++ b/trust-center-agent/src/main/java/care/smith/fts/tca/consent/configuration/GicsFhirConfiguration.java @@ -1,17 +1,24 @@ package care.smith.fts.tca.consent.configuration; -import care.smith.fts.tca.consent.FhirConsentedPatientsProvider; +import static care.smith.fts.util.FhirClientUtils.fetchCapabilityStatement; + +import care.smith.fts.tca.consent.GicsFhirConsentedPatientsProvider; +import care.smith.fts.util.FhirClientUtils; import care.smith.fts.util.HttpClientConfig; import care.smith.fts.util.WebClientFactory; import care.smith.fts.util.auth.HttpClientAuth; import io.micrometer.core.instrument.MeterRegistry; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.ApplicationRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; +@Slf4j @Configuration @ConfigurationProperties(prefix = "consent.gics.fhir") @Data @@ -31,11 +38,22 @@ public WebClient gicsClient(WebClientFactory clientFactory) { return clientFactory.create(config); } + @Bean("gicsApplicationRunner") + ApplicationRunner runner(@Qualifier("gicsFhirHttpClient") WebClient gicsClient) { + return args -> { + fetchCapabilityStatement(gicsClient) + .doOnNext(i -> log.info("gCIS available")) + .doOnError(e -> log.warn("No connection to gCIS", e)) + .onErrorComplete() + .block(); + }; + } + @Bean - FhirConsentedPatientsProvider fhirConsentedPatientsProvider( + GicsFhirConsentedPatientsProvider fhirConsentedPatientsProvider( WebClientFactory clientFactory, MeterRegistry meterRegistry) { var config = new HttpClientConfig(baseUrl, auth); var client = clientFactory.create(config); - return new FhirConsentedPatientsProvider(client, meterRegistry); + return new GicsFhirConsentedPatientsProvider(client, meterRegistry); } } diff --git a/trust-center-agent/src/main/java/care/smith/fts/tca/deidentification/configuration/GpasFhirDeIdentificationConfiguration.java b/trust-center-agent/src/main/java/care/smith/fts/tca/deidentification/configuration/GpasFhirDeIdentificationConfiguration.java index cc830320..a4a4005e 100644 --- a/trust-center-agent/src/main/java/care/smith/fts/tca/deidentification/configuration/GpasFhirDeIdentificationConfiguration.java +++ b/trust-center-agent/src/main/java/care/smith/fts/tca/deidentification/configuration/GpasFhirDeIdentificationConfiguration.java @@ -1,5 +1,8 @@ package care.smith.fts.tca.deidentification.configuration; +import static care.smith.fts.util.FhirClientUtils.fetchCapabilityStatement; + +import care.smith.fts.util.FhirClientUtils; import care.smith.fts.util.HttpClientConfig; import care.smith.fts.util.WebClientFactory; import care.smith.fts.util.auth.HttpClientAuth; @@ -8,11 +11,15 @@ import java.security.SecureRandom; import java.util.random.RandomGenerator; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.ApplicationRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; +@Slf4j @Configuration @ConfigurationProperties(prefix = "de-identification.gpas.fhir") @Data @@ -30,6 +37,17 @@ public WebClient gpasClient(WebClientFactory clientFactory) { return clientFactory.create(new HttpClientConfig(baseUrl, auth)); } + @Bean("gpasApplicationRunner") + ApplicationRunner runner(@Qualifier("gpasFhirHttpClient") WebClient gpasClient) { + return args -> { + fetchCapabilityStatement(gpasClient) + .doOnNext(i -> log.info("gPAS available")) + .doOnError(e -> log.warn("No connection to gPAS", e)) + .onErrorComplete() + .block(); + }; + } + @Bean public RandomGenerator secureRandom() { return new SecureRandom(); diff --git a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchAllIT.java b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchAllIT.java similarity index 96% rename from trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchAllIT.java rename to trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchAllIT.java index 6c2e0afe..9bea9775 100644 --- a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchAllIT.java +++ b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchAllIT.java @@ -48,7 +48,7 @@ @SpringBootTest @WireMockTest @Import(TestWebClientFactory.class) -class FhirConsentedPatientsProviderFetchAllIT { +class GicsFhirConsentedPatientsProviderFetchAllIT { @Autowired WebClient.Builder httpClientBuilder; @Autowired MeterRegistry meterRegistry; @@ -98,7 +98,7 @@ void tearDown() { void paging() { int totalEntries = 2 * defaultPageSize; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); Bundle bundle = @@ -155,7 +155,7 @@ void noNextLinkOnLastPage() { int pageSize = 1; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); Bundle bundle = @@ -188,7 +188,7 @@ void noConsents() { int pageSize = 1; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); Bundle bundle = Stream.generate(gicsConsentGenerator::generateString) @@ -223,7 +223,7 @@ void unknownDomainCausesGicsNotFound() { int pageSize = 2; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -252,7 +252,7 @@ void somethingElseCausesGicsNotFound() { int pageSize = 2; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -281,7 +281,7 @@ void diagnosticsIsNullInHandleGicsNotFound() { int pageSize = 2; var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -311,7 +311,7 @@ void emptyPoliciesYieldEmptyBundle() { var consentRequest = new ConsentFetchAllRequest("MII", Set.of(), POLICY_SYSTEM); var fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); Bundle bundle = Stream.generate(gicsConsentGenerator::generateString) diff --git a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchIT.java b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchIT.java similarity index 96% rename from trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchIT.java rename to trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchIT.java index 5db1ec03..4fd09311 100644 --- a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/FhirConsentedPatientsProviderFetchIT.java +++ b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/GicsFhirConsentedPatientsProviderFetchIT.java @@ -44,7 +44,7 @@ @SpringBootTest @WireMockTest @Import(TestWebClientFactory.class) -class FhirConsentedPatientsProviderFetchIT { +class GicsFhirConsentedPatientsProviderFetchIT { @Autowired WebClient.Builder httpClientBuilder; @Autowired MeterRegistry meterRegistry; @@ -56,7 +56,7 @@ class FhirConsentedPatientsProviderFetchIT { "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy"; private static final String PATIENT_IDENTIFIER_SYSTEM = "https://ths-greifswald.de/fhir/gics/identifiers/Pseudonym"; - private FhirConsentedPatientsProvider fhirConsentProvider; + private GicsFhirConsentedPatientsProvider fhirConsentProvider; private static final Set POLICIES = Set.of( @@ -106,7 +106,7 @@ void paging() { int totalEntries = 2 * pageSize; fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var bundle1 = @@ -172,7 +172,7 @@ void noConsents() { int pageSize = 2; fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); Bundle bundle = Stream.generate(gicsConsentGenerator::generateString) @@ -207,7 +207,7 @@ void unknownDomainCausesGicsNotFound() { int pageSize = 2; fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -233,7 +233,7 @@ void somethingElseCausesGicsNotFound() { int pageSize = 2; fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -259,7 +259,7 @@ void diagnosticsIsNullInHandleGicsNotFound() { int pageSize = 2; fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); var operationOutcome = new OperationOutcome(); @@ -289,7 +289,7 @@ void emptyPoliciesYieldEmptyBundle() { PATIENT_IDENTIFIER_SYSTEM, List.of("id1", "id2", "id3", "id4")); fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); create( @@ -310,7 +310,7 @@ void emptyPidsYieldEmptyBundle() { new ConsentFetchRequest( "MII", POLICIES, POLICY_SYSTEM, PATIENT_IDENTIFIER_SYSTEM, List.of()); fhirConsentProvider = - new FhirConsentedPatientsProvider( + new GicsFhirConsentedPatientsProvider( httpClientBuilder.baseUrl(address).build(), meterRegistry); create( fhirConsentProvider.fetch( diff --git a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/configuration/GicsFhirConfigurationTest.java b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/configuration/GicsFhirConfigurationTest.java index 500f63a0..7b442f76 100644 --- a/trust-center-agent/src/test/java/care/smith/fts/tca/consent/configuration/GicsFhirConfigurationTest.java +++ b/trust-center-agent/src/test/java/care/smith/fts/tca/consent/configuration/GicsFhirConfigurationTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import care.smith.fts.tca.consent.FhirConsentedPatientsProvider; +import care.smith.fts.tca.consent.GicsFhirConsentedPatientsProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -16,7 +16,7 @@ class GicsFhirConfigurationIT { @Autowired private GicsFhirConfiguration gicsFhirConfiguration; - @Autowired private FhirConsentedPatientsProvider fhirConsentProvider; + @Autowired private GicsFhirConsentedPatientsProvider fhirConsentProvider; @MockitoBean RedissonClient redisClient; // We need to mock the redisClient otherwise the tests won't start diff --git a/util/src/main/java/care/smith/fts/util/FhirClientUtils.java b/util/src/main/java/care/smith/fts/util/FhirClientUtils.java new file mode 100644 index 00000000..dd5f6baa --- /dev/null +++ b/util/src/main/java/care/smith/fts/util/FhirClientUtils.java @@ -0,0 +1,12 @@ +package care.smith.fts.util; + +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +public interface FhirClientUtils { + + static Mono fetchCapabilityStatement(WebClient client) { + return client.get().uri("/metadata").retrieve().bodyToMono(CapabilityStatement.class); + } +} diff --git a/util/src/main/java/care/smith/fts/util/FhirUtils.java b/util/src/main/java/care/smith/fts/util/FhirUtils.java index 54d83794..01fe48a4 100644 --- a/util/src/main/java/care/smith/fts/util/FhirUtils.java +++ b/util/src/main/java/care/smith/fts/util/FhirUtils.java @@ -10,7 +10,9 @@ import java.util.stream.Stream; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.Resource; +import org.springframework.web.reactive.function.client.WebClient; public interface FhirUtils { FhirContext fctx = FhirContext.forR4(); @@ -76,4 +78,5 @@ private static Bundle toBundle(List l) { l.stream().map(r -> new Bundle.BundleEntryComponent().setResource(r)).toList(); return new Bundle().setTotal(l.size()).setEntry(list); } + }