Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Kotlinx serialization #99

Merged
merged 11 commits into from
Aug 16, 2024
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ hibernateTypesVersion=3.7.3
commonsIOVersion=2.15.1
commonsCompressVersion=1.26.2
rsqlParserVersion=2.1.0
jacksonVersion=2.13.3
jacksonVersion=2.17.1
flywayVersion=10.10.0
guavaVersion=33.1.0-jre
springdocVersion=1.8.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package dk.cachet.carp.webservices.account.domain
import dk.cachet.carp.webservices.security.authorization.Role
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotBlank
import kotlinx.serialization.Serializable

@Serializable
data class AccountRequest(
@field:NotBlank
@field:Email
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package dk.cachet.carp.webservices.common.serialisers
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer
import com.fasterxml.jackson.module.kotlin.KotlinModule
import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.common.application.devices.DeviceRegistration
Expand Down Expand Up @@ -105,7 +106,10 @@ class ObjectMapperConfig(validationMessages: MessageBase) : SimpleModule() {
this.addDeserializer(StudyDetails::class.java, StudyDetailsDeserializer(validationMessages))
// ParticipantGroupStatus
this.addSerializer(ParticipantGroupStatus::class.java, ParticipantGroupStatusSerializer(validationMessages))
this.addDeserializer(ParticipantGroupStatus::class.java, ParticipantGroupStatusDeserializer(validationMessages))
this.addDeserializer(
ParticipantGroupStatus::class.java,
ParticipantGroupStatusDeserializer(validationMessages),
)
// ProtocolFactoryServiceRequest
this.addSerializer(
ProtocolFactoryServiceRequest::class.java,
Expand Down Expand Up @@ -178,6 +182,9 @@ class ObjectMapperConfig(validationMessages: MessageBase) : SimpleModule() {
// DataStreamBatch
this.addSerializer(DataStreamBatch::class.java, DataStreamBatchSerializer(validationMessages))
this.addDeserializer(DataStreamBatch::class.java, DataStreamBatchDeserializer(validationMessages))

this.addSerializer(java.time.Instant::class.java, InstantSerializer.INSTANCE)
this.addDeserializer(java.time.Instant::class.java, InstantDeserializer.INSTANT)
}

/**
Expand All @@ -194,7 +201,7 @@ class ObjectMapperConfig(validationMessages: MessageBase) : SimpleModule() {

objectMapper.registerModule(this)

objectMapper.registerModule(JavaTimeModule())
// objectMapper.registerModule(JavaTimeModule())
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)

return objectMapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.deployments.domain.DeploymentRepository
import dk.cachet.carp.deployments.domain.StudyDeploymentSnapshot
import dk.cachet.carp.webservices.common.configuration.internationalisation.service.MessageBase
import dk.cachet.carp.webservices.common.input.WS_JSON
import dk.cachet.carp.webservices.deployment.domain.StudyDeployment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -28,7 +29,7 @@ class CoreDeploymentRepository(

override suspend fun add(studyDeployment: CoreStudyDeployment) =
withContext(Dispatchers.IO) {
if (studyDeploymentRepository.findByDeploymentId(studyDeployment.id.stringRepresentation).isPresent) {
if (studyDeploymentRepository.findByDeploymentId(studyDeployment.id.stringRepresentation) != null) {
LOGGER.warn("Deployment already exists, id: ${studyDeployment.id.stringRepresentation}")
throw IllegalArgumentException(
validationMessages.get(
Expand All @@ -49,7 +50,7 @@ class CoreDeploymentRepository(
override suspend fun getStudyDeploymentBy(id: UUID) =
withContext(Dispatchers.IO) {
val result = getWSDeploymentById(id) ?: return@withContext null
val snapshot = objectMapper.treeToValue(result.snapshot, StudyDeploymentSnapshot::class.java)
val snapshot = WS_JSON.decodeFromString<StudyDeploymentSnapshot>(result.snapshot!!.toString())
CoreStudyDeployment.fromSnapshot(snapshot)
}

Expand Down Expand Up @@ -92,15 +93,15 @@ class CoreDeploymentRepository(

fun getWSDeploymentById(id: UUID): StudyDeployment? {
val optionalResult = studyDeploymentRepository.findByDeploymentId(id.stringRepresentation)
if (!optionalResult.isPresent) {
if (optionalResult == null) {
LOGGER.info("Deployment is not found, id: ${id.stringRepresentation}")
return null
}
return optionalResult.get()
return optionalResult
}

private fun mapWSDeploymentToCore(deployment: StudyDeployment): CoreStudyDeployment {
val snapshot = objectMapper.treeToValue(deployment.snapshot, StudyDeploymentSnapshot::class.java)
val snapshot = WS_JSON.decodeFromString<StudyDeploymentSnapshot>(deployment.snapshot!!.toString())
return CoreStudyDeployment.fromSnapshot(snapshot)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import dk.cachet.carp.deployments.domain.users.AccountParticipation
import dk.cachet.carp.deployments.domain.users.ParticipantGroup
import dk.cachet.carp.deployments.domain.users.ParticipantGroupSnapshot
import dk.cachet.carp.deployments.domain.users.ParticipationRepository
import dk.cachet.carp.webservices.common.configuration.internationalisation.service.MessageBase
import dk.cachet.carp.webservices.common.input.WS_JSON
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.logging.log4j.LogManager
Expand All @@ -23,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional
class CoreParticipationRepository(
private val participantGroupRepository: ParticipantGroupRepository,
private val objectMapper: ObjectMapper,
private val validationMessage: MessageBase,
) : ParticipationRepository {
companion object {
private val LOGGER: Logger = LogManager.getLogger()
Expand All @@ -33,19 +36,18 @@ class CoreParticipationRepository(
*/
override suspend fun getParticipantGroup(studyDeploymentId: UUID): ParticipantGroup? =
withContext(Dispatchers.IO) {
val optionalGroup =
val group =
participantGroupRepository.findByStudyDeploymentId(
studyDeploymentId.stringRepresentation,
)

if (!optionalGroup.isPresent) {
checkNotNull(group) {
LOGGER.warn(
"Participant group was not found for deployment with id: ${studyDeploymentId.stringRepresentation}",
)
validationMessage.get("participantGroup.notFound", studyDeploymentId.stringRepresentation)
return@withContext null
}

val group = optionalGroup.get()
mapParticipantGroupSnapshotJsonNodeToParticipantGroup(group.snapshot!!)
}

Expand Down Expand Up @@ -81,11 +83,11 @@ class CoreParticipationRepository(
participantGroupRepository.findByStudyDeploymentId(
group.studyDeploymentId.stringRepresentation,
)
val snapshotToSave = objectMapper.valueToTree<JsonNode>(group.getSnapshot())
val snapshotToSave = WS_JSON.encodeToString(ParticipantGroupSnapshot.serializer(), group.getSnapshot())

if (!optionalGroup.isPresent) {
if (optionalGroup == null) {
val newParticipantGroup = dk.cachet.carp.webservices.deployment.domain.ParticipantGroup()
newParticipantGroup.snapshot = snapshotToSave
newParticipantGroup.snapshot = objectMapper.valueToTree(snapshotToSave)
val savedGroup = participantGroupRepository.save(newParticipantGroup)
LOGGER.info(
"New participant group with id: ${savedGroup.id} " +
Expand All @@ -94,11 +96,10 @@ class CoreParticipationRepository(
return@withContext null
}

val storedGroup = optionalGroup.get()
val oldSnapshot = storedGroup.snapshot!!
storedGroup.snapshot = snapshotToSave
participantGroupRepository.save(storedGroup)
LOGGER.info("Participant Group with id: ${storedGroup.id} is updated with a new snapshot.")
val oldSnapshot = optionalGroup.snapshot!!
optionalGroup.snapshot = objectMapper.valueToTree(snapshotToSave)
participantGroupRepository.save(optionalGroup)
LOGGER.info("Participant Group with id: ${optionalGroup.id} is updated with a new snapshot.")

mapParticipantGroupSnapshotJsonNodeToParticipantGroup(oldSnapshot)
}
Expand Down Expand Up @@ -127,11 +128,11 @@ class CoreParticipationRepository(
/**
* Maps [ParticipantGroupSnapshot] as a JsonNode to [ParticipantGroup]
*
* @param node The [JsonNode] that needs mapping.
* @param node The [JsonNode] that needs deserialize.
* @return [ParticipantGroup]
*/
private fun mapParticipantGroupSnapshotJsonNodeToParticipantGroup(node: JsonNode): ParticipantGroup {
val snapshot = objectMapper.treeToValue(node, ParticipantGroupSnapshot::class.java)
val snapshot = WS_JSON.decodeFromString(ParticipantGroupSnapshot.serializer(), node.toString())
return ParticipantGroup.fromSnapshot(snapshot)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.util.*

@Repository
interface ParticipantGroupRepository : JpaRepository<ParticipantGroup, Int> {
@Query(
nativeQuery = true,
value = "SELECT * FROM participant_groups WHERE snapshot->>'studyDeploymentId' = ?1",
)
fun findByStudyDeploymentId(studyDeploymentId: String): Optional<ParticipantGroup>
fun findByStudyDeploymentId(studyDeploymentId: String): ParticipantGroup?

@Query(
nativeQuery = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.util.*

@Repository
interface StudyDeploymentRepository : JpaRepository<StudyDeployment, String> {
@Query(value = "SELECT * FROM deployments WHERE snapshot->>'id' = ?1", nativeQuery = true)
fun findByDeploymentId(id: String): Optional<StudyDeployment>
fun findByDeploymentId(id: String): StudyDeployment?

@Query(value = "SELECT * FROM deployments WHERE snapshot->>'id' in ?1", nativeQuery = true)
fun findAllByStudyDeploymentIds(deploymentIds: Collection<String>): List<StudyDeployment>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dk.cachet.carp.webservices.deployment.service.impl

import com.fasterxml.jackson.databind.ObjectMapper
import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.deployments.domain.StudyDeploymentSnapshot
import dk.cachet.carp.webservices.common.input.WS_JSON
import dk.cachet.carp.webservices.common.services.CoreServiceContainer
import dk.cachet.carp.webservices.deployment.repository.StudyDeploymentRepository
import dk.cachet.carp.webservices.deployment.service.DeploymentService
Expand All @@ -15,7 +15,6 @@ import java.nio.file.Path
@Service
class DeploymentServiceWrapper(
private val repository: StudyDeploymentRepository,
private val objectMapper: ObjectMapper,
services: CoreServiceContainer,
) : DeploymentService, ResourceExporter<StudyDeploymentSnapshot> {
final override val core = services.deploymentService
Expand All @@ -29,6 +28,6 @@ class DeploymentServiceWrapper(
) = withContext(Dispatchers.IO) {
repository
.findAllByStudyDeploymentIds(deploymentIds.map { it.stringRepresentation })
.map { objectMapper.treeToValue(it.snapshot, StudyDeploymentSnapshot::class.java) }
.map { WS_JSON.decodeFromString(StudyDeploymentSnapshot.serializer(), it.snapshot!!.toString()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import jakarta.validation.constraints.NotNull

/**
* The Data Class [UpdateDocumentRequestDto].
* The [UpdateDocumentRequestDto] represents an document request with the given [name] and [data].
* The [UpdateDocumentRequestDto] represents a document request with the given [name] and [data].
*/
data class UpdateDocumentRequestDto(
/** The [name] of the document. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dk.cachet.carp.protocols.application.ProtocolVersion
import dk.cachet.carp.protocols.application.StudyProtocolSnapshot
import dk.cachet.carp.protocols.domain.*
import dk.cachet.carp.webservices.common.configuration.internationalisation.service.MessageBase
import dk.cachet.carp.webservices.common.input.WS_JSON
import dk.cachet.carp.webservices.protocol.domain.Protocol
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -22,8 +23,8 @@ import org.springframework.stereotype.Service
@Service
class CoreProtocolRepository(
private val protocolRepository: ProtocolRepository,
private val objectMapper: ObjectMapper,
private val validationMessages: MessageBase,
private val objectMapper: ObjectMapper,
) : StudyProtocolRepository {
companion object {
private val LOGGER: Logger = LogManager.getLogger()
Expand Down Expand Up @@ -116,9 +117,9 @@ class CoreProtocolRepository(
}

/**
* Returns all stored versions for the [StudyProtocol] with the specified [protocol].
* Returns all stored versions for the [StudyProtocol] with the specified [Protocol].
*
* @throws IllegalArgumentException when a protocol with the specified [protocol] does not exist.
* @throws IllegalArgumentException when a protocol with the specified [Protocol] does not exist.
*/
override suspend fun getVersionHistoryFor(id: UUID): List<ProtocolVersion> =
withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -213,11 +214,11 @@ class CoreProtocolRepository(
/**
* The [convertJsonNodeToStudyProtocol] function converts a [JsonNode] to a [StudyProtocol].
*
* @param jsonNode The [jsonNode] to convert to a study protocol.
* @param node The [JsonNode] to convert to a study protocol.
* @return A [StudyProtocol] object containing the protocol.
*/
private fun convertJsonNodeToStudyProtocol(jsonNode: JsonNode): StudyProtocol {
val snapshot = objectMapper.treeToValue(jsonNode, StudyProtocolSnapshot::class.java)
private fun convertJsonNodeToStudyProtocol(node: JsonNode): StudyProtocol {
val snapshot = WS_JSON.decodeFromString(StudyProtocolSnapshot.serializer(), node.toString())
return StudyProtocol.fromSnapshot(snapshot)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class ProtocolServiceWrapper(
account: Account? = null,
): ProtocolOverview {
val snapshot = objectMapper.treeToValue(versions.last().snapshot, StudyProtocolSnapshot::class.java)

val owner = account ?: accountService.findByUUID(snapshot.ownerId)

return ProtocolOverview(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ class StudyController(
request.emails.forEach { e -> recruitmentService.core.addParticipant(studyId, EmailAddress(e)) }
}


/**
* Get inactive participants.
* @param studyId The study id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.studies.domain.users.ParticipantRepository
import dk.cachet.carp.studies.domain.users.Recruitment
import dk.cachet.carp.studies.domain.users.RecruitmentSnapshot
import dk.cachet.carp.webservices.common.exception.responses.ResourceNotFoundException
import dk.cachet.carp.webservices.common.input.WS_JSON
import dk.cachet.carp.webservices.study.domain.Recruitment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import dk.cachet.carp.studies.domain.users.Recruitment as CoreRecruitment

@Service
@Transactional
class CoreParticipantRepository(
private val objectMapper: ObjectMapper,
private val recruitmentRepository: RecruitmentRepository,
private val objectMapper: ObjectMapper,
) : ParticipantRepository {
companion object {
private val LOGGER: Logger = LogManager.getLogger()
}

override suspend fun addRecruitment(recruitment: Recruitment) =
override suspend fun addRecruitment(recruitment: CoreRecruitment) =
withContext(Dispatchers.IO) {
val studyId = recruitment.studyId.stringRepresentation
val existingRecruitment = recruitmentRepository.findRecruitmentByStudyId(studyId)
Expand All @@ -33,14 +35,14 @@ class CoreParticipantRepository(

val snapshotJsonNode = objectMapper.valueToTree<JsonNode>(recruitment.getSnapshot())
val newRecruitment =
dk.cachet.carp.webservices.study.domain.Recruitment().apply {
Recruitment().apply {
snapshot = snapshotJsonNode
}
val saved = recruitmentRepository.save(newRecruitment)
LOGGER.info("New recruitment with id ${saved.id} is saved for study with id $studyId.")
}

override suspend fun getRecruitment(studyId: UUID): Recruitment? =
override suspend fun getRecruitment(studyId: UUID): CoreRecruitment? =
withContext(Dispatchers.IO) {
val existingRecruitment = recruitmentRepository.findRecruitmentByStudyId(studyId.stringRepresentation)

Expand All @@ -60,21 +62,21 @@ class CoreParticipantRepository(
true
}

override suspend fun updateRecruitment(recruitment: Recruitment) =
override suspend fun updateRecruitment(recruitment: CoreRecruitment) =
withContext(Dispatchers.IO) {
val studyId = recruitment.studyId.stringRepresentation
val recruitmentFound =
recruitmentRepository.findRecruitmentByStudyId(studyId)
?: throw ResourceNotFoundException("Recruitment with studyId $studyId is not found.")

val newSnapshotNode = objectMapper.valueToTree<JsonNode>(recruitment.getSnapshot())
recruitmentFound.snapshot = newSnapshotNode
val newSnapshotNode = WS_JSON.encodeToString(RecruitmentSnapshot.serializer(), recruitment.getSnapshot())
recruitmentFound.snapshot = objectMapper.valueToTree(newSnapshotNode)
recruitmentRepository.save(recruitmentFound)
LOGGER.info("Recruitment with studyId $studyId is updated.")
}

private fun mapWSRecruitmentToCore(recruitment: dk.cachet.carp.webservices.study.domain.Recruitment): Recruitment {
val snapshot = objectMapper.treeToValue(recruitment.snapshot!!, RecruitmentSnapshot::class.java)
return Recruitment.fromSnapshot(snapshot)
private fun mapWSRecruitmentToCore(recruitment: Recruitment): CoreRecruitment {
val snapshot = WS_JSON.decodeFromString(RecruitmentSnapshot.serializer(), recruitment.snapshot!!.toString())
return CoreRecruitment.fromSnapshot(snapshot)
}
}
Loading
Loading