summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorPaul-Christian Volkmer2026-03-11 14:13:29 +0100
committerGitHub2026-03-11 13:13:29 +0000
commita8f8d5f137c9776a20b2bc91cd3bdd99c9b96991 (patch)
treeb2df9c483c2324a4bc837b9c4fa127da8fd0333f /src/main
parent5178673955a69b14ff39bf8a2a73d50ef2fd9cd2 (diff)
feat: save error request for invalid input (#264)
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt57
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt410
2 files changed, 247 insertions, 220 deletions
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 f4ab194..523a0a8 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
@@ -33,36 +33,37 @@ import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping(path = ["mtbfile", "mtb", "api/mtbfile", "api/mtb"])
class MtbFileRestController(
- private val requestProcessor: RequestProcessor,
- private val consentEvaluator: ConsentEvaluator,
+ private val requestProcessor: RequestProcessor
) {
- 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")
- }
+ @GetMapping
+ fun info(): ResponseEntity<String> {
+ return ResponseEntity.ok("Test")
+ }
- @PostMapping(
- path = ["", "etl/patient-record"],
- consumes =
- [
- MediaType.APPLICATION_JSON_VALUE,
- CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE,
- ],
- )
- fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
- logger.debug("Accepted MTB File (DNPM V2) for processing")
- requestProcessor.processMtbFile(mtbFile)
- return ResponseEntity.accepted().build()
- }
+ @PostMapping(
+ path = ["", "etl/patient-record"],
+ consumes =
+ [
+ MediaType.APPLICATION_JSON_VALUE,
+ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE,
+ ],
+ )
+ fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
+ logger.debug("Accepted MTB File (DNPM V2) for processing")
+ if (requestProcessor.processMtbFile(mtbFile)) {
+ return ResponseEntity.accepted().build()
+ }
+ return ResponseEntity.badRequest().build()
+ }
- @DeleteMapping(
- path = ["{patientId}", "etl/patient-record/{patientId}", "etl/patient/{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()
- }
+ @DeleteMapping(
+ path = ["{patientId}", "etl/patient-record/{patientId}", "etl/patient/{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/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
index fe1fd3b..7325265 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
@@ -56,225 +56,251 @@ class RequestProcessor(
private val consentProcessor: ConsentProcessor?,
) {
- private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
+ private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
- fun processMtbFile(mtbFile: Mtb) {
- processMtbFile(mtbFile, randomRequestId())
- }
+ fun processMtbFile(mtbFile: Mtb): Boolean {
+ return processMtbFile(mtbFile, randomRequestId())
+ }
+
+ fun processMtbFile(mtbFile: Mtb, requestId: RequestId): Boolean {
+ val isConsentOk =
+ consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
+ consentProcessor == null
- fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
- val isConsentOk =
- consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
- consentProcessor == null
+ if (!isConsentOk) {
+ logger.warn("consent check failed but will be sent to DNPM:DIP!")
+ }
- if (!isConsentOk) {
- logger.warn("consent check failed but will be sent to DNPM:DIP!")
+ try {
+ mtbFile addGenomDeTan pseudonymizeService
+ mtbFile pseudonymizeWith pseudonymizeService
+ mtbFile anonymizeContentWith pseudonymizeService
+ val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
+ saveAndSend(request)
+ } catch (e: Exception) {
+ logger.error("Error while processing MtbFile", e)
+ requestService.save(
+ Request(
+ null,
+ requestId,
+ PatientPseudonym("INVALID"),
+ emptyPatientId(),
+ fingerprint(""),
+ RequestType.MTB_FILE,
+ SubmissionType.UNKNOWN,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ report = Report("Fehlerhafte Eingangsdaten. Keine Verarbeitung oder Weiterleitung."),
+ )
+ )
+ return false
+ }
+ return true
}
- mtbFile addGenomDeTan pseudonymizeService
- mtbFile pseudonymizeWith pseudonymizeService
- mtbFile anonymizeContentWith pseudonymizeService
- val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
- saveAndSend(request)
- }
-
- private fun <T> saveAndSend(request: MtbFileRequest<T>) {
- var submissionType: SubmissionType =
- when (request) {
- is DnpmV2MtbFileRequest -> {
- when (request.content.metadata?.type) {
- MvhSubmissionType.TEST -> SubmissionType.TEST
- MvhSubmissionType.INITIAL -> SubmissionType.INITIAL
- MvhSubmissionType.ADDITION -> SubmissionType.ADDITION
- MvhSubmissionType.CORRECTION -> SubmissionType.CORRECTION
- MvhSubmissionType.FOLLOWUP -> SubmissionType.FOLLOWUP
- else -> SubmissionType.UNKNOWN
+ private fun <T> saveAndSend(request: MtbFileRequest<T>) {
+ var submissionType: SubmissionType =
+ when (request) {
+ is DnpmV2MtbFileRequest -> {
+ when (request.content.metadata?.type) {
+ MvhSubmissionType.TEST -> SubmissionType.TEST
+ MvhSubmissionType.INITIAL -> SubmissionType.INITIAL
+ MvhSubmissionType.ADDITION -> SubmissionType.ADDITION
+ MvhSubmissionType.CORRECTION -> SubmissionType.CORRECTION
+ MvhSubmissionType.FOLLOWUP -> SubmissionType.FOLLOWUP
+ else -> SubmissionType.UNKNOWN
+ }
+ }
}
- }
- }
- if (
- appConfigProperties.postInitialSubmissionBlock &&
+ if (
+ appConfigProperties.postInitialSubmissionBlock &&
hasSuccessfullInitialSubmission(request.patientPseudonym()) &&
hasUnacceptedInitialSubmission(request.patientPseudonym())
- ) {
- requestService.save(
- Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.BLOCKED_INITIAL,
- Tan(request.content.metadata?.transferTan.orEmpty())
- )
- )
- // Exit - no further processing
- return
- }
+ ) {
+ requestService.save(
+ Request(
+ request.requestId,
+ request.patientPseudonym(),
+ emptyPatientId(),
+ fingerprint(request),
+ RequestType.MTB_FILE,
+ submissionType,
+ RequestStatus.BLOCKED_INITIAL,
+ Tan(request.content.metadata?.transferTan.orEmpty())
+ )
+ )
+ // Exit - no further processing
+ return
+ }
- if (
- appConfigProperties.postInitialSubmissionBlock &&
+ if (
+ appConfigProperties.postInitialSubmissionBlock &&
hasSuccessfullInitialSubmission(request.patientPseudonym()) &&
!hasUnacceptedInitialSubmission(request.patientPseudonym())
- ) {
- // Use "addition" after "intial" with "Meldebestaetigung"
- request.content.metadata?.let {
- logger.warn("Override submission type using 'addition' after first initial submission!")
- it.type = MvhSubmissionType.ADDITION
- submissionType = SubmissionType.ADDITION
- }
- }
+ ) {
+ // Use "addition" after "intial" with "Meldebestaetigung"
+ request.content.metadata?.let {
+ logger.warn("Override submission type using 'addition' after first initial submission!")
+ it.type = MvhSubmissionType.ADDITION
+ submissionType = SubmissionType.ADDITION
+ }
+ }
- requestService.save(
- Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.UNKNOWN,
- Tan(request.content.metadata?.transferTan.orEmpty()),
+ requestService.save(
+ Request(
+ request.requestId,
+ request.patientPseudonym(),
+ emptyPatientId(),
+ fingerprint(request),
+ RequestType.MTB_FILE,
+ submissionType,
+ RequestStatus.UNKNOWN,
+ Tan(request.content.metadata?.transferTan.orEmpty()),
+ )
)
- )
- if (appConfigProperties.duplicationDetection && isDuplication(request)) {
- applicationEventPublisher.publishEvent(
- ResponseEvent(request.requestId, Instant.now(), RequestStatus.DUPLICATION)
- )
- return
- }
+ 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()
- },
- )
- )
- }
+ val responseStatus = sender.send(request)
- private fun hasSuccessfullInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
- return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
- it.submissionType == SubmissionType.INITIAL &&
- (it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING)
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(
+ request.requestId,
+ Instant.now(),
+ responseStatus.status,
+ when (responseStatus.status) {
+ RequestStatus.ERROR,
+ RequestStatus.WARNING -> Optional.of(responseStatus.body)
+
+ else -> Optional.empty()
+ },
+ )
+ )
}
- }
- private fun hasUnacceptedInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
- return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
- it.submissionType == SubmissionType.INITIAL &&
- !(it.submissionAccepted || it.status == RequestStatus.BLOCKED_INITIAL)
+ private fun hasSuccessfullInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
+ return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
+ it.submissionType == SubmissionType.INITIAL &&
+ (it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING)
+ }
}
- }
- private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
- val patientPseudonym =
- when (pseudonymizedMtbFileRequest) {
- is DnpmV2MtbFileRequest ->
- PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
+ private fun hasUnacceptedInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
+ return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
+ it.submissionType == SubmissionType.INITIAL &&
+ !(it.submissionAccepted || it.status == RequestStatus.BLOCKED_INITIAL)
}
+ }
- 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!"
- )
+ private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
+ val patientPseudonym =
+ when (pseudonymizedMtbFileRequest) {
+ is DnpmV2MtbFileRequest ->
+ PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
}
- }
-
- requestService.save(
- Request(
- requestId,
- patientPseudonym,
- emptyPatientId(),
- fingerprint(patientPseudonym.value),
- RequestType.DELETE,
- SubmissionType.UNKNOWN,
- requestStatus,
- Tan.empty()
- )
- )
-
- 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,
- submissionType = SubmissionType.UNKNOWN,
- report = Report("Fehler bei der Pseudonymisierung"),
- tan = Tan.empty(),
- )
- )
+
+ val lastMtbFileRequestForPatient =
+ requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
+ val isLastRequestDeletion =
+ requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
+
+ return null != lastMtbFileRequestForPatient &&
+ !isLastRequestDeletion &&
+ lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
}
- }
- private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
- return when (request) {
- is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
+ fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
+ processDeletion(patientId, randomRequestId(), isConsented)
}
- }
- private fun fingerprint(s: String): Fingerprint {
- return Fingerprint(Base32().encodeAsString(DigestUtils.sha256(s))
- .replace("=", "")
- .lowercase())
- }
+ 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,
+ emptyPatientId(),
+ fingerprint(patientPseudonym.value),
+ RequestType.DELETE,
+ SubmissionType.UNKNOWN,
+ requestStatus,
+ Tan.empty()
+ )
+ )
+
+ 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,
+ submissionType = SubmissionType.UNKNOWN,
+ report = Report("Fehler bei der Pseudonymisierung"),
+ tan = Tan.empty(),
+ )
+ )
+ }
+ }
+
+ 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()
+ )
+ }
}