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

Schema management #214

Merged
merged 3 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
package io.tofhir.common.model

import io.tofhir.common.util.HashUtil
import org.json4s.JsonAST.{JObject, JString}

import java.util.UUID

/**
* Entity representing a FHIR StructureDefinition in the context of toFHIR
*
* @param id Identifier of the schema
* @param url URL of the schema
* @param `type` Type of entities that this schema represents
* @param name Name of the schema
* @param description Description of the schema
* @param rootDefinition Root element definition for the schema i.e. the first element in the definition
* @param fieldDefinitions Rest of the element definitions
*/
case class SchemaDefinition(id: String = UUID.randomUUID().toString,
case class SchemaDefinition(id: String,
url: String,
`type`: String,
name: String,
description: Option[String],
rootDefinition: Option[SimpleStructureDefinition],
fieldDefinitions: Option[Seq[SimpleStructureDefinition]]) {

Expand All @@ -38,3 +39,16 @@ case class SchemaDefinition(id: String = UUID.randomUUID().toString,
)
}
}

object SchemaDefinition {
def apply(url: String,
`type`: String,
name: String,
description: Option[String],
rootDefinition: Option[SimpleStructureDefinition],
fieldDefinitions: Option[Seq[SimpleStructureDefinition]]): SchemaDefinition = {
// If id is not provided, use MD5 hash of url as the id
val effectiveId = HashUtil.md5Hash(url)
SchemaDefinition(effectiveId, url, `type`, name, description, rootDefinition, fieldDefinitions)
}
}
27 changes: 27 additions & 0 deletions tofhir-common/src/main/scala/io/tofhir/common/util/HashUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.tofhir.common.util

import java.security.MessageDigest

/**
* Utility class providing hash functions
*/
object HashUtil {

/**
* Given a text input, return MD5 hash representation as text
*
* @param text
* @return
*/
def md5Hash(text: String): String = {
// Get an instance of the MD5 MessageDigest
val md = MessageDigest.getInstance("MD5")

// Convert the input string to bytes and compute the hash
val hashBytes = md.digest(text.getBytes)

// Convert the byte array to a hexadecimal string
hashBytes.map("%02x".format(_)).mkString
}

}
29 changes: 16 additions & 13 deletions tofhir-common/src/main/scala/io/tofhir/common/util/SchemaUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ object SchemaUtil {
* @return
*/
def convertToStructureDefinitionResource(schemaDefinition: SchemaDefinition, fhirVersion: String): Resource = {
val structureDefinitionResource: Resource =
var structureDefinitionResource: Resource =
("id" -> schemaDefinition.id) ~
("resourceType" -> "StructureDefinition") ~
("url" -> schemaDefinition.url) ~
("name" -> schemaDefinition.name) ~
("status" -> "draft") ~
("fhirVersion" -> fhirVersion) ~
("kind" -> "logical") ~
("abstract" -> false) ~
("type" -> schemaDefinition.`type`) ~
("baseDefinition" -> "http://hl7.org/fhir/StructureDefinition/Element") ~
("derivation" -> "specialization") ~
("differential" -> ("element" -> generateElementArray(schemaDefinition.`type`, schemaDefinition.fieldDefinitions.getOrElse(Seq.empty))))
structureDefinitionResource
("name" -> schemaDefinition.name)
if (schemaDefinition.description.isDefined) { // If the description exists, add it here in order not to break the order of JSON elements (better to see the description close to the name)
structureDefinitionResource = structureDefinitionResource ~ ("description" -> schemaDefinition.description.get)
}
structureDefinitionResource ~
("status" -> "draft") ~
("fhirVersion" -> fhirVersion) ~
("kind" -> "logical") ~
("abstract" -> false) ~
("type" -> schemaDefinition.`type`) ~
("baseDefinition" -> "http://hl7.org/fhir/StructureDefinition/Element") ~
("derivation" -> "specialization") ~
("differential" -> ("element" -> generateElementArray(schemaDefinition.`type`, schemaDefinition.fieldDefinitions.getOrElse(Seq.empty))))
}

/**
Expand Down Expand Up @@ -70,11 +73,11 @@ object SchemaUtil {
("profile" -> dt.profiles)
})
// add the field definition if it exists
if(fd.definition.nonEmpty){
if (fd.definition.nonEmpty) {
elementJson = elementJson ~ ("definition" -> fd.definition)
}
// add the field short if it exists
if(fd.short.nonEmpty){
if (fd.short.nonEmpty) {
elementJson = elementJson ~ ("short" -> fd.short)
}
elementJson
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ object FileUtils {

object FileExtensions extends Enumeration {
type FileExtensions = Value
final val StructureDefinition = Value(".StructureDefinition")
final val CSV = Value(".csv")
final val JSON = Value(".json")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ object RedCapUtil {
url = s"$definitionRootUrl/${FHIR_FOUNDATION_RESOURCES.FHIR_STRUCTURE_DEFINITION}/$schemaId",
`type` = schemaId,
name = schemaName,
description = None,
rootDefinition = None,
fieldDefinitions = Some(definitions))
}).toSeq
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object ToFhirHttpServer extends LazyLogging {
var serverBinding: Option[Http.ServerBinding] = None
try {
serverBinding = Some(Await.result(serverBindingFuture, FiniteDuration(10L, TimeUnit.SECONDS)))
logger.info(s"tofHIR server ready at ${webServerConfig.serverHost}:${webServerConfig.serverPort}")
logger.info(s"toFHIR server ready at ${webServerConfig.serverHost}:${webServerConfig.serverPort}")
} catch {
case e: Exception =>
logger.error("Problem while binding to the given HTTP address and port!", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos
importFromFhirServer(projectId)
} ~ pathPrefix(SEGMENT_IMPORT_ZIP) {
importFromZipOfFHIRProfiles(projectId)
} ~ pathPrefix(Segment) { id: String => // Operations on a single schema identified by its id
getSchema(projectId, id) ~ updateSchema(projectId, id) ~ deleteSchema(projectId, id)
} ~ pathPrefix(Segment) { schemaId: String => // Operations on a single schema identified by its id
getSchema(projectId, schemaId) ~ updateSchema(projectId, schemaId) ~ deleteSchema(projectId, schemaId)
}
}
}
Expand Down Expand Up @@ -127,11 +127,11 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos
}
}

private def updateSchema(projectId: String, id: String): Route = {
private def updateSchema(projectId: String, schemaId: String): Route = {
put {
entity(as[SchemaDefinition]) { schemaDefinition =>
complete {
service.putSchema(projectId, id, schemaDefinition) map { _ =>
service.putSchema(projectId, schemaId, schemaDefinition) map { _ =>
StatusCodes.OK -> schemaDefinition
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ class ProjectMappingFolderRepository(mappingRepositoryFolderPath: String, projec
* @return
*/
private def getFileForMapping(projectId: String, fhirMapping: FhirMapping): Future[File] = {
val projectFuture: Future[Option[Project]] = projectFolderRepository.getProject(projectId)
projectFuture.map(project => {
projectFolderRepository.getProject(projectId).map(project => {
if (project.isEmpty) throw new IllegalStateException(s"This should not be possible. ProjectId: $projectId does not exist in the project folder repository.")
val file: File = FileUtils.getPath(mappingRepositoryFolderPath, project.get.id, getFileName(fhirMapping.id)).toFile
// If the project folder does not exist, create it
if (!file.getParentFile.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ trait ISchemaRepository extends IFhirSchemaLoader {
* Update the schema to the repository.
*
* @param projectId Project containing the schema definition
* @param id Identifier of the schema
* @param schemaId Identifier of the schema
* @param schemaDefinition Content of the schema definition
* @return
*/
def updateSchema(projectId: String, id: String, schemaDefinition: SchemaDefinition): Future[Unit]
def updateSchema(projectId: String, schemaId: String, schemaDefinition: SchemaDefinition): Future[Unit]

/**
* Delete the schema from the repository.
Expand Down
Loading
Loading