summaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorPaul-Christian Volkmer2025-11-11 09:36:17 +0100
committerGitHub2025-11-11 09:36:17 +0100
commit8c3143081f5f535671bc7b0c7d98302549b9a1da (patch)
tree1402d033ef7b13ab6befddf2b8d1f385f79d410b /src/main/kotlin
parent76a16e22bb9fba21d02b0584ec83e046196da045 (diff)
chore: use spotless for kotlin code (#191)
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/Exceptions.kt2
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/ServletInitializer.kt7
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt74
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt435
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppJdbcConfiguration.kt14
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt34
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppRestConfiguration.kt20
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt259
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt9
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt3
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt26
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt35
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt36
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt52
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt428
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/ReportService.kt92
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt133
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestType.kt6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt39
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/MtbFileSender.kt18
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSender.kt16
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt41
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt17
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt14
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt10
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeService.kt22
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt456
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/security/TokenService.kt73
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt19
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/security/UserRoleService.kt21
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt465
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt303
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt66
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt75
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt73
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt86
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/types.kt29
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/ApplicationControllerAdvice.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt336
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt21
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt11
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsController.kt4
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt210
47 files changed, 2055 insertions, 2073 deletions
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/Exceptions.kt b/src/main/kotlin/dev/dnpm/etl/processor/Exceptions.kt
index 32d0954..1c590fc 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/Exceptions.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/Exceptions.kt
@@ -19,4 +19,4 @@
package dev.dnpm.etl.processor
-class NotFoundException : RuntimeException() \ No newline at end of file
+class NotFoundException : RuntimeException()
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/ServletInitializer.kt b/src/main/kotlin/dev/dnpm/etl/processor/ServletInitializer.kt
index 2618b09..e35cddf 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/ServletInitializer.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/ServletInitializer.kt
@@ -23,9 +23,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
class ServletInitializer : SpringBootServletInitializer() {
-
- override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
- return application.sources(EtlProcessorApplication::class.java)
- }
-
+ override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder =
+ application.sources(EtlProcessorApplication::class.java)
}
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 57ab06c..8786c34 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
@@ -28,7 +28,7 @@ data class AppConfigProperties(
var transformations: List<TransformationProperties> = listOf(),
var maxRetryAttempts: Int = 3,
var duplicationDetection: Boolean = true,
- var genomDeTestSubmission: Boolean = false
+ var genomDeTestSubmission: Boolean = false,
) {
companion object {
const val NAME = "app"
@@ -49,8 +49,7 @@ data class PseudonymizeConfigProperties(
data class GPasConfigProperties(
val uri: String?,
val soapEndpoint: String?,
- @get:DeprecatedConfigurationProperty(since = "0.12")
- val pidDomain: String?,
+ @get:DeprecatedConfigurationProperty(since = "0.12") val pidDomain: String?,
val patientDomain: String = pidDomain ?: "etl-processor",
val genomDeTanDomain: String = "ccdn",
val username: String?,
@@ -63,7 +62,7 @@ data class GPasConfigProperties(
@ConfigurationProperties(ConsentConfigProperties.NAME)
data class ConsentConfigProperties(
- var service: ConsentService = ConsentService.NONE
+ var service: ConsentService = ConsentService.NONE,
) {
companion object {
const val NAME = "app.consent"
@@ -72,60 +71,33 @@ data class ConsentConfigProperties(
@ConfigurationProperties(GIcsConfigProperties.NAME)
data class GIcsConfigProperties(
- /**
- * Base URL to gICS System
- *
- */
+ /** Base URL to gICS System */
val uri: String?,
val username: String? = null,
val password: String? = null,
-
/**
* gICS specific system
- * **/
+ * *
+ */
val personIdentifierSystem: String =
"https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID",
-
- /**
- * Domain of broad consent resources
- **/
+ /** Domain of broad consent resources */
val broadConsentDomainName: String = "MII",
-
- /**
- * Domain of Modelvorhaben 64e consent resources
- **/
+ /** Domain of Modelvorhaben 64e consent resources */
val genomDeConsentDomainName: String = "GenomDE_MV",
-
- /**
- * Value to expect in case of positiv consent
- */
+ /** 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
- */
+ /** 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",
-
- /**
- * Consent Policy uri for MII Broad Consent Version
- */
+ /** Consent Policy uri for MII Broad Consent Version */
val broadConsentPolicyUri: String = "urn:oid:2.16.840.1.113883.3.1937.777.24.2.1790",
-
- /**
- * Value to expect in case of positiv consent
- */
+ /** 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"
+ /** 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"
@@ -136,7 +108,7 @@ data class GIcsConfigProperties(
data class RestTargetProperties(
val uri: String?,
val username: String?,
- val password: String?
+ val password: String?,
) {
companion object {
const val NAME = "app.rest"
@@ -149,7 +121,7 @@ data class KafkaProperties(
val outputTopic: String = "etl-processor",
val outputResponseTopic: String = "${outputTopic}_response",
val groupId: String = "${outputTopic}_group",
- val servers: String = ""
+ val servers: String = "",
) {
companion object {
const val NAME = "app.kafka"
@@ -162,7 +134,7 @@ data class SecurityConfigProperties(
val adminPassword: String?,
val enableTokens: Boolean = false,
val enableOidc: Boolean = false,
- val defaultNewUserRole: Role = Role.USER
+ val defaultNewUserRole: Role = Role.USER,
) {
companion object {
const val NAME = "app.security"
@@ -171,16 +143,16 @@ data class SecurityConfigProperties(
enum class PseudonymGenerator {
BUILDIN,
- GPAS
+ GPAS,
}
enum class ConsentService {
NONE,
- GICS
+ GICS,
}
data class TransformationProperties(
val path: String,
val from: String,
- val to: String
+ val to: String,
)
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 de302fd..36b0a75 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
@@ -30,6 +30,8 @@ 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 kotlin.time.Duration.Companion.seconds
+import kotlin.time.toJavaDuration
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition
@@ -53,258 +55,251 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.web.client.HttpClientErrorException
import org.springframework.web.client.RestTemplate
import reactor.core.publisher.Sinks
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.toJavaDuration
-
@Configuration
@EnableConfigurationProperties(
- value = [
- AppConfigProperties::class,
- PseudonymizeConfigProperties::class,
- GPasConfigProperties::class,
- ConsentConfigProperties::class,
- GIcsConfigProperties::class
- ]
+ value =
+ [
+ AppConfigProperties::class,
+ PseudonymizeConfigProperties::class,
+ GPasConfigProperties::class,
+ ConsentConfigProperties::class,
+ GIcsConfigProperties::class,
+ ]
)
@EnableScheduling
class AppConfiguration {
- private val logger = LoggerFactory.getLogger(AppConfiguration::class.java)
+ private val logger = LoggerFactory.getLogger(AppConfiguration::class.java)
- @Bean
- fun restTemplate(): RestTemplate {
- return RestTemplate()
- }
+ @Bean
+ fun restTemplate(): RestTemplate {
+ return RestTemplate()
+ }
- @Bean
- fun appFhirConfig(): AppFhirConfig {
- return AppFhirConfig()
- }
+ @Bean
+ fun appFhirConfig(): AppFhirConfig {
+ return AppFhirConfig()
+ }
- @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
- @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
- @Bean
- fun gpasSoapProxyFactoryBean(gpasConfigProperties: GPasConfigProperties): JaxWsProxyFactoryBean {
- val proxyFactory = JaxWsProxyFactoryBean()
- proxyFactory.serviceClass = GpasSoapService::class.java
- proxyFactory.address = gpasConfigProperties.soapEndpoint
- return proxyFactory
- }
+ @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
+ @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
+ @Bean
+ fun gpasSoapProxyFactoryBean(gpasConfigProperties: GPasConfigProperties): JaxWsProxyFactoryBean {
+ val proxyFactory = JaxWsProxyFactoryBean()
+ proxyFactory.serviceClass = GpasSoapService::class.java
+ proxyFactory.address = gpasConfigProperties.soapEndpoint
+ return proxyFactory
+ }
- @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
- @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
- @Bean
- fun gpasSoapProxy(gpasConfigProperties: GPasConfigProperties): GpasSoapService {
- return gpasSoapProxyFactoryBean(gpasConfigProperties).create() as GpasSoapService
- }
+ @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
+ @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
+ @Bean
+ fun gpasSoapProxy(gpasConfigProperties: GPasConfigProperties): GpasSoapService {
+ return gpasSoapProxyFactoryBean(gpasConfigProperties).create() as GpasSoapService
+ }
- @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
- @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
- @Bean
- fun gpasSoapPseudonymGenerator(
- configProperties: GPasConfigProperties,
- retryTemplate: RetryTemplate,
- gpasSoapService: GpasSoapService,
- appFhirConfig: AppFhirConfig
- ): Generator {
- logger.info("Selected 'GpasSoapPseudonym Generator'")
- return GpasSoapPseudonymGenerator(configProperties, retryTemplate, gpasSoapService, appFhirConfig)
- }
-
- @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
- @ConditionalOnProperty(value = ["app.pseudonymize.gpas.uri"])
- @Bean
- fun gpasPseudonymGenerator(
- configProperties: GPasConfigProperties,
- retryTemplate: RetryTemplate,
- restTemplate: RestTemplate,
- appFhirConfig: AppFhirConfig
- ): Generator {
- logger.info("Selected 'GpasPseudonym Generator'")
- return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig)
- }
-
- @ConditionalOnProperty(
- value = ["app.pseudonymize.generator"],
- havingValue = "BUILDIN",
- matchIfMissing = true
+ @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
+ @ConditionalOnProperty(value = ["app.pseudonymize.gpas.soap-endpoint"])
+ @Bean
+ fun gpasSoapPseudonymGenerator(
+ configProperties: GPasConfigProperties,
+ retryTemplate: RetryTemplate,
+ gpasSoapService: GpasSoapService,
+ appFhirConfig: AppFhirConfig,
+ ): Generator {
+ logger.info("Selected 'GpasSoapPseudonym Generator'")
+ return GpasSoapPseudonymGenerator(
+ configProperties,
+ retryTemplate,
+ gpasSoapService,
+ appFhirConfig,
)
- @Bean
- fun buildinPseudonymGenerator(): Generator {
- logger.info("Selected 'BUILDIN Pseudonym Generator'")
- return AnonymizingGenerator()
- }
+ }
- @Bean
- fun pseudonymizeService(
- generator: Generator,
- pseudonymizeConfigProperties: PseudonymizeConfigProperties
- ): PseudonymizeService {
- return PseudonymizeService(generator, pseudonymizeConfigProperties)
- }
+ @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
+ @ConditionalOnProperty(value = ["app.pseudonymize.gpas.uri"])
+ @Bean
+ fun gpasPseudonymGenerator(
+ configProperties: GPasConfigProperties,
+ retryTemplate: RetryTemplate,
+ restTemplate: RestTemplate,
+ appFhirConfig: AppFhirConfig,
+ ): Generator {
+ logger.info("Selected 'GpasPseudonym Generator'")
+ return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig)
+ }
- @Bean
- fun reportService(): ReportService {
- return ReportService(getObjectMapper())
- }
+ @ConditionalOnProperty(
+ value = ["app.pseudonymize.generator"],
+ havingValue = "BUILDIN",
+ matchIfMissing = true,
+ )
+ @Bean
+ fun buildinPseudonymGenerator(): Generator {
+ logger.info("Selected 'BUILDIN Pseudonym Generator'")
+ return AnonymizingGenerator()
+ }
- @Bean
- fun getObjectMapper(): ObjectMapper {
- return JacksonConfig().objectMapper()
- }
+ @Bean
+ fun pseudonymizeService(
+ generator: Generator,
+ pseudonymizeConfigProperties: PseudonymizeConfigProperties,
+ ): PseudonymizeService {
+ return PseudonymizeService(generator, pseudonymizeConfigProperties)
+ }
- @Bean
- fun transformationService(
- configProperties: AppConfigProperties
- ): TransformationService {
- logger.info("Apply ${configProperties.transformations.size} transformation rules")
- return TransformationService(getObjectMapper(), configProperties.transformations.map {
- Transformation.of(it.path) from it.from to it.to
- })
- }
+ @Bean
+ fun reportService(): ReportService {
+ return ReportService(getObjectMapper())
+ }
- @Bean
- fun retryTemplate(configProperties: AppConfigProperties): RetryTemplate {
- return RetryTemplateBuilder()
- .notRetryOn(IllegalArgumentException::class.java)
- .notRetryOn(HttpClientErrorException.BadRequest::class.java)
- .notRetryOn(HttpClientErrorException.UnprocessableEntity::class.java)
- .exponentialBackoff(2.seconds.toJavaDuration(), 1.25, 5.seconds.toJavaDuration())
- .customPolicy(SimpleRetryPolicy(configProperties.maxRetryAttempts))
- .withListener(object : RetryListener {
- override fun <T : Any, E : Throwable> onError(
- context: RetryContext,
- callback: RetryCallback<T, E>,
- throwable: Throwable
- ) {
- logger.warn(
- "Error occured: {}. Retrying {}",
- throwable.message,
- context.retryCount
- )
- }
- })
- .build()
- }
+ @Bean
+ fun getObjectMapper(): ObjectMapper {
+ return JacksonConfig().objectMapper()
+ }
- @ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true")
- @Bean
- fun tokenService(
- userDetailsManager: InMemoryUserDetailsManager,
- passwordEncoder: PasswordEncoder,
- tokenRepository: TokenRepository
- ): TokenService {
- return TokenService(userDetailsManager, passwordEncoder, tokenRepository)
- }
+ @Bean
+ fun transformationService(configProperties: AppConfigProperties): TransformationService {
+ logger.info("Apply ${configProperties.transformations.size} transformation rules")
+ return TransformationService(
+ getObjectMapper(),
+ configProperties.transformations.map { Transformation.of(it.path) from it.from to it.to },
+ )
+ }
- @Bean
- fun statisticsUpdateProducer(): Sinks.Many<Any> {
- return Sinks.many().multicast().directBestEffort()
- }
+ @Bean
+ fun retryTemplate(configProperties: AppConfigProperties): RetryTemplate {
+ return RetryTemplateBuilder()
+ .notRetryOn(IllegalArgumentException::class.java)
+ .notRetryOn(HttpClientErrorException.BadRequest::class.java)
+ .notRetryOn(HttpClientErrorException.UnprocessableEntity::class.java)
+ .exponentialBackoff(2.seconds.toJavaDuration(), 1.25, 5.seconds.toJavaDuration())
+ .customPolicy(SimpleRetryPolicy(configProperties.maxRetryAttempts))
+ .withListener(
+ object : RetryListener {
+ override fun <T : Any, E : Throwable> onError(
+ context: RetryContext,
+ callback: RetryCallback<T, E>,
+ throwable: Throwable,
+ ) {
+ logger.warn("Error occured: {}. Retrying {}", throwable.message, context.retryCount)
+ }
+ }
+ )
+ .build()
+ }
- @Bean
- fun connectionCheckUpdateProducer(): Sinks.Many<ConnectionCheckResult> {
- return Sinks.many().multicast().onBackpressureBuffer()
- }
+ @ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true")
+ @Bean
+ fun tokenService(
+ userDetailsManager: InMemoryUserDetailsManager,
+ passwordEncoder: PasswordEncoder,
+ tokenRepository: TokenRepository,
+ ): TokenService {
+ return TokenService(userDetailsManager, passwordEncoder, tokenRepository)
+ }
- @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
- @Bean
- fun gPasConnectionCheckService(
- restTemplate: RestTemplate,
- gPasConfigProperties: GPasConfigProperties,
- connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
- ): ConnectionCheckService {
- return GPasConnectionCheckService(
- restTemplate,
- gPasConfigProperties,
- connectionCheckUpdateProducer
- )
- }
+ @Bean
+ fun statisticsUpdateProducer(): Sinks.Many<Any> {
+ return Sinks.many().multicast().directBestEffort()
+ }
- @ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS")
- @ConditionalOnMissingBean
- @Bean
- fun gPasConnectionCheckServiceOnDeprecatedProperty(
- restTemplate: RestTemplate,
- gPasConfigProperties: GPasConfigProperties,
- connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
- ): ConnectionCheckService {
- return GPasConnectionCheckService(
- restTemplate,
- gPasConfigProperties,
- connectionCheckUpdateProducer
- )
- }
+ @Bean
+ fun connectionCheckUpdateProducer(): Sinks.Many<ConnectionCheckResult> {
+ return Sinks.many().multicast().onBackpressureBuffer()
+ }
- @Bean
- fun jdbcConfiguration(): AbstractJdbcConfiguration {
- return AppJdbcConfiguration()
- }
+ @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
+ @Bean
+ fun gPasConnectionCheckService(
+ restTemplate: RestTemplate,
+ gPasConfigProperties: GPasConfigProperties,
+ connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
+ ): ConnectionCheckService {
+ return GPasConnectionCheckService(
+ restTemplate,
+ gPasConfigProperties,
+ connectionCheckUpdateProducer,
+ )
+ }
- @Conditional(GicsEnabledCondition::class)
- @Bean
- fun gicsConsentService(
- gIcsConfigProperties: GIcsConfigProperties,
- retryTemplate: RetryTemplate,
- restTemplate: RestTemplate,
- appFhirConfig: AppFhirConfig
- ): IConsentService {
- return GicsConsentService(
- gIcsConfigProperties,
- retryTemplate,
- restTemplate,
- appFhirConfig
- )
- }
+ @ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS")
+ @ConditionalOnMissingBean
+ @Bean
+ fun gPasConnectionCheckServiceOnDeprecatedProperty(
+ restTemplate: RestTemplate,
+ gPasConfigProperties: GPasConfigProperties,
+ connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
+ ): ConnectionCheckService {
+ return GPasConnectionCheckService(
+ restTemplate,
+ gPasConfigProperties,
+ connectionCheckUpdateProducer,
+ )
+ }
- @Conditional(GicsEnabledCondition::class)
- @Bean
- fun consentProcessor(
- configProperties: AppConfigProperties,
- gIcsConfigProperties: GIcsConfigProperties,
- getObjectMapper: ObjectMapper,
- appFhirConfig: AppFhirConfig,
- gicsConsentService: IConsentService
- ): ConsentProcessor {
- return ConsentProcessor(
- configProperties,
- gIcsConfigProperties,
- getObjectMapper,
- appFhirConfig.fhirContext(),
- gicsConsentService
- )
- }
+ @Bean
+ fun jdbcConfiguration(): AbstractJdbcConfiguration {
+ return AppJdbcConfiguration()
+ }
- @Conditional(GicsEnabledCondition::class)
- @Bean
- fun gIcsConnectionCheckService(
- restTemplate: RestTemplate,
- gIcsConfigProperties: GIcsConfigProperties,
- connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
- ): ConnectionCheckService {
- return GIcsConnectionCheckService(
- restTemplate,
- gIcsConfigProperties,
- connectionCheckUpdateProducer
- )
- }
+ @Conditional(GicsEnabledCondition::class)
+ @Bean
+ fun gicsConsentService(
+ gIcsConfigProperties: GIcsConfigProperties,
+ retryTemplate: RetryTemplate,
+ restTemplate: RestTemplate,
+ appFhirConfig: AppFhirConfig,
+ ): IConsentService {
+ return GicsConsentService(gIcsConfigProperties, retryTemplate, restTemplate, appFhirConfig)
+ }
- @Bean
- @ConditionalOnMissingBean
- fun iGetConsentService(): IConsentService {
- return MtbFileConsentService()
- }
+ @Conditional(GicsEnabledCondition::class)
+ @Bean
+ fun consentProcessor(
+ configProperties: AppConfigProperties,
+ gIcsConfigProperties: GIcsConfigProperties,
+ getObjectMapper: ObjectMapper,
+ appFhirConfig: AppFhirConfig,
+ gicsConsentService: IConsentService,
+ ): 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(): IConsentService {
+ return MtbFileConsentService()
+ }
}
class GicsEnabledCondition :
AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) {
- @ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
- @ConditionalOnProperty(name = ["app.consent.gics.uri"])
- class OnGicsServiceSelected {
- // Just for Condition
- }
-
+ @ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
+ @ConditionalOnProperty(name = ["app.consent.gics.uri"])
+ class OnGicsServiceSelected {
+ // 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
index 2b5ff8f..052822e 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppFhirConfig.kt
@@ -4,13 +4,9 @@ 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
+ @Bean fun fhirContext(): FhirContext = fhirCtx
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppJdbcConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppJdbcConfiguration.kt
index 898982c..769faf3 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppJdbcConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppJdbcConfiguration.kt
@@ -7,19 +7,13 @@ import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration
@Configuration
class AppJdbcConfiguration : AbstractJdbcConfiguration() {
- override fun userConverters(): MutableList<*> {
- return mutableListOf(StringToFingerprintConverter(), FingerprintToStringConverter())
- }
+ override fun userConverters(): MutableList<*> = mutableListOf(StringToFingerprintConverter(), FingerprintToStringConverter())
}
class StringToFingerprintConverter : Converter<String, Fingerprint> {
- override fun convert(source: String): Fingerprint {
- return Fingerprint(source)
- }
+ override fun convert(source: String): Fingerprint = Fingerprint(source)
}
class FingerprintToStringConverter : Converter<Fingerprint, String> {
- override fun convert(source: Fingerprint): String {
- return source.value
- }
-} \ No newline at end of file
+ override fun convert(source: Fingerprint): String = source.value
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
index 6551713..2f89dea 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
@@ -45,14 +45,11 @@ import org.springframework.retry.support.RetryTemplate
import reactor.core.publisher.Sinks
@Configuration
-@EnableConfigurationProperties(
- value = [KafkaProperties::class]
-)
+@EnableConfigurationProperties(value = [KafkaProperties::class])
@ConditionalOnProperty(value = ["app.kafka.servers"])
@ConditionalOnMissingBean(MtbFileSender::class)
@Order(-5)
class AppKafkaConfiguration {
-
private val logger = LoggerFactory.getLogger(AppKafkaConfiguration::class.java)
@Bean
@@ -60,7 +57,7 @@ class AppKafkaConfiguration {
kafkaTemplate: KafkaTemplate<String, String>,
kafkaProperties: KafkaProperties,
retryTemplate: RetryTemplate,
- objectMapper: ObjectMapper
+ objectMapper: ObjectMapper,
): MtbFileSender {
logger.info("Selected 'KafkaMtbFileSender'")
return KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, objectMapper)
@@ -70,7 +67,7 @@ class AppKafkaConfiguration {
fun kafkaResponseListenerContainer(
consumerFactory: ConsumerFactory<String, String>,
kafkaProperties: KafkaProperties,
- kafkaResponseProcessor: KafkaResponseProcessor
+ kafkaResponseProcessor: KafkaResponseProcessor,
): KafkaMessageListenerContainer<String, String> {
val containerProperties = ContainerProperties(kafkaProperties.outputResponseTopic)
containerProperties.messageListener = kafkaResponseProcessor
@@ -80,17 +77,15 @@ class AppKafkaConfiguration {
@Bean
fun kafkaResponseProcessor(
applicationEventPublisher: ApplicationEventPublisher,
- objectMapper: ObjectMapper
- ): KafkaResponseProcessor {
- return KafkaResponseProcessor(applicationEventPublisher, objectMapper)
- }
+ objectMapper: ObjectMapper,
+ ): KafkaResponseProcessor = KafkaResponseProcessor(applicationEventPublisher, objectMapper)
@Bean
@ConditionalOnProperty(value = ["app.kafka.input-topic"])
fun kafkaInputListenerContainer(
consumerFactory: ConsumerFactory<String, String>,
kafkaProperties: KafkaProperties,
- kafkaInputListener: KafkaInputListener
+ kafkaInputListener: KafkaInputListener,
): KafkaMessageListenerContainer<String, String> {
val containerProperties = ContainerProperties(kafkaProperties.inputTopic)
containerProperties.messageListener = kafkaInputListener
@@ -102,17 +97,16 @@ class AppKafkaConfiguration {
fun kafkaInputListener(
requestProcessor: RequestProcessor,
objectMapper: ObjectMapper,
- consentEvaluator: ConsentEvaluator
- ): KafkaInputListener {
- return KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
- }
+ consentEvaluator: ConsentEvaluator,
+ ): KafkaInputListener = KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
@Bean
fun kafkaConnectionCheckService(
consumerFactory: ConsumerFactory<String, String>,
- connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
- ): ConnectionCheckService {
- return KafkaConnectionCheckService(consumerFactory.createConsumer(), connectionCheckUpdateProducer)
- }
-
+ connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
+ ): ConnectionCheckService =
+ KafkaConnectionCheckService(
+ consumerFactory.createConsumer(),
+ connectionCheckUpdateProducer,
+ )
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppRestConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppRestConfiguration.kt
index 62c25bc..565209e 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppRestConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppRestConfiguration.kt
@@ -37,16 +37,11 @@ import org.springframework.web.client.RestTemplate
import reactor.core.publisher.Sinks
@Configuration
-@EnableConfigurationProperties(
- value = [
- RestTargetProperties::class
- ]
-)
+@EnableConfigurationProperties(value = [RestTargetProperties::class])
@ConditionalOnProperty(value = ["app.rest.uri"])
@ConditionalOnMissingBean(MtbFileSender::class)
@Order(-10)
class AppRestConfiguration {
-
private val logger = LoggerFactory.getLogger(AppRestConfiguration::class.java)
@Bean
@@ -64,10 +59,11 @@ class AppRestConfiguration {
fun restConnectionCheckService(
restTemplate: RestTemplate,
restTargetProperties: RestTargetProperties,
- connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
- ): ConnectionCheckService {
- return RestConnectionCheckService(restTemplate, restTargetProperties, connectionCheckUpdateProducer)
- }
-
+ connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
+ ): ConnectionCheckService =
+ RestConnectionCheckService(
+ restTemplate,
+ restTargetProperties,
+ connectionCheckUpdateProducer,
+ )
}
-
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt
index b2bd044..d44303b 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt
@@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.config
import dev.dnpm.etl.processor.security.UserRole
import dev.dnpm.etl.processor.security.UserRoleRepository
import dev.dnpm.etl.processor.security.UserRoleService
+import java.util.*
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
@@ -41,158 +42,146 @@ import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
-import java.util.*
-
private const val LOGIN_PATH = "/login"
@Configuration
-@EnableConfigurationProperties(
- value = [
- SecurityConfigProperties::class
- ]
-)
+@EnableConfigurationProperties(value = [SecurityConfigProperties::class])
@ConditionalOnProperty(value = ["app.security.admin-user"])
@EnableWebSecurity
-class AppSecurityConfiguration(
- private val securityConfigProperties: SecurityConfigProperties
-) {
+class AppSecurityConfiguration(private val securityConfigProperties: SecurityConfigProperties) {
- private val logger = LoggerFactory.getLogger(AppSecurityConfiguration::class.java)
+ private val logger = LoggerFactory.getLogger(AppSecurityConfiguration::class.java)
- @Bean
- fun userDetailsService(passwordEncoder: PasswordEncoder): InMemoryUserDetailsManager {
- val adminUser = if (securityConfigProperties.adminUser.isNullOrBlank()) {
- logger.warn("Using random Admin User: admin")
- "admin"
+ @Bean
+ fun userDetailsService(passwordEncoder: PasswordEncoder): InMemoryUserDetailsManager {
+ val adminUser =
+ if (securityConfigProperties.adminUser.isNullOrBlank()) {
+ logger.warn("Using random Admin User: admin")
+ "admin"
} else {
- securityConfigProperties.adminUser
+ securityConfigProperties.adminUser
}
- val adminPassword = if (securityConfigProperties.adminPassword.isNullOrBlank()) {
- val random = UUID.randomUUID().toString()
- logger.warn("Using random Admin Passwort: {}", random)
- passwordEncoder.encode(random)
+ val adminPassword =
+ if (securityConfigProperties.adminPassword.isNullOrBlank()) {
+ val random = UUID.randomUUID().toString()
+ logger.warn("Using random Admin Passwort: {}", random)
+ passwordEncoder.encode(random)
} else {
- securityConfigProperties.adminPassword
+ securityConfigProperties.adminPassword
}
- val user: UserDetails = User.withUsername(adminUser)
- .password(adminPassword)
- .roles("ADMIN")
- .build()
-
- return InMemoryUserDetailsManager(user)
- }
-
- @Bean
- @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
- fun filterChainOidc(
- http: HttpSecurity,
- passwordEncoder: PasswordEncoder,
- userRoleRepository: UserRoleRepository,
- sessionRegistry: SessionRegistry
- ): SecurityFilterChain {
- http {
- authorizeHttpRequests {
- authorize("/configs/**", hasRole("ADMIN"))
- authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER"))
- authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER"))
- authorize("/report/**", hasAnyRole("ADMIN", "USER"))
- authorize("*.css", permitAll)
- authorize("*.ico", permitAll)
- authorize("*.jpeg", permitAll)
- authorize("*.js", permitAll)
- authorize("*.svg", permitAll)
- authorize("*.css", permitAll)
- authorize("/login/**", permitAll)
- authorize(anyRequest, permitAll)
- }
- httpBasic {
- realmName = "ETL-Processor"
- }
- formLogin {
- loginPage = LOGIN_PATH
- }
- oauth2Login {
- loginPage = LOGIN_PATH
- }
- sessionManagement {
- sessionConcurrency {
- maximumSessions = 1
- expiredUrl = "$LOGIN_PATH?expired"
- }
- sessionFixation {
- newSession()
- }
- }
- csrf { disable() }
+ val user: UserDetails =
+ User.withUsername(adminUser).password(adminPassword).roles("ADMIN").build()
+
+ return InMemoryUserDetailsManager(user)
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
+ fun filterChainOidc(
+ http: HttpSecurity,
+ passwordEncoder: PasswordEncoder,
+ userRoleRepository: UserRoleRepository,
+ sessionRegistry: SessionRegistry,
+ ): SecurityFilterChain {
+ http {
+ authorizeHttpRequests {
+ authorize("/configs/**", hasRole("ADMIN"))
+ authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER"))
+ authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER"))
+ authorize("/report/**", hasAnyRole("ADMIN", "USER"))
+ authorize("*.css", permitAll)
+ authorize("*.ico", permitAll)
+ authorize("*.jpeg", permitAll)
+ authorize("*.js", permitAll)
+ authorize("*.svg", permitAll)
+ authorize("*.css", permitAll)
+ authorize("/login/**", permitAll)
+ authorize(anyRequest, permitAll)
+ }
+ httpBasic { realmName = "ETL-Processor" }
+ formLogin { loginPage = LOGIN_PATH }
+ oauth2Login { loginPage = LOGIN_PATH }
+ sessionManagement {
+ sessionConcurrency {
+ maximumSessions = 1
+ expiredUrl = "$LOGIN_PATH?expired"
}
- return http.build()
+ sessionFixation { newSession() }
+ }
+ csrf { disable() }
}
-
- @Bean
- @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
- fun grantedAuthoritiesMapper(
- userRoleRepository: UserRoleRepository,
- appSecurityConfigProperties: SecurityConfigProperties
- ): GrantedAuthoritiesMapper {
- return GrantedAuthoritiesMapper { grantedAuthority ->
- grantedAuthority.filterIsInstance<OidcUserAuthority>()
- .onEach {
- val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
- if (userRole.isEmpty) {
- userRoleRepository.save(
- UserRole(
- null,
- it.userInfo.preferredUsername,
- appSecurityConfigProperties.defaultNewUserRole
- )
- )
- }
- }
- .map {
- val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
- SimpleGrantedAuthority("ROLE_${userRole.get().role.toString().uppercase()}")
- }
- }
- }
-
- @Bean
- @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "false", matchIfMissing = true)
- fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
- http {
- authorizeHttpRequests {
- authorize("/configs/**", hasRole("ADMIN"))
- authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN"))
- authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN"))
- authorize("/report/**", hasRole("ADMIN"))
- authorize(anyRequest, permitAll)
+ return http.build()
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
+ fun grantedAuthoritiesMapper(
+ userRoleRepository: UserRoleRepository,
+ appSecurityConfigProperties: SecurityConfigProperties,
+ ): GrantedAuthoritiesMapper {
+ return GrantedAuthoritiesMapper { grantedAuthority ->
+ grantedAuthority
+ .filterIsInstance<OidcUserAuthority>()
+ .onEach {
+ val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
+ if (userRole.isEmpty) {
+ userRoleRepository.save(
+ UserRole(
+ null,
+ it.userInfo.preferredUsername,
+ appSecurityConfigProperties.defaultNewUserRole,
+ )
+ )
}
- httpBasic {
- realmName = "ETL-Processor"
- }
- formLogin {
- loginPage = LOGIN_PATH
- }
- csrf { disable() }
- }
- return http.build()
+ }
+ .map {
+ val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
+ SimpleGrantedAuthority("ROLE_${userRole.get().role.toString().uppercase()}")
+ }
}
-
- @Bean
- fun sessionRegistry(): SessionRegistry {
- return SessionRegistryImpl()
- }
-
- @Bean
- fun passwordEncoder(): PasswordEncoder {
- return PasswordEncoderFactories.createDelegatingPasswordEncoder()
- }
-
- @Bean
- @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
- fun userRoleService(userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry): UserRoleService {
- return UserRoleService(userRoleRepository, sessionRegistry)
+ }
+
+ @Bean
+ @ConditionalOnProperty(
+ value = ["app.security.enable-oidc"],
+ havingValue = "false",
+ matchIfMissing = true,
+ )
+ fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
+ http {
+ authorizeHttpRequests {
+ authorize("/configs/**", hasRole("ADMIN"))
+ authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN"))
+ authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN"))
+ authorize("/report/**", hasRole("ADMIN"))
+ authorize(anyRequest, permitAll)
+ }
+ httpBasic { realmName = "ETL-Processor" }
+ formLogin { loginPage = LOGIN_PATH }
+ csrf { disable() }
}
+ return http.build()
+ }
+
+ @Bean
+ fun sessionRegistry(): SessionRegistry {
+ return SessionRegistryImpl()
+ }
+
+ @Bean
+ fun passwordEncoder(): PasswordEncoder {
+ return PasswordEncoderFactories.createDelegatingPasswordEncoder()
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
+ fun userRoleService(
+ userRoleRepository: UserRoleRepository,
+ sessionRegistry: SessionRegistry,
+ ): UserRoleService {
+ return UserRoleService(userRoleRepository, sessionRegistry)
+ }
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt
index 5469b1b..48163a1 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceDeserializer.kt
@@ -1,18 +1,19 @@
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 {
-
+ 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
index 812ce44..b4f29a4 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/ConsentResourceSerializer.kt
@@ -7,9 +7,11 @@ import org.hl7.fhir.r4.model.Consent
class ConsentResourceSerializer : JsonSerializer<Consent>() {
override fun serialize(
- value: Consent, gen: JsonGenerator, serializers: SerializerProvider
+ 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
index 2ae0dd3..45a3d93 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/FhirResourceModule.kt
@@ -1,6 +1,5 @@
package dev.dnpm.etl.processor.config
-
import com.fasterxml.jackson.databind.module.SimpleModule
import org.hl7.fhir.r4.model.Consent
@@ -9,4 +8,4 @@ class FhirResourceModule : SimpleModule() {
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
index 282f69e..2480de8 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/JacksonConfig.kt
@@ -2,33 +2,27 @@ package dev.dnpm.etl.processor.config
import ca.uhn.fhir.context.FhirContext
import com.fasterxml.jackson.annotation.JsonInclude
-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.jdk8.Jdk8Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
@Configuration
class JacksonConfig {
-
companion object {
var fhirContext: FhirContext = FhirContext.forR4()
- @JvmStatic
- fun fhirContext(): FhirContext {
- return fhirContext
- }
+ @JvmStatic fun fhirContext(): FhirContext = fhirContext
}
@Bean
- fun objectMapper(): ObjectMapper = ObjectMapper().registerModule(FhirResourceModule())
- .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
- .registerModule(
- JavaTimeModule()
- )
- .registerModule(
- Jdk8Module()
- )
- .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ fun objectMapper(): ObjectMapper =
+ ObjectMapper()
+ .registerModule(FhirResourceModule())
+ .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+ .registerModule(JavaTimeModule())
+ .registerModule(Jdk8Module())
+ .setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
index 195346d..58f647f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
@@ -24,38 +24,35 @@ import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
import dev.pcvolkmer.mv64e.mtb.Mtb
import org.springframework.stereotype.Service
-/**
- * Evaluates consent using provided consent service and file based consent information
- */
+/** Evaluates consent using provided consent service and file based consent information */
@Service
class ConsentEvaluator(
- private val consentService: IConsentService
+ private val consentService: IConsentService,
) {
fun check(mtbFile: Mtb): ConsentEvaluation {
val ttpConsentStatus = consentService.getTtpBroadConsentStatus(mtbFile.patient.id)
- val consentGiven = ttpConsentStatus == TtpConsentStatus.BROAD_CONSENT_GIVEN
- || ttpConsentStatus == TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT
+ val consentGiven =
+ ttpConsentStatus == TtpConsentStatus.BROAD_CONSENT_GIVEN ||
+ ttpConsentStatus == TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT ||
// Aktuell nur Modellvorhaben Consent im File
- || ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE && mtbFile.metadata?.modelProjectConsent?.provisions?.any {
- it.purpose == ModelProjectConsentPurpose.SEQUENCING
- && it.type == ConsentProvision.PERMIT
+ ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE &&
+ mtbFile.metadata?.modelProjectConsent?.provisions?.any {
+ it.purpose == ModelProjectConsentPurpose.SEQUENCING &&
+ it.type == ConsentProvision.PERMIT
} == true
return ConsentEvaluation(ttpConsentStatus, consentGiven)
}
}
-data class ConsentEvaluation(private val ttpConsentStatus: TtpConsentStatus, private val consentGiven: Boolean) {
- /**
- * Checks if any required consent is present
- */
- fun hasConsent(): Boolean {
- return consentGiven
- }
+data class ConsentEvaluation(
+ private val ttpConsentStatus: TtpConsentStatus,
+ private val consentGiven: Boolean,
+) {
+ /** Checks if any required consent is present */
+ fun hasConsent(): Boolean = consentGiven
- /**
- * Returns the consent status
- */
+ /** Returns the consent status */
fun getStatus(): TtpConsentStatus {
if (ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE) {
// in case ttp check is disabled - we propagate rejected status anyway
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 d53eb7e..2f6b2bb 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
@@ -36,7 +36,7 @@ import java.nio.charset.Charset
class KafkaInputListener(
private val requestProcessor: RequestProcessor,
private val consentEvaluator: ConsentEvaluator,
- private val objectMapper: ObjectMapper
+ private val objectMapper: ObjectMapper,
) : MessageListener<String, String> {
private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java)
@@ -45,29 +45,40 @@ class KafkaInputListener(
MediaType.APPLICATION_JSON_VALUE -> handleDnpmV2Message(record)
CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE -> handleDnpmV2Message(record)
else -> {
- /* ignore other messages */
+ // ignore other messages
}
}
}
private fun guessMimeType(record: ConsumerRecord<String, String>): String? {
- if (record.headers().headers("contentType").toList().isEmpty()) {
+ if (record
+ .headers()
+ .headers("contentType")
+ .toList()
+ .isEmpty()
+ ) {
// Fallback if no contentType set (old behavior)
return MediaType.APPLICATION_JSON_VALUE
}
- return record.headers().headers("contentType")?.firstOrNull()?.value()?.toString(Charset.forName("UTF-8"))
+ return record
+ .headers()
+ .headers("contentType")
+ ?.firstOrNull()
+ ?.value()
+ ?.toString(Charset.forName("UTF-8"))
}
private fun handleDnpmV2Message(record: ConsumerRecord<String, String>) {
val mtbFile = objectMapper.readValue(record.value(), Mtb::class.java)
val patientId = PatientId(mtbFile.patient.id)
val firstRequestIdHeader = record.headers().headers("requestId")?.firstOrNull()
- val requestId = if (null != firstRequestIdHeader) {
- RequestId(String(firstRequestIdHeader.value()))
- } else {
- RequestId("")
- }
+ val requestId =
+ if (null != firstRequestIdHeader) {
+ RequestId(String(firstRequestIdHeader.value()))
+ } else {
+ RequestId("")
+ }
if (consentEvaluator.check(mtbFile).hasConsent()) {
logger.debug("Accepted MTB File for processing")
@@ -81,13 +92,8 @@ class KafkaInputListener(
if (requestId.isBlank()) {
requestProcessor.processDeletion(patientId, TtpConsentStatus.UNKNOWN_CHECK_FILE)
} else {
- requestProcessor.processDeletion(
- patientId,
- requestId,
- TtpConsentStatus.UNKNOWN_CHECK_FILE
- )
+ 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 e154536..5a43242 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
@@ -34,34 +34,36 @@ import org.springframework.web.bind.annotation.*
@RequestMapping(path = ["mtbfile", "mtb"])
class MtbFileRestController(
private val requestProcessor: RequestProcessor,
- private val consentEvaluator: ConsentEvaluator
+ private val consentEvaluator: ConsentEvaluator,
) {
- private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
+ private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
- @GetMapping
- fun info(): ResponseEntity<String> {
- return ResponseEntity.ok("Test")
- }
-
- @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE])
- fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
- val consentEvaluation = consentEvaluator.check(mtbFile)
- if (consentEvaluation.hasConsent()) {
- logger.debug("Accepted MTB File (DNPM V2) for processing")
- requestProcessor.processMtbFile(mtbFile)
- } else {
- logger.debug("Accepted MTB File (DNPM V2) and process deletion")
- val patientId = PatientId(mtbFile.patient.id)
- requestProcessor.processDeletion(patientId, consentEvaluation.getStatus())
- }
- return ResponseEntity.accepted().build()
- }
+ @GetMapping
+ fun info(): ResponseEntity<String> {
+ return ResponseEntity.ok("Test")
+ }
- @DeleteMapping(path = ["{patientId}"])
- fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
- logger.debug("Accepted patient ID to process deletion")
- requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE)
- return ResponseEntity.accepted().build()
+ @PostMapping(
+ consumes =
+ [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE]
+ )
+ fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
+ val consentEvaluation = consentEvaluator.check(mtbFile)
+ if (consentEvaluation.hasConsent()) {
+ logger.debug("Accepted MTB File (DNPM V2) for processing")
+ requestProcessor.processMtbFile(mtbFile)
+ } else {
+ logger.debug("Accepted MTB File (DNPM V2) and process deletion")
+ val patientId = PatientId(mtbFile.patient.id)
+ requestProcessor.processDeletion(patientId, consentEvaluation.getStatus())
}
+ return ResponseEntity.accepted().build()
+ }
+ @DeleteMapping(path = ["{patientId}"])
+ fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
+ logger.debug("Accepted patient ID to process deletion")
+ 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 6e97865..085d0a3 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt
@@ -17,13 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-
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
+import java.time.Instant
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.toJavaDuration
import org.apache.kafka.clients.consumer.Consumer
import org.apache.kafka.common.errors.TimeoutException
import org.slf4j.LoggerFactory
@@ -33,254 +35,288 @@ import org.springframework.scheduling.annotation.Scheduled
import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder
import reactor.core.publisher.Sinks
-import java.time.Instant
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.toJavaDuration
fun interface ConnectionCheckService {
- fun connectionAvailable(): ConnectionCheckResult
-
+ fun connectionAvailable(): ConnectionCheckResult
}
interface OutputConnectionCheckService : ConnectionCheckService
sealed class ConnectionCheckResult {
- abstract val available: Boolean
+ abstract val available: Boolean
- abstract val timestamp: Instant
+ abstract val timestamp: Instant
- abstract val lastChange: Instant
+ abstract val lastChange: Instant
- data class KafkaConnectionCheckResult(
- override val available: Boolean,
- override val timestamp: Instant,
- override val lastChange: Instant
- ) : ConnectionCheckResult()
+ data class KafkaConnectionCheckResult(
+ override val available: Boolean,
+ override val timestamp: Instant,
+ override val lastChange: Instant,
+ ) : ConnectionCheckResult()
- data class RestConnectionCheckResult(
- override val available: Boolean,
- override val timestamp: Instant,
- override val lastChange: Instant
- ) : ConnectionCheckResult()
+ data class RestConnectionCheckResult(
+ override val available: Boolean,
+ override val timestamp: Instant,
+ override val lastChange: Instant,
+ ) : ConnectionCheckResult()
- data class GPasConnectionCheckResult(
- override val available: Boolean,
- override val timestamp: Instant,
- override val lastChange: Instant
- ) : ConnectionCheckResult()
+ data class GPasConnectionCheckResult(
+ override val available: Boolean,
+ 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()
+ data class GIcsConnectionCheckResult(
+ override val available: Boolean,
+ override val timestamp: Instant,
+ override val lastChange: Instant,
+ ) : ConnectionCheckResult()
}
class KafkaConnectionCheckService(
private val consumer: Consumer<String, String>,
@param:Qualifier("connectionCheckUpdateProducer")
- private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
+ private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
) : OutputConnectionCheckService {
- private val logger = LoggerFactory.getLogger(javaClass)
- private var result = ConnectionCheckResult.KafkaConnectionCheckResult(false, Instant.now(), Instant.now())
-
- @PostConstruct
- @Scheduled(cron = "0 * * * * *")
- fun check() {
- result = try {
- val available = null != consumer.listTopics(5.seconds.toJavaDuration())
- ConnectionCheckResult.KafkaConnectionCheckResult(
- available,
- Instant.now(),
- if (result.available == available) { result.lastChange } else { Instant.now() }
- )
+ private val logger = LoggerFactory.getLogger(javaClass)
+ private var result =
+ ConnectionCheckResult.KafkaConnectionCheckResult(false, Instant.now(), Instant.now())
+
+ @PostConstruct
+ @Scheduled(cron = "0 * * * * *")
+ fun check() {
+ result =
+ try {
+ val available = null != consumer.listTopics(5.seconds.toJavaDuration())
+ ConnectionCheckResult.KafkaConnectionCheckResult(
+ available,
+ Instant.now(),
+ if (result.available == available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
} catch (ex: TimeoutException) {
- logger.error("Connection-Timeout error: {}", ex.message)
- ConnectionCheckResult.KafkaConnectionCheckResult(
- false,
- Instant.now(),
- if (!result.available) { result.lastChange } else { Instant.now() }
- )
+ logger.error("Connection-Timeout error: {}", ex.message)
+ ConnectionCheckResult.KafkaConnectionCheckResult(
+ false,
+ Instant.now(),
+ if (!result.available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
}
- connectionCheckUpdateProducer.emitNext(
- result,
- Sinks.EmitFailureHandler.FAIL_FAST
- )
- }
-
- override fun connectionAvailable(): ConnectionCheckResult.KafkaConnectionCheckResult {
- return this.result
- }
+ connectionCheckUpdateProducer.emitNext(result, Sinks.EmitFailureHandler.FAIL_FAST)
+ }
+ override fun connectionAvailable(): ConnectionCheckResult.KafkaConnectionCheckResult {
+ return this.result
+ }
}
class RestConnectionCheckService(
private val restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties,
@param:Qualifier("connectionCheckUpdateProducer")
- private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
+ private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
) : OutputConnectionCheckService {
- private val logger = LoggerFactory.getLogger(javaClass)
- private var result = ConnectionCheckResult.RestConnectionCheckResult(false, Instant.now(), Instant.now())
-
- @PostConstruct
- @Scheduled(cron = "0 * * * * *")
- fun check() {
- result = try {
- val statusCode = restTemplate.getForEntity(
- UriComponentsBuilder.fromUriString(restTargetProperties.uri.toString())
- .pathSegment("mtb")
- .pathSegment("kaplan-meier")
- .pathSegment("config")
- .toUriString(),
- String::class.java
- ).statusCode
- val available = statusCode == HttpStatus.OK
-
- if (available.not()) {
- logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
- }
- ConnectionCheckResult.RestConnectionCheckResult(
- available,
- Instant.now(),
- if (result.available == available) { result.lastChange } else { Instant.now() }
- )
+ private val logger = LoggerFactory.getLogger(javaClass)
+ private var result =
+ ConnectionCheckResult.RestConnectionCheckResult(false, Instant.now(), Instant.now())
+
+ @PostConstruct
+ @Scheduled(cron = "0 * * * * *")
+ fun check() {
+ result =
+ try {
+ val statusCode =
+ restTemplate
+ .getForEntity(
+ UriComponentsBuilder.fromUriString(restTargetProperties.uri.toString())
+ .pathSegment("mtb")
+ .pathSegment("kaplan-meier")
+ .pathSegment("config")
+ .toUriString(),
+ String::class.java,
+ )
+ .statusCode
+ val available = statusCode == HttpStatus.OK
+
+ if (available.not()) {
+ logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
+ }
+ ConnectionCheckResult.RestConnectionCheckResult(
+ available,
+ Instant.now(),
+ if (result.available == available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
} catch (ex: Exception) {
- logger.error("Connection-Check error: {}", ex.message)
- ConnectionCheckResult.RestConnectionCheckResult(
- false,
- Instant.now(),
- if (!result.available) { result.lastChange } else { Instant.now() }
- )
+ logger.error("Connection-Check error: {}", ex.message)
+ ConnectionCheckResult.RestConnectionCheckResult(
+ false,
+ Instant.now(),
+ if (!result.available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
}
- connectionCheckUpdateProducer.emitNext(
- result,
- Sinks.EmitFailureHandler.FAIL_FAST
- )
- }
-
- override fun connectionAvailable(): ConnectionCheckResult.RestConnectionCheckResult {
- return this.result
- }
+ connectionCheckUpdateProducer.emitNext(result, Sinks.EmitFailureHandler.FAIL_FAST)
+ }
+
+ override fun connectionAvailable(): ConnectionCheckResult.RestConnectionCheckResult {
+ return this.result
+ }
}
class GPasConnectionCheckService(
private val restTemplate: RestTemplate,
private val gPasConfigProperties: GPasConfigProperties,
@param:Qualifier("connectionCheckUpdateProducer")
- private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
+ private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
) : ConnectionCheckService {
- private val logger = LoggerFactory.getLogger(javaClass)
- private var result = ConnectionCheckResult.GPasConnectionCheckResult(false, Instant.now(), Instant.now())
-
- @PostConstruct
- @Scheduled(cron = "0 * * * * *")
- fun check() {
- result = try {
- val uri = UriComponentsBuilder.fromUriString(
- gPasConfigProperties.uri.toString()).path("/metadata").build().toUri()
-
- val headers = HttpHeaders()
- headers.contentType = MediaType.APPLICATION_JSON
- if (!gPasConfigProperties.username.isNullOrBlank() && !gPasConfigProperties.password.isNullOrBlank()) {
- headers.setBasicAuth(gPasConfigProperties.username, gPasConfigProperties.password)
- }
-
- val statusCode = restTemplate.exchange(
- uri,
- HttpMethod.GET,
- HttpEntity<Void>(headers),
- Void::class.java
- ).statusCode
- val available = statusCode == HttpStatus.OK
-
- if (available.not()) {
- logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
- }
- ConnectionCheckResult.GPasConnectionCheckResult(
- available,
- Instant.now(),
- if (result.available == available) { result.lastChange } else { Instant.now() }
- )
+ private val logger = LoggerFactory.getLogger(javaClass)
+ private var result =
+ ConnectionCheckResult.GPasConnectionCheckResult(false, Instant.now(), Instant.now())
+
+ @PostConstruct
+ @Scheduled(cron = "0 * * * * *")
+ fun check() {
+ result =
+ try {
+ val uri =
+ UriComponentsBuilder.fromUriString(gPasConfigProperties.uri.toString())
+ .path("/metadata")
+ .build()
+ .toUri()
+
+ val headers = HttpHeaders()
+ headers.contentType = MediaType.APPLICATION_JSON
+ if (
+ !gPasConfigProperties.username.isNullOrBlank() &&
+ !gPasConfigProperties.password.isNullOrBlank()
+ ) {
+ headers.setBasicAuth(gPasConfigProperties.username, gPasConfigProperties.password)
+ }
+
+ val statusCode =
+ restTemplate
+ .exchange(uri, HttpMethod.GET, HttpEntity<Void>(headers), Void::class.java)
+ .statusCode
+ val available = statusCode == HttpStatus.OK
+
+ if (available.not()) {
+ logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
+ }
+ ConnectionCheckResult.GPasConnectionCheckResult(
+ available,
+ Instant.now(),
+ if (result.available == available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
} catch (ex: Exception) {
- logger.error("Connection-Check error: {}", ex.message)
- ConnectionCheckResult.GPasConnectionCheckResult(
- false,
- Instant.now(),
- if (!result.available) { result.lastChange } else { Instant.now() }
- )
+ logger.error("Connection-Check error: {}", ex.message)
+ ConnectionCheckResult.GPasConnectionCheckResult(
+ false,
+ Instant.now(),
+ if (!result.available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
}
- connectionCheckUpdateProducer.emitNext(
- result,
- Sinks.EmitFailureHandler.FAIL_FAST
- )
- }
-
- override fun connectionAvailable(): ConnectionCheckResult.GPasConnectionCheckResult {
- return this.result
- }
+ connectionCheckUpdateProducer.emitNext(result, Sinks.EmitFailureHandler.FAIL_FAST)
+ }
+
+ override fun connectionAvailable(): ConnectionCheckResult.GPasConnectionCheckResult {
+ return this.result
+ }
}
class GIcsConnectionCheckService(
private val restTemplate: RestTemplate,
private val gIcsConfigProperties: GIcsConfigProperties,
@param:Qualifier("connectionCheckUpdateProducer")
- private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
+ private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
) : ConnectionCheckService {
- private val logger = LoggerFactory.getLogger(javaClass)
- 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 statusCode = restTemplate.exchange(
- uri,
- HttpMethod.GET,
- HttpEntity<Void>(headers),
- Void::class.java
- ).statusCode
- val available = statusCode == HttpStatus.OK
-
- if (available.not()) {
- logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
- }
- ConnectionCheckResult.GIcsConnectionCheckResult(
- available,
- Instant.now(),
- if (result.available == available) { result.lastChange } else { Instant.now() }
- )
+ private val logger = LoggerFactory.getLogger(javaClass)
+ 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 statusCode =
+ restTemplate
+ .exchange(uri, HttpMethod.GET, HttpEntity<Void>(headers), Void::class.java)
+ .statusCode
+ val available = statusCode == HttpStatus.OK
+
+ if (available.not()) {
+ logger.error("Invalid response code {}, expected HTTP status 200", statusCode)
+ }
+ ConnectionCheckResult.GIcsConnectionCheckResult(
+ available,
+ Instant.now(),
+ if (result.available == available) {
+ result.lastChange
+ } else {
+ Instant.now()
+ },
+ )
} catch (ex: Exception) {
- logger.error("Connection-Check error: {}", ex.message)
- ConnectionCheckResult.GIcsConnectionCheckResult(
- false,
- Instant.now(),
- if (!result.available) { result.lastChange } else { Instant.now() }
- )
+ logger.error("Connection-Check error: {}", ex.message)
+ 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
- }
+ connectionCheckUpdateProducer.emitNext(result, Sinks.EmitFailureHandler.FAIL_FAST)
+ }
+
+ override fun connectionAvailable(): ConnectionCheckResult.GIcsConnectionCheckResult {
+ return this.result
+ }
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ReportService.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ReportService.kt
index dd5c44a..c54aa7a 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ReportService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ReportService.kt
@@ -30,61 +30,53 @@ import dev.dnpm.etl.processor.monitoring.ReportService.Issue
import dev.dnpm.etl.processor.monitoring.ReportService.Severity
import java.util.*
-class ReportService(
- private val objectMapper: ObjectMapper
-) {
+class ReportService(private val objectMapper: ObjectMapper) {
- fun deserialize(dataQualityReport: String?): List<Issue> {
- if (dataQualityReport.isNullOrBlank()) {
- return listOf()
- }
- return try {
- objectMapper
- .readValue(dataQualityReport, DataQualityReport::class.java)
- .issues
- .sortedBy { it.severity }
- } catch (e: Exception) {
- val otherIssue =
- Issue(Severity.ERROR, "Not parsable data quality report '$dataQualityReport'")
- return when (e) {
- is JsonMappingException -> listOf(otherIssue)
- is JsonParseException -> listOf(otherIssue)
- else -> throw e
- }
- }
+ fun deserialize(dataQualityReport: String?): List<Issue> {
+ if (dataQualityReport.isNullOrBlank()) {
+ return listOf()
}
+ return try {
+ objectMapper.readValue(dataQualityReport, DataQualityReport::class.java).issues.sortedBy {
+ it.severity
+ }
+ } catch (e: Exception) {
+ val otherIssue =
+ Issue(Severity.ERROR, "Not parsable data quality report '$dataQualityReport'")
+ return when (e) {
+ is JsonMappingException -> listOf(otherIssue)
+ is JsonParseException -> listOf(otherIssue)
+ else -> throw e
+ }
+ }
+ }
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ private data class DataQualityReport(
+ @param:JsonProperty(value = "issues") val issues: List<Issue>
+ )
- @JsonIgnoreProperties(ignoreUnknown = true)
- private data class DataQualityReport(
- @param:JsonProperty(value = "issues")
- val issues: List<Issue>
- )
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- data class Issue(
- @param:JsonProperty(value = "severity")
- val severity: Severity,
- @param:JsonProperty(value = "message")
- @param:JsonAlias("details")
- val message: String,
- @param:JsonProperty(value = "path")
- val path: Optional<String> = Optional.empty()
- )
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ data class Issue(
+ @param:JsonProperty(value = "severity") val severity: Severity,
+ @param:JsonProperty(value = "message") @param:JsonAlias("details") val message: String,
+ @param:JsonProperty(value = "path") val path: Optional<String> = Optional.empty(),
+ )
- enum class Severity(@JsonValue val value: String) {
- FATAL("fatal"),
- ERROR("error"),
- WARNING("warning"),
- INFO("info")
- }
+ enum class Severity(@JsonValue val value: String) {
+ FATAL("fatal"),
+ ERROR("error"),
+ WARNING("warning"),
+ INFO("info"),
+ }
}
fun List<Issue>.asRequestStatus(): RequestStatus {
- val severity = this.minOfOrNull { it.severity }
- return when (severity) {
- Severity.FATAL, Severity.ERROR -> RequestStatus.ERROR
- Severity.WARNING -> RequestStatus.WARNING
- else -> RequestStatus.SUCCESS
- }
-} \ No newline at end of file
+ val severity = this.minOfOrNull { it.severity }
+ return when (severity) {
+ Severity.FATAL,
+ Severity.ERROR -> RequestStatus.ERROR
+ Severity.WARNING -> RequestStatus.WARNING
+ else -> RequestStatus.SUCCESS
+ }
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
index f2509dd..71731f1 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
@@ -20,6 +20,9 @@
package dev.dnpm.etl.processor.monitoring
import dev.dnpm.etl.processor.*
+import java.time.Instant
+import java.time.temporal.ChronoUnit
+import java.util.*
import org.springframework.data.annotation.Id
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
@@ -29,9 +32,6 @@ import org.springframework.data.relational.core.mapping.Embedded
import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.PagingAndSortingRepository
-import java.time.Instant
-import java.time.temporal.ChronoUnit
-import java.util.*
@Table("request")
data class Request(
@@ -39,46 +39,38 @@ data class Request(
val uuid: RequestId = randomRequestId(),
val patientPseudonym: PatientPseudonym,
val pid: PatientId,
- @Column("fingerprint")
- val fingerprint: Fingerprint,
+ @Column("fingerprint") val fingerprint: Fingerprint,
val type: RequestType,
var status: RequestStatus,
var processedAt: Instant = Instant.now(),
- @Embedded.Nullable var report: Report? = null
+ @Embedded.Nullable var report: Report? = null,
) {
- constructor(
- uuid: RequestId,
- patientPseudonym: PatientPseudonym,
- pid: PatientId,
- fingerprint: Fingerprint,
- type: RequestType,
- status: RequestStatus
- ) :
- this(null, uuid, patientPseudonym, pid, fingerprint, type, status, Instant.now())
-
- constructor(
- uuid: RequestId,
- patientPseudonym: PatientPseudonym,
- pid: PatientId,
- fingerprint: Fingerprint,
- type: RequestType,
- status: RequestStatus,
- processedAt: Instant
- ) :
- this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt)
-
- fun isPendingUnknown(): Boolean {
- return this.status == RequestStatus.UNKNOWN && this.processedAt.isBefore(
- Instant.now().minus(10, ChronoUnit.MINUTES)
- )
- }
+ constructor(
+ uuid: RequestId,
+ patientPseudonym: PatientPseudonym,
+ pid: PatientId,
+ fingerprint: Fingerprint,
+ type: RequestType,
+ status: RequestStatus,
+ ) : this(null, uuid, patientPseudonym, pid, fingerprint, type, status, Instant.now())
+
+ constructor(
+ uuid: RequestId,
+ patientPseudonym: PatientPseudonym,
+ pid: PatientId,
+ fingerprint: Fingerprint,
+ type: RequestType,
+ status: RequestStatus,
+ processedAt: Instant,
+ ) : this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt)
+
+ fun isPendingUnknown(): Boolean {
+ return this.status == RequestStatus.UNKNOWN &&
+ this.processedAt.isBefore(Instant.now().minus(10, ChronoUnit.MINUTES))
+ }
}
-@JvmRecord
-data class Report(
- val description: String,
- val dataQualityReport: String = ""
-)
+@JvmRecord data class Report(val description: String, val dataQualityReport: String = "")
@JvmRecord
data class CountedState(
@@ -86,34 +78,41 @@ data class CountedState(
val status: RequestStatus,
)
-interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRepository<Request, Long> {
-
- fun findAllByPatientPseudonymOrderByProcessedAtDesc(patientId: PatientPseudonym): List<Request>
-
- fun findByUuidEquals(uuid: RequestId): Optional<Request>
-
- fun findRequestByPatientPseudonym(patientPseudonym: PatientPseudonym, pageable: Pageable): Page<Request>
-
- @Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;")
- fun countStates(): List<CountedState>
-
- @Query(
- "SELECT count(*) AS count, status FROM (" +
- "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
- "WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " +
- ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
- )
- fun findPatientUniqueStates(): List<CountedState>
-
- @Query("SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;")
- fun countDeleteStates(): List<CountedState>
-
- @Query(
- "SELECT count(*) AS count, status FROM (" +
- "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
- "WHERE type = 'DELETE'" +
- ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
- )
- fun findPatientUniqueDeleteStates(): List<CountedState>
-
+interface RequestRepository :
+ CrudRepository<Request, Long>, PagingAndSortingRepository<Request, Long> {
+
+ fun findAllByPatientPseudonymOrderByProcessedAtDesc(patientId: PatientPseudonym): List<Request>
+
+ fun findByUuidEquals(uuid: RequestId): Optional<Request>
+
+ fun findRequestByPatientPseudonym(
+ patientPseudonym: PatientPseudonym,
+ pageable: Pageable,
+ ): Page<Request>
+
+ @Query(
+ "SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;"
+ )
+ fun countStates(): List<CountedState>
+
+ @Query(
+ "SELECT count(*) AS count, status FROM (" +
+ "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
+ "WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " +
+ ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
+ )
+ fun findPatientUniqueStates(): List<CountedState>
+
+ @Query(
+ "SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;"
+ )
+ fun countDeleteStates(): List<CountedState>
+
+ @Query(
+ "SELECT count(*) AS count, status FROM (" +
+ "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
+ "WHERE type = 'DELETE'" +
+ ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
+ )
+ fun findPatientUniqueDeleteStates(): List<CountedState>
}
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 0c8adb1..5487a05 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt
@@ -19,11 +19,13 @@
package dev.dnpm.etl.processor.monitoring
-enum class RequestStatus(val value: String) {
+enum class RequestStatus(
+ val value: String,
+) {
SUCCESS("success"),
WARNING("warning"),
ERROR("error"),
UNKNOWN("unknown"),
DUPLICATION("duplication"),
- NO_CONSENT("no-consent")
-} \ No newline at end of file
+ NO_CONSENT("no-consent"),
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestType.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestType.kt
index cb43d7f..ef7f1e3 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestType.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestType.kt
@@ -19,7 +19,9 @@
package dev.dnpm.etl.processor.monitoring
-enum class RequestType(val value: String) {
+enum class RequestType(
+ val value: String,
+) {
MTB_FILE("mtb_file"),
DELETE("delete"),
-} \ 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 ef46c0a..71e4a78 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt
@@ -34,9 +34,8 @@ class KafkaMtbFileSender(
private val kafkaTemplate: KafkaTemplate<String, String>,
private val kafkaProperties: KafkaProperties,
private val retryTemplate: RetryTemplate,
- private val objectMapper: ObjectMapper
+ private val objectMapper: ObjectMapper,
) : MtbFileSender {
-
private val logger = LoggerFactory.getLogger(KafkaMtbFileSender::class.java)
override fun <T> send(request: MtbFileRequest<T>): MtbFileSender.Response {
@@ -50,11 +49,13 @@ class KafkaMtbFileSender(
)
record.headers().add("requestId", request.requestId.value.toByteArray())
when (request) {
- is DnpmV2MtbFileRequest -> record.headers()
- .add(
- "contentType",
- CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray()
- )
+ is DnpmV2MtbFileRequest ->
+ record
+ .headers()
+ .add(
+ "contentType",
+ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray(),
+ )
}
val result = kafkaTemplate.send(record)
@@ -72,9 +73,7 @@ class KafkaMtbFileSender(
}
override fun send(request: DeleteRequest): MtbFileSender.Response {
- val dummyMtbFile = Mtb.builder()
- .metadata(MvhMetadata())
- .build()
+ val dummyMtbFile = Mtb.builder().metadata(MvhMetadata()).build()
return try {
return retryTemplate.execute<MtbFileSender.Response, Exception> {
@@ -83,11 +82,8 @@ class KafkaMtbFileSender(
kafkaProperties.outputTopic,
key(request),
objectMapper.writeValueAsString(
- DnpmV2MtbFileRequest(
- request.requestId,
- dummyMtbFile
- )
- )
+ DnpmV2MtbFileRequest(request.requestId, dummyMtbFile),
+ ),
)
record.headers().add("requestId", request.requestId.value.toByteArray())
val result = kafkaTemplate.send(record)
@@ -104,15 +100,14 @@ class KafkaMtbFileSender(
}
}
- override fun endpoint(): String {
- return "${this.kafkaProperties.servers} (${this.kafkaProperties.outputTopic}/${this.kafkaProperties.outputResponseTopic})"
- }
+ override fun endpoint(): String =
+ "${this.kafkaProperties.servers} (${this.kafkaProperties.outputTopic}/${this.kafkaProperties.outputResponseTopic})"
- private fun key(request: MtbRequest): String {
- return when (request) {
+ private fun key(request: MtbRequest): String =
+ when (request) {
is DnpmV2MtbFileRequest -> "{\"pid\": \"${request.content.patient.id}\"}"
is DeleteRequest -> "{\"pid\": \"${request.patientId.value}\"}"
- else -> throw IllegalArgumentException("Unsupported request type: ${request::class.simpleName}")
+ else ->
+ throw IllegalArgumentException("Unsupported request type: ${request::class.simpleName}")
}
- }
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbFileSender.kt
index 285ce07..c81b572 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbFileSender.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbFileSender.kt
@@ -29,18 +29,18 @@ interface MtbFileSender {
fun endpoint(): String
- data class Response(val status: RequestStatus, val body: String = "")
+ data class Response(
+ val status: RequestStatus,
+ val body: String = "",
+ )
}
-fun Int.asRequestStatus(): RequestStatus {
- return when (this) {
+fun Int.asRequestStatus(): RequestStatus =
+ when (this) {
200 -> RequestStatus.SUCCESS
201 -> RequestStatus.WARNING
- in 400 .. 999 -> RequestStatus.ERROR
- else -> RequestStatus.UNKNOWN
+ in 400..999 -> RequestStatus.ERROR
+ else -> RequestStatus.UNKNOWN
}
-}
-fun HttpStatusCode.asRequestStatus(): RequestStatus {
- return this.value().asRequestStatus()
-}
+fun HttpStatusCode.asRequestStatus(): RequestStatus = this.value().asRequestStatus()
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
index 7512200..b228c4c 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
@@ -36,14 +36,12 @@ sealed interface MtbFileRequest<out T> : MtbRequest {
data class DnpmV2MtbFileRequest(
override val requestId: RequestId,
- override val content: Mtb
+ override val content: Mtb,
) : MtbFileRequest<Mtb> {
- override fun patientPseudonym(): PatientPseudonym {
- return PatientPseudonym(content.patient.id)
- }
+ override fun patientPseudonym(): PatientPseudonym = PatientPseudonym(content.patient.id)
}
data class DeleteRequest(
override val requestId: RequestId,
- val patientId: PatientPseudonym
+ val patientId: PatientPseudonym,
) : MtbRequest
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSender.kt
index 1e6a5a7..5aad133 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSender.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSender.kt
@@ -30,26 +30,22 @@ class RestDipMtbFileSender(
restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties,
retryTemplate: RetryTemplate,
- reportService: ReportService
+ reportService: ReportService,
) : RestMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) {
-
- override fun sendUrl(): String {
- return UriComponentsBuilder
+ override fun sendUrl(): String =
+ UriComponentsBuilder
.fromUriString(restTargetProperties.uri.toString())
.pathSegment("mtb")
.pathSegment("etl")
.pathSegment("patient-record")
.toUriString()
- }
- override fun deleteUrl(patientId: PatientPseudonym): String {
- return UriComponentsBuilder
+ override fun deleteUrl(patientId: PatientPseudonym): String =
+ UriComponentsBuilder
.fromUriString(restTargetProperties.uri.toString())
.pathSegment("mtb")
.pathSegment("etl")
.pathSegment("patient")
.pathSegment(patientId.value)
.toUriString()
- }
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt
index ec6ff85..4120d4a 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt
@@ -38,9 +38,8 @@ abstract class RestMtbFileSender(
private val restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties,
private val retryTemplate: RetryTemplate,
- private val reportService: ReportService
+ private val reportService: ReportService,
) : MtbFileSender {
-
private val logger = LoggerFactory.getLogger(RestMtbFileSender::class.java)
abstract fun sendUrl(): String
@@ -52,27 +51,29 @@ abstract class RestMtbFileSender(
return retryTemplate.execute<MtbFileSender.Response, Exception> {
val headers = getHttpHeaders(request)
val entityReq = HttpEntity(request.content, headers)
- val response = restTemplate.postForEntity(
- sendUrl(),
- entityReq,
- String::class.java
- )
+ val response = restTemplate.postForEntity(sendUrl(), entityReq, String::class.java)
if (!response.statusCode.is2xxSuccessful) {
logger.warn("Error sending to remote system: {}", response.body)
return@execute MtbFileSender.Response(
reportService.deserialize(response.body).asRequestStatus(),
- "Status-Code: ${response.statusCode.value()}"
+ "Status-Code: ${response.statusCode.value()}",
)
}
logger.debug("Sent file via RestMtbFileSender")
- return@execute MtbFileSender.Response(reportService.deserialize(response.body).asRequestStatus(), response.body.orEmpty())
+ return@execute MtbFileSender.Response(
+ reportService.deserialize(response.body).asRequestStatus(),
+ response.body.orEmpty(),
+ )
}
} catch (e: IllegalArgumentException) {
logger.error("Not a valid URI to export to: '{}'", restTargetProperties.uri!!)
} catch (e: RestClientResponseException) {
logger.info(restTargetProperties.uri!!.toString())
logger.error("Request data not accepted by remote system", e)
- return MtbFileSender.Response(reportService.deserialize(e.responseBodyAsString).asRequestStatus(), e.responseBodyAsString)
+ return MtbFileSender.Response(
+ reportService.deserialize(e.responseBodyAsString).asRequestStatus(),
+ e.responseBodyAsString,
+ )
}
return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung")
}
@@ -82,11 +83,7 @@ abstract class RestMtbFileSender(
return retryTemplate.execute<MtbFileSender.Response, Exception> {
val headers = getHttpHeaders(request)
val entityReq = HttpEntity(null, headers)
- restTemplate.delete(
- deleteUrl(request.patientId),
- entityReq,
- String::class.java
- )
+ restTemplate.delete(deleteUrl(request.patientId), entityReq, String::class.java)
logger.debug("Sent file via RestMtbFileSender")
return@execute MtbFileSender.Response(RequestStatus.SUCCESS)
}
@@ -99,18 +96,17 @@ abstract class RestMtbFileSender(
return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung")
}
- override fun endpoint(): String {
- return this.restTargetProperties.uri.orEmpty()
- }
+ override fun endpoint(): String = this.restTargetProperties.uri.orEmpty()
private fun getHttpHeaders(request: MtbRequest): HttpHeaders {
val username = restTargetProperties.username
val password = restTargetProperties.password
val headers = HttpHeaders()
- headers.contentType = when (request) {
- is DnpmV2MtbFileRequest -> CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON
- else -> MediaType.APPLICATION_JSON
- }
+ headers.contentType =
+ when (request) {
+ is DnpmV2MtbFileRequest -> CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON
+ else -> MediaType.APPLICATION_JSON
+ }
if (username.isNullOrBlank() || password.isNullOrBlank()) {
return headers
@@ -119,5 +115,4 @@ abstract class RestMtbFileSender(
headers.setBasicAuth(username, password)
return headers
}
-
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt
index dcb438f..90d867f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt
@@ -24,24 +24,17 @@ import org.apache.commons.codec.digest.DigestUtils
import java.security.SecureRandom
class AnonymizingGenerator : Generator {
- companion object fun getSecureRandom() : SecureRandom {
- return SecureRandom()
- }
+ companion object
- override fun generate(id: String): String {
- return Base32().encodeAsString(DigestUtils.sha256(id))
- .substring(0..41)
- .lowercase()
- }
+ fun getSecureRandom(): SecureRandom = SecureRandom()
+
+ override fun generate(id: String): String = Base32().encodeAsString(DigestUtils.sha256(id)).substring(0..41).lowercase()
@OptIn(ExperimentalStdlibApi::class)
override fun generateGenomDeTan(id: String): String {
-
val bytes = ByteArray(64 / 2)
getSecureRandom().nextBytes(bytes)
return bytes.joinToString("") { "%02x".format(it) }
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt
index 8215d23..089736c 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt
@@ -8,19 +8,15 @@ class GpasSoapPseudonymGenerator(
private val gpasCfg: GPasConfigProperties,
private val retryTemplate: RetryTemplate,
private val gpasSoapService: GpasSoapService,
- private val appFhirConfig: AppFhirConfig
+ private val appFhirConfig: AppFhirConfig,
) : Generator {
-
- override fun generate(id: String): String {
- return retryTemplate.execute<String, Exception> {
+ override fun generate(id: String): String =
+ retryTemplate.execute<String, Exception> {
gpasSoapService.getOrCreatePseudonymFor(id, gpasCfg.patientDomain)
}
- }
- override fun generateGenomDeTan(id: String): String {
- return retryTemplate.execute<String, Exception> {
+ override fun generateGenomDeTan(id: String): String =
+ retryTemplate.execute<String, Exception> {
gpasSoapService.createPseudonymsFor(id, gpasCfg.genomDeTanDomain, 1).first()
}
- }
}
-
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt
index 0909924..f1121b8 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt
@@ -8,15 +8,14 @@ import jakarta.xml.bind.annotation.XmlElementWrapper
@WebService(
name = "PSNManagerBeanService",
- targetNamespace ="http://psn.ttp.ganimed.icmvc.emau.org/"
+ targetNamespace = "http://psn.ttp.ganimed.icmvc.emau.org/",
)
interface GpasSoapService {
-
@WebMethod(operationName = "getOrCreatePseudonymFor")
@WebResult(name = "psn")
fun getOrCreatePseudonymFor(
@WebParam(name = "value") value: String,
- @WebParam(name = "domainName") domainName: String
+ @WebParam(name = "domainName") domainName: String,
): String
@WebMethod(operationName = "createPseudonymsFor")
@@ -25,7 +24,6 @@ interface GpasSoapService {
fun createPseudonymsFor(
@WebParam(name = "value") value: String,
@WebParam(name = "domainName") domainName: String,
- @WebParam(name = "number") minNumber: Int
+ @WebParam(name = "number") minNumber: Int,
): List<String>
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeService.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeService.kt
index 96225a9..77ab87d 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeService.kt
@@ -25,22 +25,16 @@ import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties
class PseudonymizeService(
private val generator: Generator,
- private val configProperties: PseudonymizeConfigProperties
+ private val configProperties: PseudonymizeConfigProperties,
) {
-
- fun patientPseudonym(patientId: PatientId): PatientPseudonym {
- return when (generator) {
+ fun patientPseudonym(patientId: PatientId): PatientPseudonym =
+ when (generator) {
is GpasPseudonymGenerator -> PatientPseudonym(generator.generate(patientId.value))
- else -> PatientPseudonym("${configProperties.prefix}_${generator.generate(patientId.value)}")
+ else ->
+ PatientPseudonym("${configProperties.prefix}_${generator.generate(patientId.value)}")
}
- }
-
- fun genomDeTan(patientId: PatientId): String {
- return generator.generateGenomDeTan(patientId.value)
- }
- fun prefix(): String {
- return configProperties.prefix
- }
+ fun genomDeTan(patientId: PatientId): String = generator.generateGenomDeTan(patientId.value)
-} \ No newline at end of file
+ fun prefix(): String = configProperties.prefix
+}
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 8721cbe..48ac58a 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt
@@ -25,282 +25,246 @@ 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
- *
- * @since 0.11.0
+/**
+ * Replaces patient ID with generated patient pseudonym
*
* @param pseudonymizeService The pseudonymizeService to be used
* @return The MTB file containing patient pseudonymes
+ * @since 0.11.0
*/
infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
- val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient.id)).value
-
- this.episodesOfCare?.forEach { it.patient?.id = patientPseudonym }
- this.carePlans?.forEach {
- it.patient.id = patientPseudonym
- it.rebiopsyRequests?.forEach { it.patient?.id = patientPseudonym }
- it.histologyReevaluationRequests?.forEach { it.patient?.id = patientPseudonym }
- it.medicationRecommendations?.forEach { it.patient?.id = patientPseudonym }
- it.studyEnrollmentRecommendations?.forEach { it.patient?.id = patientPseudonym }
- it.procedureRecommendations?.forEach { it.patient?.id = patientPseudonym }
- it.geneticCounselingRecommendation?.patient?.id = patientPseudonym
- }
- this.diagnoses?.forEach { it.patient?.id = patientPseudonym }
- this.guidelineTherapies?.forEach { it.patient?.id = patientPseudonym }
- this.guidelineProcedures?.forEach { it.patient?.id = patientPseudonym }
- this.patient.id = patientPseudonym
- this.claims?.forEach { it.patient?.id = patientPseudonym }
- this.claimResponses?.forEach { it.patient?.id = patientPseudonym }
- this.diagnoses?.forEach { it.patient?.id = patientPseudonym }
- this.familyMemberHistories?.forEach { it.patient?.id = patientPseudonym }
- this.histologyReports?.forEach {
- it.patient.id = patientPseudonym
- it.results.tumorMorphology?.patient?.id = patientPseudonym
- it.results.tumorCellContent?.patient?.id = patientPseudonym
- }
- this.ngsReports?.forEach {
- it.patient?.id = patientPseudonym
- it.results?.simpleVariants?.forEach { it.patient?.id = patientPseudonym }
- it.results?.copyNumberVariants?.forEach { it.patient?.id = patientPseudonym }
- it.results?.dnaFusions?.forEach { it.patient?.id = patientPseudonym }
- it.results?.rnaFusions?.forEach { it.patient?.id = patientPseudonym }
- it.results?.tumorCellContent?.patient?.id = patientPseudonym
- it.results?.brcaness?.patient?.id = patientPseudonym
- it.results?.tmb?.patient?.id = patientPseudonym
- it.results?.hrdScore?.patient?.id = patientPseudonym
- }
- this.ihcReports?.forEach {
- it.patient?.id = patientPseudonym
- it.results?.msiMmr?.forEach { it.patient?.id = patientPseudonym }
- it.results?.proteinExpression?.forEach { it.patient?.id = patientPseudonym }
- }
- this.responses?.forEach { it.patient?.id = patientPseudonym }
- this.specimens?.forEach { it.patient?.id = patientPseudonym }
- this.priorDiagnosticReports?.forEach { it.patient?.id = patientPseudonym }
- this.performanceStatus?.forEach { it.patient?.id = patientPseudonym }
- this.systemicTherapies?.forEach {
- it.history?.forEach {
- it.patient?.id = patientPseudonym
- }
- }
- this.followUps?.forEach {
- it.patient?.id = patientPseudonym
- }
-
- this.msiFindings?.forEach { it -> 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")
- }
+ val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient.id)).value
+
+ this.episodesOfCare?.forEach { it.patient?.id = patientPseudonym }
+ this.carePlans?.forEach {
+ it.patient.id = patientPseudonym
+ it.rebiopsyRequests?.forEach { it.patient?.id = patientPseudonym }
+ it.histologyReevaluationRequests?.forEach { it.patient?.id = patientPseudonym }
+ it.medicationRecommendations?.forEach { it.patient?.id = patientPseudonym }
+ it.studyEnrollmentRecommendations?.forEach { it.patient?.id = patientPseudonym }
+ it.procedureRecommendations?.forEach { it.patient?.id = patientPseudonym }
+ it.geneticCounselingRecommendation?.patient?.id = patientPseudonym
+ }
+ this.diagnoses?.forEach { it.patient?.id = patientPseudonym }
+ this.guidelineTherapies?.forEach { it.patient?.id = patientPseudonym }
+ this.guidelineProcedures?.forEach { it.patient?.id = patientPseudonym }
+ this.patient.id = patientPseudonym
+ this.claims?.forEach { it.patient?.id = patientPseudonym }
+ this.claimResponses?.forEach { it.patient?.id = patientPseudonym }
+ this.diagnoses?.forEach { it.patient?.id = patientPseudonym }
+ this.familyMemberHistories?.forEach { it.patient?.id = patientPseudonym }
+ this.histologyReports?.forEach {
+ it.patient.id = patientPseudonym
+ it.results.tumorMorphology?.patient?.id = patientPseudonym
+ it.results.tumorCellContent?.patient?.id = patientPseudonym
+ }
+ this.ngsReports?.forEach {
+ it.patient?.id = patientPseudonym
+ it.results?.simpleVariants?.forEach { it.patient?.id = patientPseudonym }
+ it.results?.copyNumberVariants?.forEach { it.patient?.id = patientPseudonym }
+ it.results?.dnaFusions?.forEach { it.patient?.id = patientPseudonym }
+ it.results?.rnaFusions?.forEach { it.patient?.id = patientPseudonym }
+ it.results?.tumorCellContent?.patient?.id = patientPseudonym
+ it.results?.brcaness?.patient?.id = patientPseudonym
+ it.results?.tmb?.patient?.id = patientPseudonym
+ it.results?.hrdScore?.patient?.id = patientPseudonym
+ }
+ this.ihcReports?.forEach {
+ it.patient?.id = patientPseudonym
+ it.results?.msiMmr?.forEach { it.patient?.id = patientPseudonym }
+ it.results?.proteinExpression?.forEach { it.patient?.id = patientPseudonym }
+ }
+ this.responses?.forEach { it.patient?.id = patientPseudonym }
+ this.specimens?.forEach { it.patient?.id = patientPseudonym }
+ this.priorDiagnosticReports?.forEach { it.patient?.id = patientPseudonym }
+ this.performanceStatus?.forEach { it.patient?.id = patientPseudonym }
+ this.systemicTherapies?.forEach { it.history?.forEach { it.patient?.id = patientPseudonym } }
+ this.followUps?.forEach { it.patient?.id = patientPseudonym }
+
+ this.msiFindings?.forEach { it -> 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")
}
+ }
}
/**
* Creates new hash of content IDs with given prefix except for patient IDs
*
- * @since 0.11.0
- *
* @param pseudonymizeService The pseudonymizeService to be used
* @return The MTB file containing rehashed content IDs
+ * @since 0.11.0
*/
infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) {
- val prefix = pseudonymizeService.prefix()
-
- fun anonymize(id: String): String {
- val hash = DigestUtils.sha256Hex("$prefix-$id").substring(0, 41).lowercase()
- return "$prefix$hash"
- }
-
- this.episodesOfCare?.forEach {
- it?.apply { id = id?.let(::anonymize) }
- it.diagnoses?.forEach { it ->
- it?.id = it.id?.let(::anonymize)
- }
- }
-
- this.carePlans?.onEach { carePlan ->
- carePlan?.apply {
- this.id = id?.let { anonymize(it) }
-
- this.geneticCounselingRecommendation?.apply {
- this.id = this.id?.let(::anonymize)
- }
- this.rebiopsyRequests?.forEach { it ->
- it.id = it.id?.let(::anonymize)
- it.tumorEntity?.id = it.tumorEntity?.id?.let(::anonymize)
- }
- this.histologyReevaluationRequests?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.specimen?.id = it.specimen?.id?.let(::anonymize)
- }
-
- this.medicationRecommendations?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.supportingVariants?.forEach { it ->
- it.variant?.id = it.variant?.id?.let(::anonymize)
- }
- it.reason?.id = it.reason?.id?.let(::anonymize)
- }
- this.reason?.id = this.reason?.id?.let(::anonymize)
- this.studyEnrollmentRecommendations?.forEach { it ->
- it?.reason?.id = it.reason?.id?.let(::anonymize)
- }
- this.procedureRecommendations?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.supportingVariants?.forEach { it ->
- it.variant?.id = it.variant?.id?.let(::anonymize)
- }
-
- it.reason?.id = it.reason?.id?.let(::anonymize)
-
- }
- this.studyEnrollmentRecommendations?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.supportingVariants.forEach { it ->
- it.variant?.id = it?.variant?.id?.let(::anonymize)
- }
- }
- }
- }
-
-
- this.responses?.forEach { it ->
-
- it?.id = it.id?.let(::anonymize)
- it?.therapy?.id = it.therapy?.id?.let(::anonymize)
-
- }
-
- this.diagnoses?.forEach { it ->
-
+ val prefix = pseudonymizeService.prefix()
+
+ fun anonymize(id: String): String {
+ val hash = DigestUtils.sha256Hex("$prefix-$id").substring(0, 41).lowercase()
+ return "$prefix$hash"
+ }
+
+ this.episodesOfCare?.forEach {
+ it?.apply { id = id?.let(::anonymize) }
+ it.diagnoses?.forEach { it -> it?.id = it.id?.let(::anonymize) }
+ }
+
+ this.carePlans?.onEach { carePlan ->
+ carePlan?.apply {
+ this.id = id?.let { anonymize(it) }
+
+ this.geneticCounselingRecommendation?.apply { this.id = this.id?.let(::anonymize) }
+ this.rebiopsyRequests?.forEach { it ->
+ it.id = it.id?.let(::anonymize)
+ it.tumorEntity?.id = it.tumorEntity?.id?.let(::anonymize)
+ }
+ this.histologyReevaluationRequests?.forEach { it ->
it.id = it?.id?.let(::anonymize)
- it.histology?.forEach { it -> it.id = it?.id?.let(::anonymize) }
- }
-
- this.ngsReports?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.results?.tumorCellContent?.id = it.results.tumorCellContent?.id?.let(::anonymize)
- it.results?.tumorCellContent?.specimen?.id =
- it.results?.tumorCellContent?.specimen?.id?.let(::anonymize)
- it.results?.rnaFusions?.forEach { it ->
- it?.id = it.id?.let(::anonymize)
- }
- it.results?.simpleVariants?.forEach { it ->
- it?.id = it.id?.let(::anonymize)
- it?.transcriptId?.value = it.transcriptId?.value?.let(::anonymize)
- }
- it.results?.tmb?.id = it.results?.tmb?.id?.let(::anonymize)
- it.results?.tmb?.specimen?.id = it.results?.tmb?.specimen?.id?.let(::anonymize)
-
- it.results?.brcaness?.id = it.results?.brcaness?.id?.let(::anonymize)
- it.results?.brcaness?.specimen?.id = it.results?.brcaness?.specimen?.id?.let(::anonymize)
- it.results?.copyNumberVariants?.forEach { it -> it?.id = it.id?.let(::anonymize) }
- it.results?.hrdScore?.id = it.results?.hrdScore?.id?.let(::anonymize)
- it.results?.hrdScore?.specimen?.id = it.results?.hrdScore?.specimen?.id?.let(::anonymize)
- it.results?.rnaSeqs?.forEach { it -> it?.id = it.id?.let(::anonymize) }
- it.results?.dnaFusions?.forEach { it -> it?.id = it.id?.let(::anonymize) }
- it.specimen?.id = it?.specimen?.id?.let(::anonymize)
-
- }
-
- this.histologyReports?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.results?.tumorCellContent?.id = it.results?.tumorCellContent?.id?.let(::anonymize)
- it.results?.tumorCellContent?.specimen?.id =
- it.results?.tumorCellContent?.specimen?.id?.let(::anonymize)
-
- it.results?.tumorMorphology?.id = it.results?.tumorMorphology?.id?.let(::anonymize)
- it.results?.tumorMorphology?.specimen?.id =
- it.results?.tumorMorphology?.specimen?.id?.let(::anonymize)
it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ }
- }
- this.claimResponses?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.claim?.id = it.claim?.id?.let(::anonymize)
- }
- this.claims?.forEach { it ->
-
- it.id = it?.id?.let(::anonymize)
- it.recommendation?.id = it.recommendation?.id?.let(::anonymize)
-
- }
- this.familyMemberHistories?.forEach { it -> it.id = it?.id?.let(::anonymize) }
- this.guidelineProcedures?.forEach { it ->
+ this.medicationRecommendations?.forEach { it ->
it.id = it?.id?.let(::anonymize)
+ it.supportingVariants?.forEach { it -> it.variant?.id = it.variant?.id?.let(::anonymize) }
it.reason?.id = it.reason?.id?.let(::anonymize)
- it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
-
- }
-
- this.guidelineTherapies?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.reason?.id = it.reason?.id?.let(::anonymize)
- it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
- }
- this.ihcReports?.forEach { it ->
- it.id = it?.id?.let(::anonymize)
- it.specimen?.id = it.specimen?.id?.let(::anonymize)
- it.results?.proteinExpression?.forEach { it -> it?.id = it.id.let(::anonymize) }
- }
-
- this.msiFindings?.forEach { it ->
-
+ }
+ this.reason?.id = this.reason?.id?.let(::anonymize)
+ this.studyEnrollmentRecommendations?.forEach { it ->
+ it?.reason?.id = it.reason?.id?.let(::anonymize)
+ }
+ this.procedureRecommendations?.forEach { it ->
it.id = it?.id?.let(::anonymize)
- it.specimen?.id = it.specimen?.id?.let(::anonymize)
- }
-
- this.performanceStatus?.forEach { it -> it.id = it?.id?.let(::anonymize) }
-
- this.priorDiagnosticReports?.forEach { it ->
+ it.supportingVariants?.forEach { it -> it.variant?.id = it.variant?.id?.let(::anonymize) }
+ it.reason?.id = it.reason?.id?.let(::anonymize)
+ }
+ this.studyEnrollmentRecommendations?.forEach { it ->
it.id = it?.id?.let(::anonymize)
- it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ it.supportingVariants.forEach { it -> it.variant?.id = it?.variant?.id?.let(::anonymize) }
+ }
}
-
- this.specimens?.forEach { it ->
-
- it.id = it?.id?.let(::anonymize)
- it.diagnosis?.id = it.diagnosis?.id?.let(::anonymize)
-
+ }
+
+ this.responses?.forEach { it ->
+ it?.id = it.id?.let(::anonymize)
+ it?.therapy?.id = it.therapy?.id?.let(::anonymize)
+ }
+
+ this.diagnoses?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.histology?.forEach { it -> it.id = it?.id?.let(::anonymize) }
+ }
+
+ this.ngsReports?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.results?.tumorCellContent?.id = it.results.tumorCellContent?.id?.let(::anonymize)
+ it.results?.tumorCellContent?.specimen?.id =
+ it.results?.tumorCellContent?.specimen?.id?.let(::anonymize)
+ it.results?.rnaFusions?.forEach { it -> it?.id = it.id?.let(::anonymize) }
+ it.results?.simpleVariants?.forEach { it ->
+ it?.id = it.id?.let(::anonymize)
+ it?.transcriptId?.value = it.transcriptId?.value?.let(::anonymize)
}
-
- this.systemicTherapies?.forEach { it ->
-
- it.history?.forEach { it ->
-
- it.id = it?.id?.let(::anonymize)
- it.reason?.id = it.reason?.id?.let(::anonymize)
- it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
- }
-
+ it.results?.tmb?.id = it.results?.tmb?.id?.let(::anonymize)
+ it.results?.tmb?.specimen?.id = it.results?.tmb?.specimen?.id?.let(::anonymize)
+
+ it.results?.brcaness?.id = it.results?.brcaness?.id?.let(::anonymize)
+ it.results?.brcaness?.specimen?.id = it.results?.brcaness?.specimen?.id?.let(::anonymize)
+ it.results?.copyNumberVariants?.forEach { it -> it?.id = it.id?.let(::anonymize) }
+ it.results?.hrdScore?.id = it.results?.hrdScore?.id?.let(::anonymize)
+ it.results?.hrdScore?.specimen?.id = it.results?.hrdScore?.specimen?.id?.let(::anonymize)
+ it.results?.rnaSeqs?.forEach { it -> it?.id = it.id?.let(::anonymize) }
+ it.results?.dnaFusions?.forEach { it -> it?.id = it.id?.let(::anonymize) }
+ it.specimen?.id = it?.specimen?.id?.let(::anonymize)
+ }
+
+ this.histologyReports?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.results?.tumorCellContent?.id = it.results?.tumorCellContent?.id?.let(::anonymize)
+ it.results?.tumorCellContent?.specimen?.id =
+ it.results?.tumorCellContent?.specimen?.id?.let(::anonymize)
+
+ it.results?.tumorMorphology?.id = it.results?.tumorMorphology?.id?.let(::anonymize)
+ it.results?.tumorMorphology?.specimen?.id =
+ it.results?.tumorMorphology?.specimen?.id?.let(::anonymize)
+ it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ }
+ this.claimResponses?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.claim?.id = it.claim?.id?.let(::anonymize)
+ }
+ this.claims?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.recommendation?.id = it.recommendation?.id?.let(::anonymize)
+ }
+ this.familyMemberHistories?.forEach { it -> it.id = it?.id?.let(::anonymize) }
+ this.guidelineProcedures?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.reason?.id = it.reason?.id?.let(::anonymize)
+ it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
+ }
+
+ this.guidelineTherapies?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.reason?.id = it.reason?.id?.let(::anonymize)
+ it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
+ }
+ this.ihcReports?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ it.results?.proteinExpression?.forEach { it -> it?.id = it.id.let(::anonymize) }
+ }
+
+ this.msiFindings?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ }
+
+ this.performanceStatus?.forEach { it -> it.id = it?.id?.let(::anonymize) }
+
+ this.priorDiagnosticReports?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.specimen?.id = it.specimen?.id?.let(::anonymize)
+ }
+
+ this.specimens?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.diagnosis?.id = it.diagnosis?.id?.let(::anonymize)
+ }
+
+ this.systemicTherapies?.forEach { it ->
+ it.history?.forEach { it ->
+ it.id = it?.id?.let(::anonymize)
+ it.reason?.id = it.reason?.id?.let(::anonymize)
+ it.basedOn?.id = it.basedOn?.id?.let(::anonymize)
}
+ }
}
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()
- }
+ // 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()
+ }
}
infix fun Mtb.addGenomDeTan(pseudonymizeService: PseudonymizeService) {
- this.metadata?.transferTan = pseudonymizeService.genomDeTan(PatientId(this.patient.id))
+ this.metadata?.transferTan = pseudonymizeService.genomDeTan(PatientId(this.patient.id))
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/security/TokenService.kt b/src/main/kotlin/dev/dnpm/etl/processor/security/TokenService.kt
index 44b04e8..fdaa7d2 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/security/TokenService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/security/TokenService.kt
@@ -20,6 +20,8 @@
package dev.dnpm.etl.processor.security
import jakarta.annotation.PostConstruct
+import java.time.Instant
+import java.util.*
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository
@@ -27,57 +29,50 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.security.core.userdetails.User
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
-import java.time.Instant
-import java.util.*
class TokenService(
private val userDetailsManager: InMemoryUserDetailsManager,
private val passwordEncoder: PasswordEncoder,
- private val tokenRepository: TokenRepository
+ private val tokenRepository: TokenRepository,
) {
- @PostConstruct
- fun setup() {
- tokenRepository.findAll().forEach {
- userDetailsManager.createUser(
- User.withUsername(it.username)
- .password(it.password)
- .roles("MTBFILE")
- .build()
- )
- }
+ @PostConstruct
+ fun setup() {
+ tokenRepository.findAll().forEach {
+ userDetailsManager.createUser(
+ User.withUsername(it.username).password(it.password).roles("MTBFILE").build()
+ )
}
+ }
- fun addToken(name: String): Result<String> {
- val username = name.lowercase().replace("""[^a-z0-9]""".toRegex(), "")
- if (userDetailsManager.userExists(username)) {
- return Result.failure(RuntimeException("Cannot use token name"))
- }
+ fun addToken(name: String): Result<String> {
+ val username = name.lowercase().replace("""[^a-z0-9]""".toRegex(), "")
+ if (userDetailsManager.userExists(username)) {
+ return Result.failure(RuntimeException("Cannot use token name"))
+ }
- val password = Base64.getEncoder().encodeToString(UUID.randomUUID().toString().encodeToByteArray())
- val encodedPassword = passwordEncoder.encode(password).toString()
+ val password =
+ Base64.getEncoder().encodeToString(UUID.randomUUID().toString().encodeToByteArray())
+ val encodedPassword = passwordEncoder.encode(password).toString()
- userDetailsManager.createUser(
- User.withUsername(username)
- .password(encodedPassword)
- .roles("MTBFILE")
- .build()
- )
+ userDetailsManager.createUser(
+ User.withUsername(username).password(encodedPassword).roles("MTBFILE").build()
+ )
- tokenRepository.save(Token(name = name, username = username, password = encodedPassword))
+ tokenRepository.save(Token(name = name, username = username, password = encodedPassword))
- return Result.success("$username:$password")
- }
+ return Result.success("$username:$password")
+ }
- fun deleteToken(id: Long) {
- val token = tokenRepository.findByIdOrNull(id) ?: return
- userDetailsManager.deleteUser(token.username)
- tokenRepository.delete(token)
- }
+ fun deleteToken(id: Long) {
+ val token = tokenRepository.findByIdOrNull(id) ?: return
+ userDetailsManager.deleteUser(token.username)
+ tokenRepository.delete(token)
+ }
- fun findAll(): List<Token> {
- return tokenRepository.findAll().toList()
- }
+ fun findAll(): List<Token> {
+ return tokenRepository.findAll().toList()
+ }
}
@Table("token")
@@ -86,7 +81,7 @@ data class Token(
val name: String,
val username: String,
val password: String,
- val createdAt: Instant = Instant.now()
+ val createdAt: Instant = Instant.now(),
)
-interface TokenRepository : CrudRepository<Token, Long> \ No newline at end of file
+interface TokenRepository : CrudRepository<Token, Long>
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt
index a1d45c8..bfe966a 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt
@@ -20,26 +20,21 @@
package dev.dnpm.etl.processor.security
+import java.util.*
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository
-import java.util.*
@Table("user_role")
-data class UserRole(
- @Id val id: Long? = null,
- val username: String,
- var role: Role = Role.GUEST
-)
+data class UserRole(@Id val id: Long? = null, val username: String, var role: Role = Role.GUEST)
enum class Role(val value: String) {
- GUEST("guest"),
- USER("user"),
- ADMIN("admin")
+ GUEST("guest"),
+ USER("user"),
+ ADMIN("admin"),
}
interface UserRoleRepository : CrudRepository<UserRole, Long> {
- fun findByUsername(username: String): Optional<UserRole>
-
-} \ No newline at end of file
+ fun findByUsername(username: String): Optional<UserRole>
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/security/UserRoleService.kt b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRoleService.kt
index 174f8a9..bf46b84 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/security/UserRoleService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRoleService.kt
@@ -25,9 +25,12 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser
class UserRoleService(
private val userRoleRepository: UserRoleRepository,
- private val sessionRegistry: SessionRegistry
+ private val sessionRegistry: SessionRegistry,
) {
- fun updateUserRole(id: Long, role: Role) {
+ fun updateUserRole(
+ id: Long,
+ role: Role,
+ ) {
val userRole = userRoleRepository.findByIdOrNull(id) ?: return
userRole.role = role
userRoleRepository.save(userRole)
@@ -40,19 +43,13 @@ class UserRoleService(
expireSessionFor(userRole.username)
}
- fun findAll(): List<UserRole> {
- return userRoleRepository.findAll().toList()
- }
+ fun findAll(): List<UserRole> = userRoleRepository.findAll().toList()
private fun expireSessionFor(username: String) {
sessionRegistry.allPrincipals
.filterIsInstance<OidcUser>()
.filter { it.preferredUsername == username }
- .flatMap {
- sessionRegistry.getAllSessions(it, true)
- }
- .onEach {
- it.expireNow()
- }
+ .flatMap { sessionRegistry.getAllSessions(it, true) }
+ .onEach { it.expireNow() }
}
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
index b420d1f..8437962 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
@@ -11,6 +11,10 @@ import dev.dnpm.etl.processor.consent.IConsentService
import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
import dev.pcvolkmer.mv64e.mtb.*
+import java.io.IOException
+import java.time.Clock
+import java.time.Instant
+import java.util.*
import org.apache.commons.lang3.NotImplementedException
import org.hl7.fhir.r4.model.*
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
@@ -19,10 +23,6 @@ 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(
@@ -30,248 +30,271 @@ class ConsentProcessor(
private val gIcsConfigProperties: GIcsConfigProperties,
private val objectMapper: ObjectMapper,
private val fhirContext: FhirContext,
- private val consentService: IConsentService
+ private val consentService: IConsentService,
) {
- private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
-
- /**
- * In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
- *
- * Logic:
- * * <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 MtbFileConsentService) {
- // consent check is disabled
- return true
- }
-
- mtbFile.ensureMetaDataIsInitialized()
-
- val personIdentifierValue = mtbFile.patient.id
- val requestDate = Date.from(Instant.now(Clock.systemUTC()))
+ private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
+
+ /**
+ * In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
+ *
+ * Logic:
+ * * <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 MtbFileConsentService) {
+ // consent check is disabled
+ return true
+ }
- // 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
+ mtbFile.ensureMetaDataIsInitialized()
- /*
- * broad consent
- */
- val broadConsent = consentService.getConsent(
- personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT
- )
- val broadConsentHasBeenAsked = broadConsent.entry.isNotEmpty()
+ val personIdentifierValue = mtbFile.patient.id
+ val requestDate = Date.from(Instant.now(Clock.systemUTC()))
- // fast exit - if patient has not been asked, we can skip and exit
- if (!broadConsentHasBeenAsked) return false
+ // 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
- val genomeDeConsent = consentService.getConsent(
- personIdentifierValue, requestDate, ConsentDomain.MODELLVORHABEN_64E
+ /*
+ * broad consent
+ */
+ val broadConsent =
+ consentService.getConsent(personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT)
+ val broadConsentHasBeenAsked = broadConsent.entry.isNotEmpty()
+
+ // fast exit - if patient has not been asked, we can skip and exit
+ if (!broadConsentHasBeenAsked) return false
+
+ val genomeDeConsent =
+ consentService.getConsent(
+ personIdentifierValue,
+ requestDate,
+ ConsentDomain.MODELLVORHABEN_64E,
)
- addGenomeDbProvisions(mtbFile, genomeDeConsent)
+ addGenomeDbProvisions(mtbFile, genomeDeConsent)
- if (genomeDeConsent.entry.isNotEmpty()) setGenomDeSubmissionType(mtbFile)
+ if (genomeDeConsent.entry.isNotEmpty()) setGenomDeSubmissionType(mtbFile)
- embedBroadConsentResources(mtbFile, broadConsent)
+ embedBroadConsentResources(mtbFile, broadConsent)
- val broadConsentStatus = getProvisionTypeByPolicyCode(
- broadConsent, requestDate, ConsentDomain.BROAD_CONSENT
- )
-
- val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
- genomeDeConsent, requestDate, ConsentDomain.MODELLVORHABEN_64E
- )
+ val broadConsentStatus =
+ getProvisionTypeByPolicyCode(broadConsent, requestDate, ConsentDomain.BROAD_CONSENT)
- if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
- // bc not asked
- return false
- }
- if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus || Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus) return true
+ val genomDeSequencingStatus =
+ getProvisionTypeByPolicyCode(genomeDeConsent, requestDate, ConsentDomain.MODELLVORHABEN_64E)
- return false
+ if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
+ // bc not asked
+ return false
}
-
- fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) {
- for (entry in broadConsent.entry) {
- val resource = entry.resource
- 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)
- }
- }
+ if (
+ Consent.ConsentProvisionType.PERMIT == broadConsentStatus ||
+ Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus
+ )
+ return true
+
+ return false
+ }
+
+ fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) {
+ for (entry in broadConsent.entry) {
+ val resource = entry.resource
+ 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.entry) {
- val resource = entry.resource
- if (resource !is Consent) {
- continue
- }
-
- // We expect only one provision in collection, therefore get first or none
- val provisions = resource.provision.provision
- if (provisions.isEmpty()) {
- continue
- }
-
- val provisionComponent: ProvisionComponent = provisions.first()
- val provisionCode = getProvisionCode(provisionComponent)
- 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.dateTime
- }
-
- val provision = Provision.builder()
- .type(ConsentProvision.valueOf(provisionComponent.type.name))
- .date(provisionComponent.period.start)
- .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.isNotEmpty()) {
- mtbFile.metadata.modelProjectConsent.version =
- gIcsConfigProperties.genomeDeConsentVersion
- }
+ }
+
+ fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) {
+ for (entry in consentGnomeDe.entry) {
+ val resource = entry.resource
+ if (resource !is Consent) {
+ continue
+ }
+
+ // We expect only one provision in collection, therefore get first or none
+ val provisions = resource.provision.provision
+ if (provisions.isEmpty()) {
+ continue
+ }
+
+ val provisionComponent: ProvisionComponent = provisions.first()
+ val provisionCode = getProvisionCode(provisionComponent)
+ 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.dateTime
+ }
+
+ val provision =
+ Provision.builder()
+ .type(ConsentProvision.valueOf(provisionComponent.type.name))
+ .date(provisionComponent.period.start)
+ .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(),
+ )
}
- }
+ }
- private fun getProvisionCode(provisionComponent: ProvisionComponent): String? {
- var provisionCode: String? = null
- if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
- val codableConcept: CodeableConcept = provisionComponent.code.first()
- if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
- provisionCode = codableConcept.coding.first().code
- }
- }
- return provisionCode
+ if (mtbFile.metadata.modelProjectConsent.provisions.isNotEmpty()) {
+ mtbFile.metadata.modelProjectConsent.version = gIcsConfigProperties.genomeDeConsentVersion
+ }
}
-
- private fun setGenomDeSubmissionType(mtbFile: Mtb) {
- if (appConfigProperties.genomDeTestSubmission) {
- mtbFile.metadata.type = MvhSubmissionType.TEST
- logger.info("genomeDe submission mit TEST")
- } else {
- mtbFile.metadata.type = when (mtbFile.metadata.type) {
- null -> MvhSubmissionType.INITIAL
- else -> mtbFile.metadata.type
- }
- }
+ }
+
+ private fun getProvisionCode(provisionComponent: ProvisionComponent): String? {
+ var provisionCode: String? = null
+ if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
+ val codableConcept: CodeableConcept = provisionComponent.code.first()
+ if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
+ provisionCode = codableConcept.coding.first().code
+ }
}
-
- /**
- * @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.BROAD_CONSENT == consentDomain) {
- code = gIcsConfigProperties.broadConsentPolicyCode
- system = gIcsConfigProperties.broadConsentPolicySystem
- } else if (ConsentDomain.MODELLVORHABEN_64E == consentDomain) {
- code = gIcsConfigProperties.genomeDePolicyCode
- system = gIcsConfigProperties.genomeDePolicySystem
- } else {
- throw NotImplementedException("unknown consent domain " + consentDomain.name)
- }
-
- val provisionTypeByPolicyCode = getProvisionTypeByPolicyCode(
- consentBundle, code, system, requestDate
- )
- return provisionTypeByPolicyCode
+ return provisionCode
+ }
+
+ private fun setGenomDeSubmissionType(mtbFile: Mtb) {
+ if (appConfigProperties.genomDeTestSubmission) {
+ mtbFile.metadata.type = MvhSubmissionType.TEST
+ logger.info("genomeDe submission mit TEST")
+ } else {
+ mtbFile.metadata.type =
+ when (mtbFile.metadata.type) {
+ null -> MvhSubmissionType.INITIAL
+ else -> mtbFile.metadata.type
+ }
+ }
+ }
+
+ /**
+ * @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.BROAD_CONSENT == consentDomain) {
+ code = gIcsConfigProperties.broadConsentPolicyCode
+ system = gIcsConfigProperties.broadConsentPolicySystem
+ } else if (ConsentDomain.MODELLVORHABEN_64E == consentDomain) {
+ code = gIcsConfigProperties.genomeDePolicyCode
+ system = gIcsConfigProperties.genomeDePolicySystem
+ } else {
+ throw NotImplementedException("unknown consent domain " + consentDomain.name)
}
- /**
- * @param consentBundle consent resource
- * @param targetCode policyRule and provision code value
- * @param targetSystem 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, targetCode: String?, targetSystem: String?, requestDate: Date?
- ): Consent.ConsentProvisionType {
- val entriesOfInterest = consentBundle.entry.filter { entry ->
- val isConsentResource =
- entry.resource.isResource && entry.resource.resourceType == ResourceType.Consent
- val consentIsActive = (entry.resource as Consent).status == ConsentState.ACTIVE
-
- val provisions = (entry.resource as Consent).provision.provision
-
- val isValidCoding = checkProvisionExist(
- targetCode, targetSystem, provisions
- )
-
- isConsentResource && consentIsActive && isValidCoding && isRequestDateInRange(requestDate, (entry.resource as Consent).provision.period)
- }.map { entry: BundleEntryComponent ->
- val consent = (entry.getResource() as Consent)
- consent.provision.provision.filter { subProvision ->
- isRequestDateInRange(requestDate, subProvision.period)
- // search coding entries of current provision for code and system
- subProvision.code.map { c -> c.coding }.flatten().any { code ->
- targetCode.equals(code.code) && targetSystem.equals(code.system)
- }
- }.map { subProvision ->
- subProvision
+ val provisionTypeByPolicyCode =
+ getProvisionTypeByPolicyCode(consentBundle, code, system, requestDate)
+ return provisionTypeByPolicyCode
+ }
+
+ /**
+ * @param consentBundle consent resource
+ * @param targetCode policyRule and provision code value
+ * @param targetSystem 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,
+ targetCode: String?,
+ targetSystem: String?,
+ requestDate: Date?,
+ ): Consent.ConsentProvisionType {
+ val entriesOfInterest =
+ consentBundle.entry
+ .filter { entry ->
+ val isConsentResource =
+ entry.resource.isResource && entry.resource.resourceType == ResourceType.Consent
+ val consentIsActive = (entry.resource as Consent).status == ConsentState.ACTIVE
+
+ val provisions = (entry.resource as Consent).provision.provision
+
+ val isValidCoding = checkProvisionExist(targetCode, targetSystem, provisions)
+
+ isConsentResource &&
+ consentIsActive &&
+ isValidCoding &&
+ isRequestDateInRange(requestDate, (entry.resource as Consent).provision.period)
}
- }.flatten()
+ .map { entry: BundleEntryComponent ->
+ val consent = (entry.getResource() as Consent)
+ consent.provision.provision
+ .filter { subProvision ->
+ isRequestDateInRange(requestDate, subProvision.period)
+ // search coding entries of current provision for code and system
+ subProvision.code
+ .map { c -> c.coding }
+ .flatten()
+ .any { code ->
+ targetCode.equals(code.code) && targetSystem.equals(code.system)
+ }
+ }
+ .map { subProvision -> subProvision }
+ }
+ .flatten()
- if (entriesOfInterest.isNotEmpty()) {
- return entriesOfInterest.first().type
- }
- return Consent.ConsentProvisionType.NULL
+ if (entriesOfInterest.isNotEmpty()) {
+ return entriesOfInterest.first().type
}
-
- fun checkProvisionExist(
- researchAllowedPolicyOid: String?,
- researchAllowedPolicySystem: String?,
- provisions: Collection<ProvisionComponent>
- ): Boolean {
- return provisions.any { provision ->
- provision.code.any { codeableConcept -> codeableConcept.coding.any { it.system == researchAllowedPolicySystem && it.code == researchAllowedPolicyOid } }
+ return Consent.ConsentProvisionType.NULL
+ }
+
+ fun checkProvisionExist(
+ researchAllowedPolicyOid: String?,
+ researchAllowedPolicySystem: String?,
+ provisions: Collection<ProvisionComponent>,
+ ): Boolean {
+ return provisions.any { provision ->
+ provision.code.any { codeableConcept ->
+ codeableConcept.coding.any {
+ it.system == researchAllowedPolicySystem && it.code == researchAllowedPolicyOid
}
+ }
}
+ }
- fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean {
- val isRequestDateAfterOrEqualStart = provPeriod.start.compareTo(requestDate)
- val isRequestDateBeforeOrEqualEnd = provPeriod.end.compareTo(requestDate)
- return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
- }
-
+ fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean {
+ val isRequestDateAfterOrEqualStart = provPeriod.start.compareTo(requestDate)
+ val isRequestDateBeforeOrEqualEnd = provPeriod.end.compareTo(requestDate)
+ return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
+ }
}
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 07d8a8d..4721f75 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
@@ -38,14 +38,14 @@ import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
import dev.pcvolkmer.mv64e.mtb.Mtb
+import java.time.Instant
+import java.util.*
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.time.Instant
-import java.util.*
@Service
class RequestProcessor(
@@ -56,163 +56,178 @@ class RequestProcessor(
private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher,
private val appConfigProperties: AppConfigProperties,
- private val consentProcessor: ConsentProcessor?
+ private val consentProcessor: ConsentProcessor?,
) {
- private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
-
- fun processMtbFile(mtbFile: Mtb) {
- processMtbFile(mtbFile, randomRequestId())
+ private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
+
+ fun processMtbFile(mtbFile: Mtb) {
+ processMtbFile(mtbFile, randomRequestId())
+ }
+
+ fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
+ val pid = PatientId(extractPatientIdentifier(mtbFile))
+
+ val isConsentOk =
+ consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
+ consentProcessor == null
+ if (isConsentOk) {
+ if (isGenomDeConsented(mtbFile)) {
+ mtbFile addGenomDeTan pseudonymizeService
+ }
+ 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)
+ )
}
+ }
-
- fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
- val pid = PatientId(extractPatientIdentifier(mtbFile))
-
- val isConsentOk =
- consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null
- if (isConsentOk) {
- if (isGenomDeConsented(mtbFile)) {
- mtbFile addGenomDeTan pseudonymizeService
- }
- 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 isGenomDeConsented(mtbFile: Mtb): Boolean {
- val isModelProjectConsented = mtbFile.metadata?.modelProjectConsent?.provisions?.any { p ->
- p.purpose == ModelProjectConsentPurpose.SEQUENCING && p.type == ConsentProvision.PERMIT
+ private fun isGenomDeConsented(mtbFile: Mtb): Boolean {
+ val isModelProjectConsented =
+ mtbFile.metadata?.modelProjectConsent?.provisions?.any { p ->
+ p.purpose == ModelProjectConsentPurpose.SEQUENCING && p.type == ConsentProvision.PERMIT
} == true
- return isModelProjectConsented
- }
-
- private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
- requestService.save(
- Request(
- request.requestId,
- request.patientPseudonym(),
- pid,
- fingerprint(request),
- RequestType.MTB_FILE,
- RequestStatus.UNKNOWN
- )
+ return isModelProjectConsented
+ }
+
+ private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
+ requestService.save(
+ Request(
+ request.requestId,
+ request.patientPseudonym(),
+ pid,
+ fingerprint(request),
+ RequestType.MTB_FILE,
+ RequestStatus.UNKNOWN,
)
+ )
- if (appConfigProperties.duplicationDetection && isDuplication(request)) {
- applicationEventPublisher.publishEvent(
- ResponseEvent(
- request.requestId, Instant.now(), RequestStatus.DUPLICATION
- )
- )
- return
- }
-
- val responseStatus = sender.send(request)
-
- applicationEventPublisher.publishEvent(
- ResponseEvent(
- request.requestId,
- Instant.now(),
- responseStatus.status,
- when (responseStatus.status) {
- RequestStatus.ERROR, RequestStatus.WARNING -> Optional.of(responseStatus.body)
- else -> Optional.empty()
- }
- )
- )
+ if (appConfigProperties.duplicationDetection && isDuplication(request)) {
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(request.requestId, Instant.now(), RequestStatus.DUPLICATION)
+ )
+ return
}
- private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
- val patientPseudonym = when (pseudonymizedMtbFileRequest) {
- is DnpmV2MtbFileRequest -> PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
- }
-
- val lastMtbFileRequestForPatient =
- requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
- val isLastRequestDeletion =
- requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
-
- return null != lastMtbFileRequestForPatient && !isLastRequestDeletion && lastMtbFileRequestForPatient.fingerprint == fingerprint(
- pseudonymizedMtbFileRequest
+ val responseStatus = sender.send(request)
+
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(
+ request.requestId,
+ Instant.now(),
+ responseStatus.status,
+ when (responseStatus.status) {
+ RequestStatus.ERROR,
+ RequestStatus.WARNING -> Optional.of(responseStatus.body)
+ else -> Optional.empty()
+ },
)
- }
-
- fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
- processDeletion(patientId, randomRequestId(), isConsented)
- }
-
- 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,
- patientPseudonym,
- patientId,
- fingerprint(patientPseudonym.value),
- RequestType.DELETE,
- requestStatus
- )
- )
-
- val responseStatus = sender.send(DeleteRequest(requestId, patientPseudonym))
-
- applicationEventPublisher.publishEvent(
- ResponseEvent(
- requestId, Instant.now(), responseStatus.status, when (responseStatus.status) {
- RequestStatus.WARNING, RequestStatus.ERROR -> Optional.of(responseStatus.body)
- else -> Optional.empty()
- }
- )
- )
-
- } catch (_: Exception) {
- requestService.save(
- Request(
- uuid = requestId,
- patientPseudonym = emptyPatientPseudonym(),
- pid = patientId,
- fingerprint = Fingerprint.empty(),
- status = RequestStatus.ERROR,
- type = RequestType.DELETE,
- report = Report("Fehler bei der Pseudonymisierung")
- )
- )
+ )
+ }
+
+ private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
+ val patientPseudonym =
+ when (pseudonymizedMtbFileRequest) {
+ is DnpmV2MtbFileRequest ->
+ PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
}
- }
- private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
- return when (request) {
- is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
- }
+ val lastMtbFileRequestForPatient =
+ requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
+ val isLastRequestDeletion =
+ requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
+
+ return null != lastMtbFileRequestForPatient &&
+ !isLastRequestDeletion &&
+ lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
+ }
+
+ fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
+ processDeletion(patientId, randomRequestId(), isConsented)
+ }
+
+ 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,
+ patientPseudonym,
+ patientId,
+ fingerprint(patientPseudonym.value),
+ RequestType.DELETE,
+ requestStatus,
+ )
+ )
+
+ val responseStatus = sender.send(DeleteRequest(requestId, patientPseudonym))
+
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(
+ requestId,
+ Instant.now(),
+ responseStatus.status,
+ when (responseStatus.status) {
+ RequestStatus.WARNING,
+ RequestStatus.ERROR -> Optional.of(responseStatus.body)
+ else -> Optional.empty()
+ },
+ )
+ )
+ } catch (_: Exception) {
+ requestService.save(
+ Request(
+ uuid = requestId,
+ patientPseudonym = emptyPatientPseudonym(),
+ pid = patientId,
+ fingerprint = Fingerprint.empty(),
+ status = RequestStatus.ERROR,
+ type = RequestType.DELETE,
+ report = Report("Fehler bei der Pseudonymisierung"),
+ )
+ )
}
+ }
- private fun fingerprint(s: String): Fingerprint {
- return Fingerprint(
- Base32().encodeAsString(DigestUtils.sha256(s)).replace("=", "").lowercase()
- )
+ private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
+ return when (request) {
+ is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
}
-
+ }
+
+ private fun fingerprint(s: String): Fingerprint {
+ return Fingerprint(
+ 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/RequestService.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt
index 757b353..e7cb95f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt
@@ -22,55 +22,63 @@ package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.*
+import java.util.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
-import java.util.*
@Service
-class RequestService(
- private val requestRepository: RequestRepository
-) {
+class RequestService(private val requestRepository: RequestRepository) {
- fun save(request: Request) = requestRepository.save(request)
+ fun save(request: Request) = requestRepository.save(request)
- fun findAll(): Iterable<Request> = requestRepository.findAll()
+ fun findAll(): Iterable<Request> = requestRepository.findAll()
- fun findAll(pageable: Pageable): Page<Request> = requestRepository.findAll(pageable)
+ fun findAll(pageable: Pageable): Page<Request> = requestRepository.findAll(pageable)
- fun findByUuid(uuid: RequestId): Optional<Request> =
- requestRepository.findByUuidEquals(uuid)
+ fun findByUuid(uuid: RequestId): Optional<Request> = requestRepository.findByUuidEquals(uuid)
- fun findRequestByPatientId(patientPseudonym: PatientPseudonym, pageable: Pageable): Page<Request> = requestRepository.findRequestByPatientPseudonym(patientPseudonym, pageable)
+ fun findRequestByPatientId(
+ patientPseudonym: PatientPseudonym,
+ pageable: Pageable,
+ ): Page<Request> = requestRepository.findRequestByPatientPseudonym(patientPseudonym, pageable)
- fun allRequestsByPatientPseudonym(patientPseudonym: PatientPseudonym) = requestRepository
- .findAllByPatientPseudonymOrderByProcessedAtDesc(patientPseudonym)
+ fun allRequestsByPatientPseudonym(patientPseudonym: PatientPseudonym) =
+ requestRepository.findAllByPatientPseudonymOrderByProcessedAtDesc(patientPseudonym)
- fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: PatientPseudonym) =
- Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym))
+ fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: PatientPseudonym) =
+ Companion.lastMtbFileRequestForPatientPseudonym(
+ allRequestsByPatientPseudonym(patientPseudonym)
+ )
- fun isLastRequestWithKnownStatusDeletion(patientPseudonym: PatientPseudonym) =
- Companion.isLastRequestWithKnownStatusDeletion(allRequestsByPatientPseudonym(patientPseudonym))
+ fun isLastRequestWithKnownStatusDeletion(patientPseudonym: PatientPseudonym) =
+ Companion.isLastRequestWithKnownStatusDeletion(
+ allRequestsByPatientPseudonym(patientPseudonym)
+ )
- fun countStates(): Iterable<CountedState> = requestRepository.countStates()
+ fun countStates(): Iterable<CountedState> = requestRepository.countStates()
- fun countDeleteStates(): Iterable<CountedState> = requestRepository.countDeleteStates()
+ fun countDeleteStates(): Iterable<CountedState> = requestRepository.countDeleteStates()
- fun findPatientUniqueStates(): List<CountedState> = requestRepository.findPatientUniqueStates()
+ fun findPatientUniqueStates(): List<CountedState> = requestRepository.findPatientUniqueStates()
- fun findPatientUniqueDeleteStates(): List<CountedState> = requestRepository.findPatientUniqueDeleteStates()
+ fun findPatientUniqueDeleteStates(): List<CountedState> =
+ requestRepository.findPatientUniqueDeleteStates()
- companion object {
+ companion object {
- fun lastMtbFileRequestForPatientPseudonym(allRequests: List<Request>) = allRequests
+ fun lastMtbFileRequestForPatientPseudonym(allRequests: List<Request>) =
+ allRequests
.filter { it.type == RequestType.MTB_FILE }
.sortedByDescending { it.processedAt }
- .firstOrNull { it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING }
+ .firstOrNull {
+ it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING
+ }
- fun isLastRequestWithKnownStatusDeletion(allRequests: List<Request>) = allRequests
+ fun isLastRequestWithKnownStatusDeletion(allRequests: List<Request>) =
+ allRequests
.filter { it.status != RequestStatus.UNKNOWN }
- .maxByOrNull { it.processedAt }?.type == RequestType.DELETE
-
- }
-
-} \ No newline at end of file
+ .maxByOrNull { it.processedAt }
+ ?.type == RequestType.DELETE
+ }
+}
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 fb82647..190cefe 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt
@@ -22,79 +22,76 @@ package dev.dnpm.etl.processor.services
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.Report
import dev.dnpm.etl.processor.monitoring.RequestStatus
+import java.time.Instant
+import java.util.*
import org.slf4j.LoggerFactory
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Service
import reactor.core.publisher.Sinks
-import java.time.Instant
-import java.util.*
@Service
class ResponseProcessor(
private val requestService: RequestService,
- private val statisticsUpdateProducer: Sinks.Many<Any>
+ private val statisticsUpdateProducer: Sinks.Many<Any>,
) {
- private val logger = LoggerFactory.getLogger(ResponseProcessor::class.java)
+ private val logger = LoggerFactory.getLogger(ResponseProcessor::class.java)
- @EventListener(classes = [ResponseEvent::class])
- fun handleResponseEvent(event: ResponseEvent) {
- requestService.findByUuid(event.requestUuid).ifPresentOrElse({
- it.processedAt = event.timestamp
- it.status = event.status
+ @EventListener(classes = [ResponseEvent::class])
+ fun handleResponseEvent(event: ResponseEvent) {
+ requestService
+ .findByUuid(event.requestUuid)
+ .ifPresentOrElse(
+ {
+ it.processedAt = event.timestamp
+ it.status = event.status
- when (event.status) {
+ when (event.status) {
RequestStatus.SUCCESS -> {
- it.report = Report(
- "Keine Probleme erkannt",
- )
+ it.report =
+ Report(
+ "Keine Probleme erkannt",
+ )
}
RequestStatus.WARNING -> {
- it.report = Report(
- "Warnungen über mangelhafte Daten",
- event.body.orElse("")
- )
+ it.report = Report("Warnungen über mangelhafte Daten", event.body.orElse(""))
}
RequestStatus.ERROR -> {
- it.report = Report(
- "Fehler bei der Datenübertragung oder Inhalt nicht verarbeitbar",
- event.body.orElse("")
- )
+ it.report =
+ Report(
+ "Fehler bei der Datenübertragung oder Inhalt nicht verarbeitbar",
+ event.body.orElse(""),
+ )
}
RequestStatus.DUPLICATION -> {
- it.report = Report(
- "Duplikat erkannt"
- )
+ it.report = Report("Duplikat erkannt")
}
RequestStatus.NO_CONSENT -> {
- it.report = Report(
- "Einwilligung Status fehlt, widerrufen oder ungeklärt."
- )
+ it.report = Report("Einwilligung Status fehlt, widerrufen oder ungeklärt.")
}
else -> {
- logger.error("Cannot process response: Unknown response!")
- return@ifPresentOrElse
+ logger.error("Cannot process response: Unknown response!")
+ return@ifPresentOrElse
}
- }
-
- requestService.save(it)
+ }
- statisticsUpdateProducer.emitNext("", Sinks.EmitFailureHandler.FAIL_FAST)
- }, {
- logger.error("Response for unknown request '${event.requestUuid}'!")
- })
- }
+ requestService.save(it)
+ statisticsUpdateProducer.emitNext("", Sinks.EmitFailureHandler.FAIL_FAST)
+ },
+ { logger.error("Response for unknown request '${event.requestUuid}'!") },
+ )
+ }
}
data class ResponseEvent(
val requestUuid: RequestId,
val timestamp: Instant,
val status: RequestStatus,
- val body: Optional<String> = Optional.empty()
-) \ No newline at end of file
+ val body: Optional<String> = Optional.empty(),
+)
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
index 8f1081e..df8ac3d 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
@@ -24,8 +24,10 @@ import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException
import dev.pcvolkmer.mv64e.mtb.Mtb
-class TransformationService(private val objectMapper: ObjectMapper, private val transformations: List<Transformation>) {
-
+class TransformationService(
+ private val objectMapper: ObjectMapper,
+ private val transformations: List<Transformation>,
+) {
fun transform(mtbFile: Mtb): Mtb {
val json = transform(objectMapper.writeValueAsString(mtbFile))
return objectMapper.readValue(json, Mtb::class.java)
@@ -41,12 +43,24 @@ class TransformationService(private val objectMapper: ObjectMapper, private val
val before = transformation.path.substringBeforeLast(".")
val last = transformation.path.substringAfterLast(".")
- val existingValue = if (transformation.existingValue is Number) transformation.existingValue else transformation.existingValue.toString()
- val newValue = if (transformation.newValue is Number) transformation.newValue else transformation.newValue.toString()
-
- jsonPath.set("$.$before.[?]$last", newValue, {
- it.item(HashMap::class.java)[last] == existingValue
- })
+ val existingValue =
+ if (transformation.existingValue is Number) {
+ transformation.existingValue
+ } else {
+ transformation.existingValue.toString()
+ }
+ val newValue =
+ if (transformation.newValue is Number) {
+ transformation.newValue
+ } else {
+ transformation.newValue.toString()
+ }
+
+ jsonPath.set(
+ "$.$before.[?]$last",
+ newValue,
+ { it.item(HashMap::class.java)[last] == existingValue },
+ )
} catch (e: PathNotFoundException) {
// Ignore
}
@@ -57,35 +71,30 @@ class TransformationService(private val objectMapper: ObjectMapper, private val
return json
}
- fun getTransformations(): List<Transformation> {
- return this.transformations
- }
-
+ fun getTransformations(): List<Transformation> = this.transformations
}
-class Transformation private constructor(val path: String) {
-
- lateinit var existingValue: Any
- private set
- lateinit var newValue: Any
- private set
-
- infix fun from(value: Any): Transformation {
- this.existingValue = value
- return this
- }
+class Transformation
+ private constructor(
+ val path: String,
+ ) {
+ lateinit var existingValue: Any
+ private set
- infix fun to(value: Any): Transformation {
- this.newValue = value
- return this
- }
+ lateinit var newValue: Any
+ private set
- companion object {
+ infix fun from(value: Any): Transformation {
+ this.existingValue = value
+ return this
+ }
- fun of(path: String): Transformation {
- return Transformation(path)
+ infix fun to(value: Any): Transformation {
+ this.newValue = value
+ return this
}
+ companion object {
+ fun of(path: String): Transformation = Transformation(path)
+ }
}
-
-}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt
index fd6a9b4..e70f1e7 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt
@@ -26,56 +26,64 @@ import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.output.asRequestStatus
import dev.dnpm.etl.processor.services.ResponseEvent
+import java.time.Instant
+import java.util.*
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationEventPublisher
import org.springframework.kafka.listener.MessageListener
-import java.time.Instant
-import java.util.*
class KafkaResponseProcessor(
private val eventPublisher: ApplicationEventPublisher,
- private val objectMapper: ObjectMapper
+ private val objectMapper: ObjectMapper,
) : MessageListener<String, String> {
- private val logger = LoggerFactory.getLogger(KafkaResponseProcessor::class.java)
+ private val logger = LoggerFactory.getLogger(KafkaResponseProcessor::class.java)
- override fun onMessage(data: ConsumerRecord<String, String>) {
- try {
- Optional.of(objectMapper.readValue(data.value(), ResponseBody::class.java))
+ override fun onMessage(data: ConsumerRecord<String, String>) {
+ try {
+ Optional.of(objectMapper.readValue(data.value(), ResponseBody::class.java))
} catch (e: Exception) {
- logger.error("Cannot process Kafka response", e)
- Optional.empty()
- }.ifPresentOrElse({ responseBody ->
- val event = ResponseEvent(
- RequestId(responseBody.requestId),
- Instant.ofEpochMilli(data.timestamp()),
- responseBody.statusCode.asRequestStatus(),
- when (responseBody.statusCode.asRequestStatus()) {
- RequestStatus.SUCCESS -> {
- Optional.empty()
- }
-
- RequestStatus.WARNING, RequestStatus.ERROR -> {
- Optional.of(objectMapper.writeValueAsString(responseBody.statusBody))
- }
+ logger.error("Cannot process Kafka response", e)
+ Optional.empty()
+ }
+ .ifPresentOrElse(
+ { responseBody ->
+ val event =
+ ResponseEvent(
+ RequestId(responseBody.requestId),
+ Instant.ofEpochMilli(data.timestamp()),
+ responseBody.statusCode.asRequestStatus(),
+ when (responseBody.statusCode.asRequestStatus()) {
+ RequestStatus.SUCCESS -> {
+ Optional.empty()
+ }
- else -> {
- logger.error("Kafka response: Unknown response code '{}'!", responseBody.statusCode)
- Optional.empty()
- }
- }
- )
- eventPublisher.publishEvent(event)
- }, {
- logger.error("No requestId in Kafka response")
- })
- }
+ RequestStatus.WARNING,
+ RequestStatus.ERROR -> {
+ Optional.of(objectMapper.writeValueAsString(responseBody.statusBody))
+ }
- data class ResponseBody(
- @param:JsonProperty("request_id") @param:JsonAlias("requestId") val requestId: String,
- @param:JsonProperty("status_code") @param:JsonAlias("statusCode") val statusCode: Int,
- @param:JsonProperty("status_body") @param:JsonAlias("statusBody") val statusBody: Map<String, Any>
- )
+ else -> {
+ logger.error(
+ "Kafka response: Unknown response code '{}'!",
+ responseBody.statusCode,
+ )
+ Optional.empty()
+ }
+ },
+ )
+ eventPublisher.publishEvent(event)
+ },
+ { logger.error("No requestId in Kafka response") },
+ )
+ }
-} \ No newline at end of file
+ data class ResponseBody(
+ @param:JsonProperty("request_id") @param:JsonAlias("requestId") val requestId: String,
+ @param:JsonProperty("status_code") @param:JsonAlias("statusCode") val statusCode: Int,
+ @param:JsonProperty("status_body")
+ @param:JsonAlias("statusBody")
+ val statusBody: Map<String, Any>,
+ )
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/types.kt b/src/main/kotlin/dev/dnpm/etl/processor/types.kt
index 90fa7cb..c7aa110 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/types.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/types.kt
@@ -19,33 +19,30 @@
package dev.dnpm.etl.processor
-import org.springframework.http.MediaType
import java.util.*
+import org.springframework.http.MediaType
class Fingerprint(val value: String) {
- override fun hashCode() = value.hashCode()
+ override fun hashCode() = value.hashCode()
- override fun equals(other: Any?) = other is Fingerprint && other.value == value
+ override fun equals(other: Any?) = other is Fingerprint && other.value == value
- companion object {
- fun empty() = Fingerprint("")
- }
+ companion object {
+ fun empty() = Fingerprint("")
+ }
}
@JvmInline
value class RequestId(val value: String) {
- fun isBlank() = value.isBlank()
-
+ fun isBlank() = value.isBlank()
}
fun randomRequestId() = RequestId(UUID.randomUUID().toString())
-@JvmInline
-value class PatientId(val value: String)
+@JvmInline value class PatientId(val value: String)
-@JvmInline
-value class PatientPseudonym(val value: String)
+@JvmInline value class PatientPseudonym(val value: String)
fun emptyPatientPseudonym() = PatientPseudonym("")
@@ -55,9 +52,9 @@ fun emptyPatientPseudonym() = PatientPseudonym("")
* @since 0.11.0
*/
object CustomMediaType {
- val APPLICATION_VND_DNPM_V2_MTB_JSON = MediaType("application", "vnd.dnpm.v2.mtb+json")
- const val APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE = "application/vnd.dnpm.v2.mtb+json"
+ val APPLICATION_VND_DNPM_V2_MTB_JSON = MediaType("application", "vnd.dnpm.v2.mtb+json")
+ const val APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE = "application/vnd.dnpm.v2.mtb+json"
- val APPLICATION_VND_DNPM_V2_RD_JSON = MediaType("application", "vnd.dnpm.v2.rd+json")
- const val APPLICATION_VND_DNPM_V2_RD_JSON_VALUE = "application/vnd.dnpm.v2.rd+json"
+ val APPLICATION_VND_DNPM_V2_RD_JSON = MediaType("application", "vnd.dnpm.v2.rd+json")
+ const val APPLICATION_VND_DNPM_V2_RD_JSON_VALUE = "application/vnd.dnpm.v2.rd+json"
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/ApplicationControllerAdvice.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/ApplicationControllerAdvice.kt
index bdca57e..0dec30e 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/ApplicationControllerAdvice.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/ApplicationControllerAdvice.kt
@@ -27,11 +27,7 @@ import org.springframework.web.bind.annotation.ResponseStatus
@ControllerAdvice
class ApplicationControllerAdvice {
-
@ExceptionHandler(NotFoundException::class)
@ResponseStatus(HttpStatus.NOT_FOUND)
- fun handleNotFoundException(e: NotFoundException): String {
- return "errors/404"
- }
-
-} \ No newline at end of file
+ fun handleNotFoundException(e: NotFoundException): String = "errors/404"
+}
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 44571d4..b77bdf9 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt
@@ -23,11 +23,11 @@ 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
-import dev.dnpm.etl.processor.security.UserRole
import dev.dnpm.etl.processor.security.Token
import dev.dnpm.etl.processor.security.TokenService
-import dev.dnpm.etl.processor.services.TransformationService
+import dev.dnpm.etl.processor.security.UserRole
import dev.dnpm.etl.processor.security.UserRoleService
+import dev.dnpm.etl.processor.services.TransformationService
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerSentEvent
@@ -47,175 +47,193 @@ class ConfigController(
private val mtbFileSender: MtbFileSender,
private val connectionCheckServices: List<ConnectionCheckService>,
private val tokenService: TokenService?,
- private val userRoleService: UserRoleService?
+ private val userRoleService: UserRoleService?,
) {
- @GetMapping
- fun index(model: Model): String {
- val outputConnectionAvailable =
- connectionCheckServices.filterIsInstance<OutputConnectionCheckService>().firstOrNull()?.connectionAvailable()
-
- 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())
- } else {
- model.addAttribute("tokens", emptyList<Token>())
- }
- model.addAttribute("transformations", transformationService.getTransformations())
- if (userRoleService != null) {
- model.addAttribute("userRolesEnabled", true)
- model.addAttribute("userRoles", userRoleService.findAll())
- } else {
- model.addAttribute("userRolesEnabled", false)
- model.addAttribute("userRoles", emptyList<UserRole>())
- }
- return "configs"
+ @GetMapping
+ fun index(model: Model): String {
+ val outputConnectionAvailable =
+ connectionCheckServices
+ .filterIsInstance<OutputConnectionCheckService>()
+ .firstOrNull()
+ ?.connectionAvailable()
+
+ 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())
+ } else {
+ model.addAttribute("tokens", emptyList<Token>())
}
-
- @GetMapping(params = ["outputConnectionAvailable"])
- fun outputConnectionAvailable(model: Model): String {
- val outputConnectionAvailable =
- connectionCheckServices.filterIsInstance<OutputConnectionCheckService>().first().connectionAvailable()
-
- model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
- model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
- model.addAttribute("outputConnectionAvailable", outputConnectionAvailable)
- if (tokenService != null) {
- model.addAttribute("tokensEnabled", true)
- model.addAttribute("tokens", tokenService.findAll())
- } else {
- model.addAttribute("tokens", listOf<Token>())
- }
-
- return "configs/outputConnectionAvailable"
+ model.addAttribute("transformations", transformationService.getTransformations())
+ if (userRoleService != null) {
+ model.addAttribute("userRolesEnabled", true)
+ model.addAttribute("userRoles", userRoleService.findAll())
+ } else {
+ model.addAttribute("userRolesEnabled", false)
+ model.addAttribute("userRoles", emptyList<UserRole>())
}
-
- @GetMapping(params = ["gPasConnectionAvailable"])
- fun gPasConnectionAvailable(model: Model): String {
- val gPasConnectionAvailable =
- connectionCheckServices.filterIsInstance<GPasConnectionCheckService>().firstOrNull()?.connectionAvailable()
-
- model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
- model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
- model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable)
- if (tokenService != null) {
- model.addAttribute("tokensEnabled", true)
- model.addAttribute("tokens", tokenService.findAll())
- } else {
- model.addAttribute("tokens", listOf<Token>())
- }
-
- return "configs/gPasConnectionAvailable"
+ return "configs"
+ }
+
+ @GetMapping(params = ["outputConnectionAvailable"])
+ fun outputConnectionAvailable(model: Model): String {
+ val outputConnectionAvailable =
+ connectionCheckServices
+ .filterIsInstance<OutputConnectionCheckService>()
+ .first()
+ .connectionAvailable()
+
+ model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
+ model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
+ model.addAttribute("outputConnectionAvailable", outputConnectionAvailable)
+ if (tokenService != null) {
+ model.addAttribute("tokensEnabled", true)
+ model.addAttribute("tokens", tokenService.findAll())
+ } else {
+ model.addAttribute("tokens", listOf<Token>())
}
- @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"
+ return "configs/outputConnectionAvailable"
+ }
+
+ @GetMapping(params = ["gPasConnectionAvailable"])
+ fun gPasConnectionAvailable(model: Model): String {
+ val gPasConnectionAvailable =
+ connectionCheckServices
+ .filterIsInstance<GPasConnectionCheckService>()
+ .firstOrNull()
+ ?.connectionAvailable()
+
+ model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
+ model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
+ model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable)
+ if (tokenService != null) {
+ model.addAttribute("tokensEnabled", true)
+ model.addAttribute("tokens", tokenService.findAll())
+ } else {
+ model.addAttribute("tokens", listOf<Token>())
}
- @PostMapping(path = ["tokens"])
- fun addToken(@ModelAttribute("name") name: String, model: Model): String {
- if (tokenService == null) {
- model.addAttribute("tokensEnabled", false)
- model.addAttribute("success", false)
- } else {
- model.addAttribute("tokensEnabled", true)
- val result = tokenService.addToken(name)
- result.onSuccess {
- model.addAttribute("newTokenValue", it)
- model.addAttribute("success", true)
- }
- result.onFailure {
- model.addAttribute("success", false)
- }
- model.addAttribute("tokens", tokenService.findAll())
- }
-
- return "configs/tokens"
+ 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>())
}
- @DeleteMapping(path = ["tokens/{id}"])
- fun deleteToken(@PathVariable id: Long, model: Model): String {
- if (tokenService != null) {
- tokenService.deleteToken(id)
-
- model.addAttribute("tokensEnabled", true)
- model.addAttribute("tokens", tokenService.findAll())
- } else {
- model.addAttribute("tokensEnabled", false)
- model.addAttribute("tokens", listOf<Token>())
- }
- return "configs/tokens"
+ return "configs/gIcsConnectionAvailable"
+ }
+
+ @PostMapping(path = ["tokens"])
+ fun addToken(@ModelAttribute("name") name: String, model: Model): String {
+ if (tokenService == null) {
+ model.addAttribute("tokensEnabled", false)
+ model.addAttribute("success", false)
+ } else {
+ model.addAttribute("tokensEnabled", true)
+ val result = tokenService.addToken(name)
+ result.onSuccess {
+ model.addAttribute("newTokenValue", it)
+ model.addAttribute("success", true)
+ }
+ result.onFailure { model.addAttribute("success", false) }
+ model.addAttribute("tokens", tokenService.findAll())
}
- @DeleteMapping(path = ["userroles/{id}"])
- fun deleteUserRole(@PathVariable id: Long, model: Model): String {
- if (userRoleService != null) {
- userRoleService.deleteUserRole(id)
-
- model.addAttribute("userRolesEnabled", true)
- model.addAttribute("userRoles", userRoleService.findAll())
- } else {
- model.addAttribute("userRolesEnabled", false)
- model.addAttribute("userRoles", emptyList<UserRole>())
- }
- return "configs/userroles"
- }
+ return "configs/tokens"
+ }
- @PutMapping(path = ["userroles/{id}"])
- fun updateUserRole(@PathVariable id: Long, @ModelAttribute("role") role: Role, model: Model): String {
- if (userRoleService != null) {
- userRoleService.updateUserRole(id, role)
-
- model.addAttribute("userRolesEnabled", true)
- model.addAttribute("userRoles", userRoleService.findAll())
- } else {
- model.addAttribute("userRolesEnabled", false)
- model.addAttribute("userRoles", emptyList<UserRole>())
- }
- return "configs/userroles"
- }
+ @DeleteMapping(path = ["tokens/{id}"])
+ fun deleteToken(@PathVariable id: Long, model: Model): String {
+ if (tokenService != null) {
+ tokenService.deleteToken(id)
- @GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
- @ResponseBody
- fun events(): Flux<ServerSentEvent<Any>> {
- return connectionCheckUpdateProducer.asFlux().map {
- val event = when (it) {
- 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>()
- .event(event).id("none").data(it)
- .build()
- }
+ model.addAttribute("tokensEnabled", true)
+ model.addAttribute("tokens", tokenService.findAll())
+ } else {
+ model.addAttribute("tokensEnabled", false)
+ model.addAttribute("tokens", listOf<Token>())
}
-
-} \ No newline at end of file
+ return "configs/tokens"
+ }
+
+ @DeleteMapping(path = ["userroles/{id}"])
+ fun deleteUserRole(@PathVariable id: Long, model: Model): String {
+ if (userRoleService != null) {
+ userRoleService.deleteUserRole(id)
+
+ model.addAttribute("userRolesEnabled", true)
+ model.addAttribute("userRoles", userRoleService.findAll())
+ } else {
+ model.addAttribute("userRolesEnabled", false)
+ model.addAttribute("userRoles", emptyList<UserRole>())
+ }
+ return "configs/userroles"
+ }
+
+ @PutMapping(path = ["userroles/{id}"])
+ fun updateUserRole(
+ @PathVariable id: Long,
+ @ModelAttribute("role") role: Role,
+ model: Model,
+ ): String {
+ if (userRoleService != null) {
+ userRoleService.updateUserRole(id, role)
+
+ model.addAttribute("userRolesEnabled", true)
+ model.addAttribute("userRoles", userRoleService.findAll())
+ } else {
+ model.addAttribute("userRolesEnabled", false)
+ model.addAttribute("userRoles", emptyList<UserRole>())
+ }
+ return "configs/userroles"
+ }
+
+ @GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
+ @ResponseBody
+ fun events(): Flux<ServerSentEvent<Any>> {
+ return connectionCheckUpdateProducer.asFlux().map {
+ val event =
+ when (it) {
+ 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>().event(event).id("none").data(it).build()
+ }
+ }
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
index 54920b1..082cd20 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
@@ -37,13 +37,13 @@ import org.springframework.web.bind.annotation.RequestMapping
@RequestMapping(path = ["/"])
class HomeController(
private val requestService: RequestService,
- private val reportService: ReportService
+ private val reportService: ReportService,
) {
-
@GetMapping
fun index(
- @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable,
- model: Model
+ @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC)
+ pageable: Pageable,
+ model: Model,
): String {
val requests = requestService.findAll(pageable)
model.addAttribute("requests", requests)
@@ -54,8 +54,9 @@ class HomeController(
@GetMapping(path = ["patient/{patientPseudonym}"])
fun byPatient(
@PathVariable patientPseudonym: PatientPseudonym,
- @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable,
- model: Model
+ @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC)
+ pageable: Pageable,
+ model: Model,
): String {
val requests = requestService.findRequestByPatientId(patientPseudonym, pageable)
model.addAttribute("patientPseudonym", patientPseudonym.value)
@@ -65,12 +66,14 @@ class HomeController(
}
@GetMapping(path = ["/report/{id}"])
- fun report(@PathVariable id: RequestId, model: Model): String {
+ fun report(
+ @PathVariable id: RequestId,
+ model: Model,
+ ): String {
val request = requestService.findByUuid(id).orElse(null) ?: throw NotFoundException()
model.addAttribute("request", request)
model.addAttribute("issues", reportService.deserialize(request.report?.dataQualityReport))
return "report"
}
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt
index 20837bb..7821bf9 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt
@@ -28,20 +28,21 @@ import org.springframework.web.bind.annotation.GetMapping
@Controller
class LoginController(
private val securityConfigProperties: SecurityConfigProperties?,
- private val oAuth2ClientProperties: OAuth2ClientProperties?
+ private val oAuth2ClientProperties: OAuth2ClientProperties?,
) {
-
@GetMapping(path = ["/login"])
fun login(model: Model): String {
if (securityConfigProperties?.enableOidc == true) {
model.addAttribute(
"oidcLogins",
- oAuth2ClientProperties?.registration?.map { (key, value) -> Pair(key, value.clientName) }.orEmpty()
+ oAuth2ClientProperties
+ ?.registration
+ ?.map { (key, value) -> Pair(key, value.clientName) }
+ .orEmpty(),
)
} else {
model.addAttribute("oidcLogins", emptyList<Pair<String, String>>())
}
return "login"
}
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsController.kt
index adc1e2b..e48d5df 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsController.kt
@@ -28,11 +28,9 @@ import java.time.Instant
@Controller
@RequestMapping(path = ["/statistics"])
class StatisticsController {
-
@GetMapping
fun index(model: Model): String {
model.addAttribute("now", Instant.now())
return "statistics"
}
-
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
index 8372274..c99a4a3 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
@@ -41,140 +41,168 @@ import java.time.temporal.ChronoUnit
class StatisticsRestController(
@param:Qualifier("statisticsUpdateProducer")
private val statisticsUpdateProducer: Sinks.Many<Any>,
- private val requestService: RequestService
+ private val requestService: RequestService,
) {
-
@GetMapping(path = ["requeststates"])
- fun requestStates(@RequestParam(required = false, defaultValue = "false") delete: Boolean): List<NameValue> {
- val states = if (delete) {
- requestService.countDeleteStates()
- } else {
- requestService.countStates()
- }
+ fun requestStates(
+ @RequestParam(required = false, defaultValue = "false") delete: Boolean,
+ ): List<NameValue> {
+ val states =
+ if (delete) {
+ requestService.countDeleteStates()
+ } else {
+ requestService.countStates()
+ }
return states
.map {
- val color = when (it.status) {
- RequestStatus.ERROR -> "red"
- RequestStatus.WARNING -> "darkorange"
- RequestStatus.SUCCESS -> "green"
- else -> "slategray"
- }
+ val color =
+ when (it.status) {
+ RequestStatus.ERROR -> "red"
+ RequestStatus.WARNING -> "darkorange"
+ RequestStatus.SUCCESS -> "green"
+ else -> "slategray"
+ }
NameValue(it.status.toString(), it.count, color)
- }
- .sortedByDescending { it.value }
+ }.sortedByDescending { it.value }
}
@GetMapping(path = ["requestslastmonth"])
fun requestsLastMonth(
- @RequestParam(
- required = false,
- defaultValue = "false"
- ) delete: Boolean
+ @RequestParam(required = false, defaultValue = "false") delete: Boolean,
): List<DateNameValues> {
- val requestType = if (delete) {
- RequestType.DELETE
- } else {
- RequestType.MTB_FILE
- }
+ val requestType =
+ if (delete) {
+ RequestType.DELETE
+ } else {
+ RequestType.MTB_FILE
+ }
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("Europe/Berlin"))
- val data = requestService.findAll()
- .filter { it.type == requestType }
- .filter { it.processedAt.isAfter(Instant.now().minus(30, ChronoUnit.DAYS)) }
- .groupBy { formatter.format(it.processedAt) }
- .map {
- val requestList = it.value
- .groupBy { request -> request.status }
- .map { request ->
- Pair(request.key, request.value.size)
- }
- .toMap()
- Pair(
- it.key.toString(),
- DateNameValues(
- it.key.toString(), NameValues(
- error = requestList[RequestStatus.ERROR] ?: 0,
- warning = requestList[RequestStatus.WARNING] ?: 0,
- success = requestList[RequestStatus.SUCCESS] ?: 0,
- duplication = requestList[RequestStatus.DUPLICATION] ?: 0,
- unknown = requestList[RequestStatus.UNKNOWN] ?: 0,
- )
+ val data =
+ requestService
+ .findAll()
+ .filter { it.type == requestType }
+ .filter { it.processedAt.isAfter(Instant.now().minus(30, ChronoUnit.DAYS)) }
+ .groupBy { formatter.format(it.processedAt) }
+ .map {
+ val requestList =
+ it.value
+ .groupBy { request -> request.status }
+ .map { request -> Pair(request.key, request.value.size) }
+ .toMap()
+ Pair(
+ it.key.toString(),
+ DateNameValues(
+ it.key.toString(),
+ NameValues(
+ error = requestList[RequestStatus.ERROR] ?: 0,
+ warning = requestList[RequestStatus.WARNING] ?: 0,
+ success = requestList[RequestStatus.SUCCESS] ?: 0,
+ duplication = requestList[RequestStatus.DUPLICATION] ?: 0,
+ unknown = requestList[RequestStatus.UNKNOWN] ?: 0,
+ ),
+ ),
)
- )
- }.toMap()
+ }.toMap()
- return (0L..30L).map { Instant.now().minus(it, ChronoUnit.DAYS) }
+ return (0L..30L)
+ .map { Instant.now().minus(it, ChronoUnit.DAYS) }
.map { formatter.format(it) }
- .map {
- DateNameValues(it, data[it]?.nameValues ?: NameValues())
- }
+ .map { DateNameValues(it, data[it]?.nameValues ?: NameValues()) }
.sortedBy { it.date }
}
@GetMapping(path = ["requestpatientstates"])
- fun requestPatientStates(@RequestParam(required = false, defaultValue = "false") delete: Boolean): List<NameValue> {
- val states = if (delete) {
- requestService.findPatientUniqueDeleteStates()
- } else {
- requestService.findPatientUniqueStates()
- }
+ fun requestPatientStates(
+ @RequestParam(required = false, defaultValue = "false") delete: Boolean,
+ ): List<NameValue> {
+ val states =
+ if (delete) {
+ requestService.findPatientUniqueDeleteStates()
+ } else {
+ requestService.findPatientUniqueStates()
+ }
return states.map {
- val color = when (it.status) {
- RequestStatus.ERROR -> "red"
- RequestStatus.WARNING -> "darkorange"
- RequestStatus.SUCCESS -> "green"
- else -> "slategray"
- }
+ val color =
+ when (it.status) {
+ RequestStatus.ERROR -> "red"
+ RequestStatus.WARNING -> "darkorange"
+ RequestStatus.SUCCESS -> "green"
+ else -> "slategray"
+ }
NameValue(it.status.toString(), it.count, color)
}
}
@GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
- fun updater(): Flux<ServerSentEvent<Any>> {
- return statisticsUpdateProducer.asFlux().flatMap {
+ fun updater(): Flux<ServerSentEvent<Any>> =
+ statisticsUpdateProducer.asFlux().flatMap {
Flux.fromIterable(
listOf(
- ServerSentEvent.builder<Any>()
- .event("requeststates").id("none").data(this.requestStates(false))
+ ServerSentEvent
+ .builder<Any>()
+ .event("requeststates")
+ .id("none")
+ .data(this.requestStates(false))
.build(),
- ServerSentEvent.builder<Any>()
- .event("requestslastmonth").id("none").data(this.requestsLastMonth(false))
+ ServerSentEvent
+ .builder<Any>()
+ .event("requestslastmonth")
+ .id("none")
+ .data(this.requestsLastMonth(false))
.build(),
- ServerSentEvent.builder<Any>()
- .event("requestpatientstates").id("none").data(this.requestPatientStates(false))
+ ServerSentEvent
+ .builder<Any>()
+ .event("requestpatientstates")
+ .id("none")
+ .data(this.requestPatientStates(false))
.build(),
-
- ServerSentEvent.builder<Any>()
- .event("deleterequeststates").id("none").data(this.requestStates(true))
+ ServerSentEvent
+ .builder<Any>()
+ .event("deleterequeststates")
+ .id("none")
+ .data(this.requestStates(true))
.build(),
- ServerSentEvent.builder<Any>()
- .event("deleterequestslastmonth").id("none").data(this.requestsLastMonth(true))
+ ServerSentEvent
+ .builder<Any>()
+ .event("deleterequestslastmonth")
+ .id("none")
+ .data(this.requestsLastMonth(true))
.build(),
- ServerSentEvent.builder<Any>()
- .event("deleterequestpatientstates").id("none").data(this.requestPatientStates(true))
+ ServerSentEvent
+ .builder<Any>()
+ .event("deleterequestpatientstates")
+ .id("none")
+ .data(this.requestPatientStates(true))
.build(),
-
- ServerSentEvent.builder<Any>()
- .event("newrequest").id("none").data("newrequest")
- .build()
- )
+ ServerSentEvent
+ .builder<Any>()
+ .event("newrequest")
+ .id("none")
+ .data("newrequest")
+ .build(),
+ ),
)
-
}
- }
-
}
-data class NameValue(val name: String, val value: Int, val color: String)
+data class NameValue(
+ val name: String,
+ val value: Int,
+ val color: String,
+)
-data class DateNameValues(val date: String, val nameValues: NameValues)
+data class DateNameValues(
+ val date: String,
+ val nameValues: NameValues,
+)
data class NameValues(
val error: Int = 0,
val warning: Int = 0,
val success: Int = 0,
val duplication: Int = 0,
- val unknown: Int = 0
-) \ No newline at end of file
+ val unknown: Int = 0,
+)