summaryrefslogtreecommitdiff
path: root/src/main/kotlin/dev
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/dev')
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt1
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt39
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt29
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt2
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt26
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt31
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt48
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessor.kt6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt2
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt2
11 files changed, 148 insertions, 46 deletions
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
index e5db63e..ee33114 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
@@ -48,6 +48,7 @@ data class PseudonymizeConfigProperties(
@ConfigurationProperties(GPasConfigProperties.NAME)
data class GPasConfigProperties(
val uri: String?,
+ val soapEndpoint: String?,
@get:DeprecatedConfigurationProperty(since = "0.12")
val pidDomain: String?,
val patientDomain: String = pidDomain ?: "etl-processor",
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 b4fad3e..de302fd 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
@@ -20,19 +20,17 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper
-import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.dnpm.etl.processor.consent.IConsentService
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.monitoring.*
-import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
-import dev.dnpm.etl.processor.pseudonym.Generator
-import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
-import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
+import dev.dnpm.etl.processor.pseudonym.*
import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.TokenService
import dev.dnpm.etl.processor.services.ConsentProcessor
import dev.dnpm.etl.processor.services.Transformation
import dev.dnpm.etl.processor.services.TransformationService
+import org.apache.cxf.jaxws.JaxWsProxyFactoryBean
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
@@ -85,6 +83,37 @@ class AppConfiguration {
}
@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 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,
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 bf8b8bd..6e97865 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckService.kt
@@ -80,7 +80,7 @@ sealed class ConnectionCheckResult {
class KafkaConnectionCheckService(
private val consumer: Consumer<String, String>,
- @Qualifier("connectionCheckUpdateProducer")
+ @param:Qualifier("connectionCheckUpdateProducer")
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
) : OutputConnectionCheckService {
@@ -120,7 +120,7 @@ class KafkaConnectionCheckService(
class RestConnectionCheckService(
private val restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties,
- @Qualifier("connectionCheckUpdateProducer")
+ @param:Qualifier("connectionCheckUpdateProducer")
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
) : OutputConnectionCheckService {
@@ -171,7 +171,7 @@ class RestConnectionCheckService(
class GPasConnectionCheckService(
private val restTemplate: RestTemplate,
private val gPasConfigProperties: GPasConfigProperties,
- @Qualifier("connectionCheckUpdateProducer")
+ @param:Qualifier("connectionCheckUpdateProducer")
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
) : ConnectionCheckService {
@@ -229,7 +229,7 @@ class GPasConnectionCheckService(
class GIcsConnectionCheckService(
private val restTemplate: RestTemplate,
private val gIcsConfigProperties: GIcsConfigProperties,
- @Qualifier("connectionCheckUpdateProducer")
+ @param:Qualifier("connectionCheckUpdateProducer")
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
) : ConnectionCheckService {
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 36c9705..f2509dd 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
@@ -30,6 +30,7 @@ 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")
@@ -65,6 +66,12 @@ data class Request(
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
@@ -90,19 +97,23 @@ interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRep
@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;")
+ @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;")
+ @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>
-} \ No newline at end of file
+}
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 0537cbb..dcb438f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/AnonymizingGenerator.kt
@@ -35,7 +35,7 @@ class AnonymizingGenerator : Generator {
}
@OptIn(ExperimentalStdlibApi::class)
- override fun generateGenomDeTan(id: String?): String {
+ override fun generateGenomDeTan(id: String): String {
val bytes = ByteArray(64 / 2)
getSecureRandom().nextBytes(bytes)
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt
new file mode 100644
index 0000000..8215d23
--- /dev/null
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapPseudonymGenerator.kt
@@ -0,0 +1,26 @@
+package dev.dnpm.etl.processor.pseudonym
+
+import dev.dnpm.etl.processor.config.AppFhirConfig
+import dev.dnpm.etl.processor.config.GPasConfigProperties
+import org.springframework.retry.support.RetryTemplate
+
+class GpasSoapPseudonymGenerator(
+ private val gpasCfg: GPasConfigProperties,
+ private val retryTemplate: RetryTemplate,
+ private val gpasSoapService: GpasSoapService,
+ private val appFhirConfig: AppFhirConfig
+) : Generator {
+
+ override fun generate(id: String): String {
+ return retryTemplate.execute<String, Exception> {
+ gpasSoapService.getOrCreatePseudonymFor(id, gpasCfg.patientDomain)
+ }
+ }
+
+ override fun generateGenomDeTan(id: String): String {
+ return 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
new file mode 100644
index 0000000..0909924
--- /dev/null
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/GpasSoapService.kt
@@ -0,0 +1,31 @@
+package dev.dnpm.etl.processor.pseudonym
+
+import jakarta.jws.WebMethod
+import jakarta.jws.WebParam
+import jakarta.jws.WebResult
+import jakarta.jws.WebService
+import jakarta.xml.bind.annotation.XmlElementWrapper
+
+@WebService(
+ name = "PSNManagerBeanService",
+ 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
+ ): String
+
+ @WebMethod(operationName = "createPseudonymsFor")
+ @WebResult(name = "psn")
+ @XmlElementWrapper(name = "return")
+ fun createPseudonymsFor(
+ @WebParam(name = "value") value: String,
+ @WebParam(name = "domainName") domainName: String,
+ @WebParam(name = "number") minNumber: Int
+ ): List<String>
+
+} \ 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 2ed21eb..b420d1f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
@@ -14,7 +14,6 @@ import dev.pcvolkmer.mv64e.mtb.*
import org.apache.commons.lang3.NotImplementedException
import org.hl7.fhir.r4.model.*
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
-import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Consent.ConsentState
import org.hl7.fhir.r4.model.Consent.ProvisionComponent
import org.slf4j.Logger
@@ -137,15 +136,7 @@ class ConsentProcessor(
}
val provisionComponent: ProvisionComponent = provisions.first()
-
- 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
- }
- }
-
+ val provisionCode = getProvisionCode(provisionComponent)
if (provisionCode != null) {
try {
val modelProjectConsentPurpose =
@@ -177,6 +168,17 @@ class ConsentProcessor(
}
}
+ 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
+ }
+
private fun setGenomDeSubmissionType(mtbFile: Mtb) {
if (appConfigProperties.genomDeTestSubmission) {
mtbFile.metadata.type = MvhSubmissionType.TEST
@@ -230,17 +232,21 @@ class ConsentProcessor(
entry.resource.isResource && entry.resource.resourceType == ResourceType.Consent
val consentIsActive = (entry.resource as Consent).status == ConsentState.ACTIVE
- isConsentResource && consentIsActive && checkCoding(
- targetCode, targetSystem, (entry.resource as Consent).policyRule.coding
- ) && isRequestDateInRange(requestDate, (entry.resource as Consent).provision.period)
+ 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().firstOrNull { code ->
+ subProvision.code.map { c -> c.coding }.flatten().any { code ->
targetCode.equals(code.code) && targetSystem.equals(code.system)
- } != null
+ }
}.map { subProvision ->
subProvision
}
@@ -252,16 +258,14 @@ class ConsentProcessor(
return Consent.ConsentProvisionType.NULL
}
- fun checkCoding(
+ fun checkProvisionExist(
researchAllowedPolicyOid: String?,
researchAllowedPolicySystem: String?,
- policyRules: Collection<Coding>
+ provisions: Collection<ProvisionComponent>
): Boolean {
- return policyRules.find { code ->
- researchAllowedPolicySystem.equals(code.getSystem()) && (researchAllowedPolicyOid.equals(
- code.getCode()
- ))
- } != null
+ return provisions.any { provision ->
+ provision.code.any { codeableConcept -> codeableConcept.coding.any { it.system == researchAllowedPolicySystem && it.code == researchAllowedPolicyOid } }
+ }
}
fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean {
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 12e824d..fd6a9b4 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
@@ -73,9 +73,9 @@ class KafkaResponseProcessor(
}
data class ResponseBody(
- @JsonProperty("request_id") @JsonAlias("requestId") val requestId: String,
- @JsonProperty("status_code") @JsonAlias("statusCode") val statusCode: Int,
- @JsonProperty("status_body") @JsonAlias("statusBody") val statusBody: Map<String, Any>
+ @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>
)
} \ No newline at end of file
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 ea89e98..44571d4 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/ConfigController.kt
@@ -40,7 +40,7 @@ import reactor.core.publisher.Sinks
@Controller
@RequestMapping(path = ["configs"])
class ConfigController(
- @Qualifier("connectionCheckUpdateProducer")
+ @param:Qualifier("connectionCheckUpdateProducer")
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>,
private val transformationService: TransformationService,
private val pseudonymGenerator: Generator,
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 c034cb4..8372274 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
@@ -39,7 +39,7 @@ import java.time.temporal.ChronoUnit
@RestController
@RequestMapping(path = ["/statistics"])
class StatisticsRestController(
- @Qualifier("statisticsUpdateProducer")
+ @param:Qualifier("statisticsUpdateProducer")
private val statisticsUpdateProducer: Sinks.Many<Any>,
private val requestService: RequestService
) {