From 7d97365aea81391ae74d64c815261a059bb48c73 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Sun, 6 Apr 2025 13:36:30 +0200 Subject: feat: add endpoint for DNPM-Datamodel V2 using content negotiation (#104) This simply adds an REST endpoint without proper implementation. The goal is to accept DNPM V2 JSON data.--- .../kotlin/dev/dnpm/etl/processor/extensions.kt | 35 ++++++++++++++++++++ .../dnpm/etl/processor/input/KafkaInputListener.kt | 37 +++++++++++++++++++--- .../etl/processor/input/MtbFileRestController.kt | 13 ++++++-- 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/dev/dnpm/etl/processor/extensions.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/dev/dnpm/etl/processor/extensions.kt b/src/main/kotlin/dev/dnpm/etl/processor/extensions.kt new file mode 100644 index 0000000..060ecb2 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/extensions.kt @@ -0,0 +1,35 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package dev.dnpm.etl.processor + +import org.springframework.http.MediaType + +/** + * Custom MediaTypes + * + * @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_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/input/KafkaInputListener.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt index 2aff8cb..e797390 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt @@ -1,7 +1,7 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -22,11 +22,13 @@ package dev.dnpm.etl.processor.input import com.fasterxml.jackson.databind.ObjectMapper import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.MtbFile +import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientId import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.services.RequestProcessor import org.apache.kafka.clients.consumer.ConsumerRecord import org.slf4j.LoggerFactory +import org.springframework.http.MediaType import org.springframework.kafka.listener.MessageListener class KafkaInputListener( @@ -35,10 +37,29 @@ class KafkaInputListener( ) : MessageListener { private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java) - override fun onMessage(data: ConsumerRecord) { - val mtbFile = objectMapper.readValue(data.value(), MtbFile::class.java) + override fun onMessage(record: ConsumerRecord) { + when (guessMimeType(record)) { + MediaType.APPLICATION_JSON_VALUE -> handleBwhcMessage(record) + CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE -> handleDnpmV2Message(record) + else -> { + /* ignore other messages */ + } + } + } + + private fun guessMimeType(record: ConsumerRecord): String { + 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().contentToString() + } + + private fun handleBwhcMessage(record: ConsumerRecord) { + val mtbFile = objectMapper.readValue(record.value(), MtbFile::class.java) val patientId = PatientId(mtbFile.patient.id) - val firstRequestIdHeader = data.headers().headers("requestId")?.firstOrNull() + val firstRequestIdHeader = record.headers().headers("requestId")?.firstOrNull() val requestId = if (null != firstRequestIdHeader) { RequestId(String(firstRequestIdHeader.value())) } else { @@ -61,4 +82,10 @@ class KafkaInputListener( } } } -} \ No newline at end of file + + private fun handleDnpmV2Message(record: ConsumerRecord) { + // Do not handle DNPM-V2 for now + logger.warn("Ignoring MTB File in DNPM V2 format: Not implemented yet") + } + +} 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 123a84f..432711a 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt @@ -21,9 +21,13 @@ package dev.dnpm.etl.processor.input import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.MtbFile +import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientId import dev.dnpm.etl.processor.services.RequestProcessor +import dev.pcvolkmer.mv64e.mtb.Mtb import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -40,7 +44,7 @@ class MtbFileRestController( return ResponseEntity.ok("Test") } - @PostMapping + @PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] ) fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity { if (mtbFile.consent.status == Consent.Status.ACTIVE) { logger.debug("Accepted MTB File for processing") @@ -53,6 +57,11 @@ class MtbFileRestController( return ResponseEntity.accepted().build() } + @PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] ) + fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity { + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build() + } + @DeleteMapping(path = ["{patientId}"]) fun deleteData(@PathVariable patientId: String): ResponseEntity { logger.debug("Accepted patient ID to process deletion") @@ -60,4 +69,4 @@ class MtbFileRestController( return ResponseEntity.accepted().build() } -} \ No newline at end of file +} -- cgit v1.2.3