diff options
| author | jlidke | 2025-07-22 20:02:15 +0200 |
|---|---|---|
| committer | GitHub | 2025-07-22 20:02:15 +0200 |
| commit | 199511e567884bb703277c276b782e54e528f744 (patch) | |
| tree | 42c19612ae161f98a252a13f4bd8234d7bae0527 /src/main/kotlin/dev/dnpm/etl/processor | |
| parent | 1319be8b3f5fbb3c4800dc9b942fc1982ac928d3 (diff) | |
63 check consent status (#120)
Co-authored-by: Paul-Christian Volkmer <code@pcvolkmer.de>
Diffstat (limited to 'src/main/kotlin/dev/dnpm/etl/processor')
17 files changed, 791 insertions, 59 deletions
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt index 331c8b5..a2ea032 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt @@ -27,7 +27,8 @@ data class AppConfigProperties( var bwhcUri: String?, var transformations: List<TransformationProperties> = listOf(), var maxRetryAttempts: Int = 3, - var duplicationDetection: Boolean = true + var duplicationDetection: Boolean = true, + var genomDeTestSubmission: Boolean = true ) { companion object { const val NAME = "app" @@ -56,6 +57,72 @@ data class GPasConfigProperties( } } +@ConfigurationProperties(ConsentConfigProperties.NAME) +data class ConsentConfigProperties( + var service: ConsentService = ConsentService.NONE +) { + companion object { + const val NAME = "app.consent" + } +} + +@ConfigurationProperties(GIcsConfigProperties.NAME) +data class GIcsConfigProperties( + /** + * Base URL to gICS System + * + */ + val uri: String?, + val username: String?, + val password: String?, + + /** + * gICS specific system + * **/ + val personIdentifierSystem: String = + "https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID", + + /** + * Domain of broad consent resources + **/ + val broadConsentDomainName: String = "MII", + + /** + * Domain of Modelvorhaben 64e consent resources + **/ + val genomDeConsentDomainName: String = "GenomDE_MV", + + /** + * Value to expect in case of positiv consent + */ + val broadConsentPolicyCode: String = "2.16.840.1.113883.3.1937.777.24.5.3.6", + + /** + * Consent Policy which should be used for consent check + */ + val broadConsentPolicySystem: String = "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + + /** + * Value to expect in case of positiv consent + */ + val genomeDePolicyCode: String = "sequencing", + + /** + * Consent Policy which should be used for consent check + */ + val genomeDePolicySystem: String = "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV", + + /** + * Consent version (fixed version) + * + */ + val genomeDeConsentVersion: String = "2.0" +) { + companion object { + const val NAME = "app.consent.gics" + } +} + @ConfigurationProperties(RestTargetProperties.NAME) data class RestTargetProperties( val uri: String?, @@ -99,8 +166,13 @@ enum class PseudonymGenerator { GPAS } +enum class ConsentService { + NONE, + GICS +} + data class TransformationProperties( val path: String, val from: String, val to: String -)
\ No newline at end of file +) diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt index c8f3fba..8f90947 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt @@ -20,24 +20,28 @@ package dev.dnpm.etl.processor.config import com.fasterxml.jackson.databind.ObjectMapper -import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult -import dev.dnpm.etl.processor.monitoring.ConnectionCheckService -import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService -import dev.dnpm.etl.processor.monitoring.ReportService +import dev.dnpm.etl.processor.consent.ConsentByMtbFile +import dev.dnpm.etl.processor.consent.GicsConsentService +import dev.dnpm.etl.processor.consent.IGetConsent +import dev.dnpm.etl.processor.monitoring.* import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator import dev.dnpm.etl.processor.pseudonym.Generator import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.security.TokenRepository import dev.dnpm.etl.processor.security.TokenService +import dev.dnpm.etl.processor.services.ConsentProcessor import dev.dnpm.etl.processor.services.Transformation import dev.dnpm.etl.processor.services.TransformationService import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Conditional import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.ConfigurationCondition import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration import org.springframework.retry.RetryCallback import org.springframework.retry.RetryContext @@ -60,7 +64,9 @@ import kotlin.time.toJavaDuration value = [ AppConfigProperties::class, PseudonymizeConfigProperties::class, - GPasConfigProperties::class + GPasConfigProperties::class, + ConsentConfigProperties::class, + GIcsConfigProperties::class ] ) @EnableScheduling @@ -73,13 +79,27 @@ class AppConfiguration { return RestTemplate() } + @Bean + fun appFhirConfig(): AppFhirConfig { + return AppFhirConfig() + } + @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS") @Bean - fun gpasPseudonymGenerator(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate): Generator { - return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate) + fun gpasPseudonymGenerator( + configProperties: GPasConfigProperties, + retryTemplate: RetryTemplate, + restTemplate: RestTemplate, + appFhirConfig: AppFhirConfig + ): Generator { + return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig) } - @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "BUILDIN", matchIfMissing = true) + @ConditionalOnProperty( + value = ["app.pseudonymize.generator"], + havingValue = "BUILDIN", + matchIfMissing = true + ) @Bean fun buildinPseudonymGenerator(): Generator { return AnonymizingGenerator() @@ -94,17 +114,21 @@ class AppConfiguration { } @Bean - fun reportService(objectMapper: ObjectMapper): ReportService { - return ReportService(objectMapper) + fun reportService(): ReportService { + return ReportService(getObjectMapper()) + } + + @Bean + fun getObjectMapper(): ObjectMapper { + return JacksonConfig().objectMapper() } @Bean fun transformationService( - objectMapper: ObjectMapper, configProperties: AppConfigProperties ): TransformationService { logger.info("Apply ${configProperties.transformations.size} transformation rules") - return TransformationService(objectMapper, configProperties.transformations.map { + return TransformationService(getObjectMapper(), configProperties.transformations.map { Transformation.of(it.path) from it.from to it.to }) } @@ -123,7 +147,11 @@ class AppConfiguration { callback: RetryCallback<T, E>, throwable: Throwable ) { - logger.warn("Error occured: {}. Retrying {}", throwable.message, context.retryCount) + logger.warn( + "Error occured: {}. Retrying {}", + throwable.message, + context.retryCount + ) } }) .build() @@ -131,7 +159,11 @@ class AppConfiguration { @ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true") @Bean - fun tokenService(userDetailsManager: InMemoryUserDetailsManager, passwordEncoder: PasswordEncoder, tokenRepository: TokenRepository): TokenService { + fun tokenService( + userDetailsManager: InMemoryUserDetailsManager, + passwordEncoder: PasswordEncoder, + tokenRepository: TokenRepository + ): TokenService { return TokenService(userDetailsManager, passwordEncoder, tokenRepository) } @@ -152,7 +184,11 @@ class AppConfiguration { gPasConfigProperties: GPasConfigProperties, connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> ): ConnectionCheckService { - return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer) + return GPasConnectionCheckService( + restTemplate, + gPasConfigProperties, + connectionCheckUpdateProducer + ) } @ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS") @@ -163,12 +199,85 @@ class AppConfiguration { gPasConfigProperties: GPasConfigProperties, connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> ): ConnectionCheckService { - return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer) + return GPasConnectionCheckService( + restTemplate, + gPasConfigProperties, + connectionCheckUpdateProducer + ) } @Bean fun jdbcConfiguration(): AbstractJdbcConfiguration { return AppJdbcConfiguration() } + + @Conditional(GicsEnabledCondition::class) + @Bean + fun gicsConsentService( + gIcsConfigProperties: GIcsConfigProperties, + retryTemplate: RetryTemplate, + restTemplate: RestTemplate, + appFhirConfig: AppFhirConfig + ): IGetConsent { + return GicsConsentService( + gIcsConfigProperties, + retryTemplate, + restTemplate, + appFhirConfig + ) + } + + @Conditional(GicsEnabledCondition::class) + @Bean + fun consentProcessor( + configProperties: AppConfigProperties, + gIcsConfigProperties: GIcsConfigProperties, + getObjectMapper: ObjectMapper, + appFhirConfig: AppFhirConfig, + gicsConsentService: IGetConsent + ): ConsentProcessor { + return ConsentProcessor( + configProperties, + gIcsConfigProperties, + getObjectMapper, + appFhirConfig.fhirContext(), + gicsConsentService + ) + } + + @Conditional(GicsEnabledCondition::class) + @Bean + fun gIcsConnectionCheckService( + restTemplate: RestTemplate, + gIcsConfigProperties: GIcsConfigProperties, + connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> + ): ConnectionCheckService { + return GIcsConnectionCheckService( + restTemplate, + gIcsConfigProperties, + connectionCheckUpdateProducer + ) + } + + @Bean + @ConditionalOnMissingBean + fun iGetConsentService(): IGetConsent { + return ConsentByMtbFile() + } + } +class GicsEnabledCondition : + AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) { + + @ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics") + class OnGicsServiceSelected { + // Just for Condition + } + + @ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true") + class OnGicsEnabled { + // Just for Condition + } + +} diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt new file mode 100644 index 0000000..2b5ff8f --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt @@ -0,0 +1,16 @@ +package dev.dnpm.etl.processor.config + +import ca.uhn.fhir.context.FhirContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + + +@Configuration +class AppFhirConfig { + private val fhirCtx: FhirContext = FhirContext.forR4() + + @Bean + fun fhirContext(): FhirContext { + return fhirCtx + } +}
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt new file mode 100644 index 0000000..5469b1b --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt @@ -0,0 +1,18 @@ +package dev.dnpm.etl.processor.config + +import com.fasterxml.jackson.core.JsonParser + +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import org.hl7.fhir.r4.model.Consent + +class ConsentResourceDeserializer : JsonDeserializer<Consent>() { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Consent { + + val jsonNode = p?.readValueAsTree<JsonNode>() + val json = jsonNode?.toString() + + return JacksonConfig.fhirContext().newJsonParser().parseResource(json) as Consent + } +}
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt new file mode 100644 index 0000000..812ce44 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt @@ -0,0 +1,15 @@ +package dev.dnpm.etl.processor.config + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import org.hl7.fhir.r4.model.Consent + +class ConsentResourceSerializer : JsonSerializer<Consent>() { + override fun serialize( + value: Consent, gen: JsonGenerator, serializers: SerializerProvider + ) { + val json = JacksonConfig.fhirContext().newJsonParser().encodeResourceToString(value) + gen.writeRawValue(json) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt new file mode 100644 index 0000000..2ae0dd3 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt @@ -0,0 +1,12 @@ +package dev.dnpm.etl.processor.config + + +import com.fasterxml.jackson.databind.module.SimpleModule +import org.hl7.fhir.r4.model.Consent + +class FhirResourceModule : SimpleModule() { + init { + addSerializer(Consent::class.java, ConsentResourceSerializer()) + addDeserializer(Consent::class.java, ConsentResourceDeserializer()) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt new file mode 100644 index 0000000..fb03d66 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt @@ -0,0 +1,27 @@ +package dev.dnpm.etl.processor.config + +import ca.uhn.fhir.context.FhirContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule + +@Configuration +class JacksonConfig { + + companion object { + var fhirContext: FhirContext = FhirContext.forR4() + + @JvmStatic + fun fhirContext(): FhirContext { + return fhirContext + } + } + + @Bean + fun objectMapper(): ObjectMapper = ObjectMapper().registerModule(FhirResourceModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).registerModule( + JavaTimeModule() + ) +} diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt index e797390..415a68f 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt @@ -25,6 +25,7 @@ import de.ukw.ccc.bwhc.dto.MtbFile import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientId import dev.dnpm.etl.processor.RequestId +import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.services.RequestProcessor import org.apache.kafka.clients.consumer.ConsumerRecord import org.slf4j.LoggerFactory @@ -76,9 +77,13 @@ class KafkaInputListener( } else { logger.debug("Accepted MTB File and process deletion") if (requestId.isBlank()) { - requestProcessor.processDeletion(patientId) + requestProcessor.processDeletion(patientId, TtpConsentStatus.UNKNOWN_CHECK_FILE) } else { - requestProcessor.processDeletion(patientId, requestId) + requestProcessor.processDeletion( + patientId, + requestId, + TtpConsentStatus.UNKNOWN_CHECK_FILE + ) } } } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt index e67a380..44c74e3 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt @@ -23,6 +23,8 @@ import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.MtbFile import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientId +import dev.dnpm.etl.processor.consent.IGetConsent +import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.services.RequestProcessor import dev.pcvolkmer.mv64e.mtb.Mtb import org.slf4j.LoggerFactory @@ -33,7 +35,7 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping(path = ["mtbfile", "mtb"]) class MtbFileRestController( - private val requestProcessor: RequestProcessor, + private val requestProcessor: RequestProcessor, private val iGetConsent: IGetConsent ) { private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java) @@ -43,20 +45,39 @@ class MtbFileRestController( return ResponseEntity.ok("Test") } - @PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] ) + @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE]) fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity<Unit> { - if (mtbFile.consent.status == Consent.Status.ACTIVE) { + val consentStatusBooleanPair = checkConsentStatus(mtbFile) + val ttpConsentStatus = consentStatusBooleanPair.first + val isConsentOK = consentStatusBooleanPair.second + if (isConsentOK) { logger.debug("Accepted MTB File (bwHC V1) for processing") requestProcessor.processMtbFile(mtbFile) } else { + logger.debug("Accepted MTB File (bwHC V1) and process deletion") val patientId = PatientId(mtbFile.patient.id) - requestProcessor.processDeletion(patientId) + requestProcessor.processDeletion(patientId, ttpConsentStatus) } return ResponseEntity.accepted().build() } - @PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] ) + private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> { + var ttpConsentStatus = iGetConsent.getTtpBroadConsentStatus(mtbFile.patient.id) + + val isConsentOK = + (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) || + ttpConsentStatus.equals( + TtpConsentStatus.BROAD_CONSENT_GIVEN + ) + if (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.REJECTED) { + // in case ttp check is disabled - we propagate rejected status anyway + ttpConsentStatus = TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED + } + return Pair(ttpConsentStatus, isConsentOK) + } + + @PostMapping(consumes = [CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE]) fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> { logger.debug("Accepted MTB File (DNPM V2) for processing") requestProcessor.processMtbFile(mtbFile) @@ -66,7 +87,7 @@ class MtbFileRestController( @DeleteMapping(path = ["{patientId}"]) fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> { logger.debug("Accepted patient ID to process deletion") - requestProcessor.processDeletion(PatientId(patientId)) + requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE) return ResponseEntity.accepted().build() } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt index b845e21..fe02b69 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt @@ -20,6 +20,7 @@ package dev.dnpm.etl.processor.monitoring +import dev.dnpm.etl.processor.config.GIcsConfigProperties import dev.dnpm.etl.processor.config.GPasConfigProperties import dev.dnpm.etl.processor.config.RestTargetProperties import jakarta.annotation.PostConstruct @@ -68,6 +69,12 @@ sealed class ConnectionCheckResult { override val timestamp: Instant, override val lastChange: Instant ) : ConnectionCheckResult() + + data class GIcsConnectionCheckResult( + override val available: Boolean, + override val timestamp: Instant, + override val lastChange: Instant + ) : ConnectionCheckResult() } class KafkaConnectionCheckService( @@ -207,4 +214,57 @@ class GPasConnectionCheckService( override fun connectionAvailable(): ConnectionCheckResult.GPasConnectionCheckResult { return this.result } +} + +class GIcsConnectionCheckService( + private val restTemplate: RestTemplate, + private val gIcsConfigProperties: GIcsConfigProperties, + @Qualifier("connectionCheckUpdateProducer") + private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> +) : ConnectionCheckService { + + private var result = ConnectionCheckResult.GIcsConnectionCheckResult(false, Instant.now(), Instant.now()) + + @PostConstruct + @Scheduled(cron = "0 * * * * *") + fun check() { + result = try { + + val uri = UriComponentsBuilder.fromUriString( + gIcsConfigProperties.uri.toString()).path("/metadata").build().toUri() + + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_JSON + if (!gIcsConfigProperties.username.isNullOrBlank() && !gIcsConfigProperties.password.isNullOrBlank()) { + headers.setBasicAuth(gIcsConfigProperties.username, gIcsConfigProperties.password) + } + + val available = restTemplate.exchange( + uri, + HttpMethod.GET, + HttpEntity<Void>(headers), + Void::class.java + ).statusCode == HttpStatus.OK + + ConnectionCheckResult.GIcsConnectionCheckResult( + available, + Instant.now(), + if (result.available == available) { result.lastChange } else { Instant.now() } + ) + } catch (_: Exception) { + ConnectionCheckResult.GIcsConnectionCheckResult( + false, + Instant.now(), + if (!result.available) { result.lastChange } else { Instant.now() } + ) + } + connectionCheckUpdateProducer.emitNext( + result, + Sinks.EmitFailureHandler.FAIL_FAST + ) + } + + override fun connectionAvailable(): ConnectionCheckResult.GIcsConnectionCheckResult { + return this.result + } }
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt index 8c19e86..0c8adb1 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt @@ -24,5 +24,6 @@ enum class RequestStatus(val value: String) { WARNING("warning"), ERROR("error"), UNKNOWN("unknown"), - DUPLICATION("duplication") + DUPLICATION("duplication"), + NO_CONSENT("no-consent") }
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt index c00b2fd..1f2743e 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt @@ -44,13 +44,20 @@ class KafkaMtbFileSender( return try { return retryTemplate.execute<MtbFileSender.Response, Exception> { val record = - ProducerRecord(kafkaProperties.outputTopic, key(request), objectMapper.writeValueAsString(request)) + ProducerRecord( + kafkaProperties.outputTopic, + key(request), + objectMapper.writeValueAsString(request) + ) when (request) { is BwhcV1MtbFileRequest -> record.headers() .add("contentType", MediaType.APPLICATION_JSON_VALUE.toByteArray()) is DnpmV2MtbFileRequest -> record.headers() - .add("contentType", CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray()) + .add( + "contentType", + CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray() + ) } val result = kafkaTemplate.send(record) @@ -84,7 +91,12 @@ class KafkaMtbFileSender( kafkaProperties.outputTopic, key(request), // Always use old BwhcV1FileRequest with Consent REJECT - objectMapper.writeValueAsString(BwhcV1MtbFileRequest(request.requestId, dummyMtbFile)) + objectMapper.writeValueAsString( + BwhcV1MtbFileRequest( + request.requestId, + dummyMtbFile + ) + ) ) val result = kafkaTemplate.send(record) diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt index 8d5a2cc..77f3399 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt @@ -21,7 +21,9 @@ package dev.dnpm.etl.processor.pseudonym import de.ukw.ccc.bwhc.dto.MtbFile import dev.dnpm.etl.processor.PatientId +import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent import dev.pcvolkmer.mv64e.mtb.Mtb +import dev.pcvolkmer.mv64e.mtb.MvhMetadata import org.apache.commons.codec.digest.DigestUtils /** Replaces patient ID with generated patient pseudonym @@ -289,6 +291,16 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { this.followUps?.forEach { it.patient.id = patientPseudonym } + + this.metadata?.researchConsents?.forEach { it -> + val entry = it ?: return@forEach + if (entry.contains("patient")) { + // here we expect only a patient reference any other data like display + // need to be removed, since may contain unsecure data + entry.remove("patient") + entry["patient"] = mapOf("reference" to "Patient/$patientPseudonym") + } + } } /** @@ -317,3 +329,23 @@ infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) { // TODO all other properties } + +fun Mtb.ensureMetaDataIsInitialized() { + // init metadata if necessary + if (this.metadata == null) { + val mvhMetadata = MvhMetadata.builder().build() + this.metadata = mvhMetadata + } + if (this.metadata.researchConsents == null) { + this.metadata.researchConsents = mutableListOf() + } + if (this.metadata.modelProjectConsent == null) { + this.metadata.modelProjectConsent = ModelProjectConsent() + this.metadata.modelProjectConsent.provisions = mutableListOf() + } else + if (this.metadata.modelProjectConsent.provisions != null) { + // make sure list can be changed + this.metadata.modelProjectConsent.provisions = + this.metadata.modelProjectConsent.provisions.toMutableList() + } +} diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt new file mode 100644 index 0000000..3841641 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt @@ -0,0 +1,282 @@ +package dev.dnpm.etl.processor.services + +import ca.uhn.fhir.context.FhirContext +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import dev.dnpm.etl.processor.config.AppConfigProperties +import dev.dnpm.etl.processor.config.GIcsConfigProperties +import dev.dnpm.etl.processor.consent.ConsentByMtbFile +import dev.dnpm.etl.processor.consent.ConsentDomain +import dev.dnpm.etl.processor.consent.IGetConsent +import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized +import dev.pcvolkmer.mv64e.mtb.* +import org.apache.commons.lang3.NotImplementedException +import org.hl7.fhir.r4.model.* +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.Consent.ConsentState +import org.hl7.fhir.r4.model.Consent.ProvisionComponent +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.io.IOException +import java.time.Clock +import java.time.Instant +import java.util.* + +@Service +class ConsentProcessor( + private val appConfigProperties: AppConfigProperties, + private val gIcsConfigProperties: GIcsConfigProperties, + private val objectMapper: ObjectMapper, + private val fhirContext: FhirContext, + private val consentService: IGetConsent +) { + private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor") + + /** + * In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked. + * + * Logik: + * * <c>true</c> IF consent check is disabled. + * * <c>true</c> IF broad consent (BC) has been given. + * * <c>true</c> BC has been asked AND declined but genomDe consent has been consented. + * * ELSE <c>false</c> is returned. + * + * @param mtbFile File v2 (will be enriched with consent data) + * @return true if consent is given + * + */ + fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean { + if (consentService is ConsentByMtbFile) { + // consent check is disabled + return true + } + + mtbFile.ensureMetaDataIsInitialized() + + val personIdentifierValue = mtbFile.patient.id + val requestDate = Date.from(Instant.now(Clock.systemUTC())) + + // 1. Broad consent Entry exists? + // 1.1. -> yes and research consent is given -> send mtb file + // 1.2. -> no -> return status error - consent has not been asked + // 2. -> Broad consent found but rejected -> is GenomDe consent provision 'sequencing' given? + // 2.1 -> yes -> send mtb file + // 2.2 -> no -> warn/info no consent given + + /* + * broad consent + */ + val broadConsent = consentService.getConsent( + personIdentifierValue, requestDate, ConsentDomain.BroadConsent + ) + val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty() + + // fast exit - if patient has not been asked, we can skip and exit + if (!broadConsentHasBeenAsked) return false + + val genomeDeConsent = consentService.getConsent( + personIdentifierValue, requestDate, ConsentDomain.Modelvorhaben64e + ) + + addGenomeDbProvisions(mtbFile, genomeDeConsent) + + if (!genomeDeConsent.entry.isEmpty()) setGenomDeSubmissionType(mtbFile) + + embedBroadConsentResources(mtbFile, broadConsent) + + val broadConsentStatus = getProvisionTypeByPolicyCode( + broadConsent, requestDate, ConsentDomain.BroadConsent + ) + + val genomDeSequencingStatus = getProvisionTypeByPolicyCode( + genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e + ) + + if (Consent.ConsentProvisionType.NULL == broadConsentStatus) { + // bc not asked + return false + } + if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus || Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus) return true + + return false + } + + fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) { + for (entry in broadConsent.getEntry()) { + val resource = entry.getResource() + if (resource is Consent) { + // since jackson convertValue does not work here, + // we need another step to back to string, before we convert to object map + val asJsonString = fhirContext.newJsonParser().encodeResourceToString(resource) + try { + val mapOfJson: HashMap<String?, Any?>? = + objectMapper.readValue<HashMap<String?, Any?>?>( + asJsonString, object : TypeReference<HashMap<String?, Any?>?>() {}) + mtbFile.metadata.researchConsents.add(mapOfJson) + } catch (e: JsonProcessingException) { + throw RuntimeException(e) + } + } + } + } + + fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) { + for (entry in consentGnomeDe.getEntry()) { + val resource = entry.getResource() + if (resource !is Consent) { + continue + } + + // We expect only one provision in collection, therefore get first or none + val provisions = resource.getProvision().getProvision() + if (provisions.isEmpty()) { + continue + } + + val provisionComponent: ProvisionComponent = provisions.first() + + var provisionCode: String? = null + if (provisionComponent.getCode() != null && !provisionComponent.getCode().isEmpty()) { + val codableConcept: CodeableConcept = provisionComponent.getCode().first() + if (codableConcept.getCoding() != null && !codableConcept.getCoding().isEmpty()) { + provisionCode = codableConcept.getCoding().first().getCode() + } + } + + if (provisionCode != null) { + try { + val modelProjectConsentPurpose = + ModelProjectConsentPurpose.forValue(provisionCode) + + if (ModelProjectConsentPurpose.SEQUENCING == modelProjectConsentPurpose) { + // CONVENTION: wrapping date is date of SEQUENCING consent + mtbFile.metadata.modelProjectConsent.date = resource.getDateTime() + } + + val provision = Provision.builder() + .type(ConsentProvision.valueOf(provisionComponent.getType().name)) + .date(provisionComponent.getPeriod().getStart()) + .purpose(modelProjectConsentPurpose).build() + + mtbFile.metadata.modelProjectConsent.provisions.add(provision) + } catch (ioe: IOException) { + logger.error( + "Provision code '$provisionCode' is unknown and cannot be mapped.", + ioe.toString() + ) + } + } + + if (!mtbFile.metadata.modelProjectConsent.provisions.isEmpty()) { + mtbFile.metadata.modelProjectConsent.version = + gIcsConfigProperties.genomeDeConsentVersion + } + } + } + + /** + * fixme: currently we do not have information about submission type + */ + private fun setGenomDeSubmissionType(mtbFile: Mtb) { + if (appConfigProperties.genomDeTestSubmission) { + + // fixme: remove INITIAL and uncomment when data model is updated + mtbFile.metadata.type = MvhSubmissionType.INITIAL + // mtbFile.metadata.type = MvhSubmissionType.Test + + logger.info("genomeDe submission mit TEST") + + } else { + mtbFile.metadata.type = MvhSubmissionType.INITIAL + } + } + + /** + * @param consentBundle consent resource + * @param requestDate date which must be within validation period of provision + * @return type of provision, will be [org.hl7.fhir.r4.model.Consent.ConsentProvisionType.NULL] if none is found. + */ + fun getProvisionTypeByPolicyCode( + consentBundle: Bundle, requestDate: Date?, consentDomain: ConsentDomain + ): Consent.ConsentProvisionType { + val code: String? + val system: String? + if (ConsentDomain.BroadConsent == consentDomain) { + code = gIcsConfigProperties.broadConsentPolicyCode + system = gIcsConfigProperties.broadConsentPolicySystem + } else if (ConsentDomain.Modelvorhaben64e == consentDomain) { + code = gIcsConfigProperties.genomeDePolicyCode + system = gIcsConfigProperties.genomeDePolicySystem + } else { + throw NotImplementedException("unknown consent domain " + consentDomain.name) + } + + val provisionTypeByPolicyCode = getProvisionTypeByPolicyCode( + consentBundle, code, system, requestDate + ) + return provisionTypeByPolicyCode + } + + /** + * @param consentBundle consent resource + * @param policyAndProvisionCode policyRule and provision code value + * @param policyAndProvisionSystem policyRule and provision system value + * @param requestDate date which must be within validation period of provision + * @return type of provision, will be [org.hl7.fhir.r4.model.Consent.ConsentProvisionType.NULL] if none is found. + */ + fun getProvisionTypeByPolicyCode( + consentBundle: Bundle, + policyAndProvisionCode: String?, + policyAndProvisionSystem: String?, + requestDate: Date? + ): Consent.ConsentProvisionType { + val entriesOfInterest = consentBundle.entry.filter { entry -> + entry.resource.isResource && entry.resource.resourceType == ResourceType.Consent && (entry.resource as Consent).status == ConsentState.ACTIVE && checkCoding( + policyAndProvisionCode, + policyAndProvisionSystem, + (entry.resource as Consent).policyRule.codingFirstRep + ) && isIsRequestDateInRange( + requestDate, (entry.resource as Consent).provision.period + ) + }.map { consentWithTargetPolicy: BundleEntryComponent -> + val provision = (consentWithTargetPolicy.getResource() as Consent).getProvision() + val provisionComponentByCode = + provision.getProvision().stream().filter { prov: ProvisionComponent? -> + checkCoding( + policyAndProvisionCode, + policyAndProvisionSystem, + prov!!.getCodeFirstRep().getCodingFirstRep() + ) && isIsRequestDateInRange( + requestDate, prov.getPeriod() + ) + }.findFirst() + if (provisionComponentByCode.isPresent) { + // actual provision we search for + return@map provisionComponentByCode.get().getType() + } else { + if (provision.type != null) return provision.type + + } + return Consent.ConsentProvisionType.NULL + }.firstOrNull() + + if (entriesOfInterest == null) return Consent.ConsentProvisionType.NULL + return entriesOfInterest + } + + fun checkCoding( + researchAllowedPolicyOid: String?, researchAllowedPolicySystem: String?, coding: Coding + ): Boolean { + return coding.getSystem() == researchAllowedPolicySystem && (coding.getCode() == researchAllowedPolicyOid) + } + + fun isIsRequestDateInRange(requestdate: Date?, provPeriod: Period): Boolean { + val isRequestDateAfterOrEqualStart = provPeriod.getStart().compareTo(requestdate) + val isRequestDateBeforeOrEqualEnd = provPeriod.getEnd().compareTo(requestdate) + return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0 + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt index f25452e..bb226c0 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import de.ukw.ccc.bwhc.dto.MtbFile import dev.dnpm.etl.processor.* import dev.dnpm.etl.processor.config.AppConfigProperties +import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.monitoring.Report import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.RequestStatus @@ -34,8 +35,11 @@ import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith import dev.pcvolkmer.mv64e.mtb.Mtb import org.apache.commons.codec.binary.Base32 import org.apache.commons.codec.digest.DigestUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service +import java.lang.RuntimeException import java.time.Instant import java.util.* @@ -47,9 +51,11 @@ class RequestProcessor( private val requestService: RequestService, private val objectMapper: ObjectMapper, private val applicationEventPublisher: ApplicationEventPublisher, - private val appConfigProperties: AppConfigProperties + private val appConfigProperties: AppConfigProperties, + private val consentProcessor: ConsentProcessor? ) { + private var logger: Logger = LoggerFactory.getLogger("RequestProcessor") fun processMtbFile(mtbFile: MtbFile) { processMtbFile(mtbFile, randomRequestId()) } @@ -66,12 +72,25 @@ class RequestProcessor( processMtbFile(mtbFile, randomRequestId()) } + fun processMtbFile(mtbFile: Mtb, requestId: RequestId) { - val pid = PatientId(mtbFile.patient.id) - mtbFile pseudonymizeWith pseudonymizeService - mtbFile anonymizeContentWith pseudonymizeService - val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile)) - saveAndSend(request, pid) + val pid = PatientId(extractPatientIdentifier(mtbFile)) + + val isConsentOk = consentProcessor != null && + consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null + if (isConsentOk) { + mtbFile pseudonymizeWith pseudonymizeService + mtbFile anonymizeContentWith pseudonymizeService + val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile)) + saveAndSend(request, pid) + } else { + logger.warn("consent check failed file will not be processed further!") + applicationEventPublisher.publishEvent( + ResponseEvent( + requestId, Instant.now(), RequestStatus.NO_CONSENT + ) + ) + } } private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) { @@ -89,9 +108,7 @@ class RequestProcessor( if (appConfigProperties.duplicationDetection && isDuplication(request)) { applicationEventPublisher.publishEvent( ResponseEvent( - request.requestId, - Instant.now(), - RequestStatus.DUPLICATION + request.requestId, Instant.now(), RequestStatus.DUPLICATION ) ) return @@ -120,21 +137,31 @@ class RequestProcessor( val lastMtbFileRequestForPatient = requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym) - val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym) + val isLastRequestDeletion = + requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym) - return null != lastMtbFileRequestForPatient - && !isLastRequestDeletion - && lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest) + return null != lastMtbFileRequestForPatient && !isLastRequestDeletion && lastMtbFileRequestForPatient.fingerprint == fingerprint( + pseudonymizedMtbFileRequest + ) } - fun processDeletion(patientId: PatientId) { - processDeletion(patientId, randomRequestId()) + fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) { + processDeletion(patientId, randomRequestId(), isConsented) } - fun processDeletion(patientId: PatientId, requestId: RequestId) { + fun processDeletion(patientId: PatientId, requestId: RequestId, isConsented: TtpConsentStatus) { try { val patientPseudonym = pseudonymizeService.patientPseudonym(patientId) + val requestStatus: RequestStatus = when (isConsented) { + TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, TtpConsentStatus.BROAD_CONSENT_MISSING, TtpConsentStatus.BROAD_CONSENT_REJECTED -> RequestStatus.NO_CONSENT + TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR + TtpConsentStatus.BROAD_CONSENT_GIVEN, TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN + TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, TtpConsentStatus.GENOM_DE_CONSENT_MISSING, TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED -> { + throw RuntimeException("processDelete should never deal with '" + isConsented.name + "' consent status. This is a bug and need to be fixed!") + } + } + requestService.save( Request( requestId, @@ -142,7 +169,7 @@ class RequestProcessor( patientId, fingerprint(patientPseudonym.value), RequestType.DELETE, - RequestStatus.UNKNOWN + requestStatus ) ) @@ -150,17 +177,14 @@ class RequestProcessor( applicationEventPublisher.publishEvent( ResponseEvent( - requestId, - Instant.now(), - responseStatus.status, - when (responseStatus.status) { + requestId, Instant.now(), responseStatus.status, when (responseStatus.status) { RequestStatus.WARNING, RequestStatus.ERROR -> Optional.of(responseStatus.body) else -> Optional.empty() } ) ) - } catch (e: Exception) { + } catch (_: Exception) { requestService.save( Request( uuid = requestId, @@ -184,10 +208,10 @@ class RequestProcessor( private fun fingerprint(s: String): Fingerprint { return Fingerprint( - Base32().encodeAsString(DigestUtils.sha256(s)) - .replace("=", "") - .lowercase() + Base32().encodeAsString(DigestUtils.sha256(s)).replace("=", "").lowercase() ) } } + +private fun extractPatientIdentifier(mtbFile: Mtb): String = mtbFile.patient.id diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt index ecb2ec7..fb82647 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt @@ -70,6 +70,12 @@ class ResponseProcessor( ) } + RequestStatus.NO_CONSENT -> { + it.report = Report( + "Einwilligung Status fehlt, widerrufen oder ungeklärt." + ) + } + else -> { logger.error("Cannot process response: Unknown response!") return@ifPresentOrElse diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt index 25ec7cc..ea89e98 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt @@ -19,10 +19,7 @@ package dev.dnpm.etl.processor.web -import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult -import dev.dnpm.etl.processor.monitoring.ConnectionCheckService -import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService -import dev.dnpm.etl.processor.monitoring.OutputConnectionCheckService +import dev.dnpm.etl.processor.monitoring.* import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.pseudonym.Generator import dev.dnpm.etl.processor.security.Role @@ -61,11 +58,15 @@ class ConfigController( val gPasConnectionAvailable = connectionCheckServices.filterIsInstance<GPasConnectionCheckService>().firstOrNull()?.connectionAvailable() + val gIcsConnectionAvailable = + connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable() + model.addAttribute("pseudonymGenerator", pseudonymGenerator.javaClass.simpleName) model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName) model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint()) model.addAttribute("outputConnectionAvailable", outputConnectionAvailable) model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable) + model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable) model.addAttribute("tokensEnabled", tokenService != null) if (tokenService != null) { model.addAttribute("tokens", tokenService.findAll()) @@ -119,6 +120,24 @@ class ConfigController( return "configs/gPasConnectionAvailable" } + @GetMapping(params = ["gIcsConnectionAvailable"]) + fun gIcsConnectionAvailable(model: Model): String { + val gIcsConnectionAvailable = + connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable() + + model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName) + model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint()) + model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable) + if (tokenService != null) { + model.addAttribute("tokensEnabled", true) + model.addAttribute("tokens", tokenService.findAll()) + } else { + model.addAttribute("tokens", listOf<Token>()) + } + + return "configs/gIcsConnectionAvailable" + } + @PostMapping(path = ["tokens"]) fun addToken(@ModelAttribute("name") name: String, model: Model): String { if (tokenService == null) { @@ -190,6 +209,7 @@ class ConfigController( is ConnectionCheckResult.KafkaConnectionCheckResult -> "output-connection-check" is ConnectionCheckResult.RestConnectionCheckResult -> "output-connection-check" is ConnectionCheckResult.GPasConnectionCheckResult -> "gpas-connection-check" + is ConnectionCheckResult.GIcsConnectionCheckResult -> "gics-connection-check" } ServerSentEvent.builder<Any>() |
