Skip to content

Commit

Permalink
♻️ Decouple SchemaDefinitionService from FhirDefinitionService
Browse files Browse the repository at this point in the history
  • Loading branch information
Şenan Postacı authored and Dogukan Cavdaroglu committed Oct 25, 2024
1 parent 8adcd53 commit aee984d
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import io.tofhir.engine.util.MajorFhirVersion
import io.tofhir.server.common.model.{BadRequest, ToFhirRestCall}
import io.tofhir.server.endpoint.FhirDefinitionsEndpoint._
import io.tofhir.server.fhir.FhirDefinitionsConfig
import io.tofhir.server.repository.mapping.IMappingRepository
import io.tofhir.server.repository.schema.ISchemaRepository
import io.tofhir.server.service.fhir.FhirDefinitionsService
import io.tofhir.server.service.fhir.base.FhirBaseProfilesService

class FhirDefinitionsEndpoint(fhirDefinitionsConfig: FhirDefinitionsConfig, schemaRepository: ISchemaRepository, mappingRepository: IMappingRepository) extends LazyLogging {
class FhirDefinitionsEndpoint(fhirDefinitionsConfig: FhirDefinitionsConfig) extends LazyLogging {

val service: FhirDefinitionsService = new FhirDefinitionsService(fhirDefinitionsConfig, schemaRepository, mappingRepository)
val service: FhirDefinitionsService = new FhirDefinitionsService(fhirDefinitionsConfig)

def route(request: ToFhirRestCall): Route =
pathPrefix(SEGMENT_FHIR_DEFINITIONS) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos
val projectId: String = request.projectId.get
pathEndOrSingleSlash {
parameterMap { queryParams =>
queryParams.get("url") match {
case Some(url) => getSchemaByUrl(url)
queryParams.get(SchemaDefinitionEndpoint.QUERY_PARAM_URL) match {
case Some(url) => getSchemaByUrl(url, queryParams.get(SchemaDefinitionEndpoint.QUERY_PARAM_TYPE))
case None => getAllSchemas(request) ~ createSchema(projectId, queryParams.getOrElse("format", SchemaFormats.SIMPLE_STRUCTURE_DEFINITION)) // Operations on all schemas
}
}
Expand Down Expand Up @@ -114,11 +114,17 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos
}
}

private def getSchemaByUrl(url: String): Route = {
private def getSchemaByUrl(url: String, returnType: Option[String]): Route = {
get {
complete {
service.getSchemaByUrl(url) map {
case Some(schemaDefinition) => StatusCodes.OK -> schemaDefinition
case Some(schemaDefinition) =>
returnType match {
case Some(SchemaFormats.SIMPLE_STRUCTURE_DEFINITION) =>
StatusCodes.OK -> schemaDefinition.fieldDefinitions.get
case _ =>
StatusCodes.OK -> schemaDefinition
}
case None => StatusCodes.NotFound -> {
throw ResourceNotFound("Schema not found", s"Schema definition with url $url not found")
}
Expand Down Expand Up @@ -245,12 +251,14 @@ object SchemaDefinitionEndpoint {
val SEGMENT_REDCAP = "redcap"
val SEGMENT_IMPORT = "import"
val SEGMENT_IMPORT_ZIP = "import-zip"
val QUERY_PARAM_TYPE = "type"
val QUERY_PARAM_URL = "url"
}

/**
* The schema formats available for POST and GET schema methods
*/
object SchemaFormats{
object SchemaFormats {
val STRUCTURE_DEFINITION = "StructureDefinition"
val SIMPLE_STRUCTURE_DEFINITION = "SimpleStructureDefinition"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ToFhirServerEndpoint(toFhirEngineConfig: ToFhirEngineConfig, webServerConf
new FolderDBInitializer(schemaRepository, mappingRepository, mappingJobRepository, projectRepository, mappingContextRepository).init()

val projectEndpoint = new ProjectEndpoint(schemaRepository, mappingRepository, mappingJobRepository, mappingContextRepository, projectRepository)
val fhirDefinitionsEndpoint = new FhirDefinitionsEndpoint(fhirDefinitionsConfig,schemaRepository, mappingRepository)
val fhirDefinitionsEndpoint = new FhirDefinitionsEndpoint(fhirDefinitionsConfig)
val fhirPathFunctionsEndpoint = new FhirPathFunctionsEndpoint()
val redcapEndpoint = redCapServiceConfig.map(config => new RedCapEndpoint(config))
val fileSystemTreeStructureEndpoint = new FileSystemTreeStructureEndpoint()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,18 @@ import io.tofhir.engine.util.{FileUtils, MajorFhirVersion}
import io.tofhir.server.common.model.BadRequest
import io.tofhir.server.fhir.{FhirDefinitionsConfig, FhirEndpointResourceReader}
import io.tofhir.server.model.ProfileInfo
import io.tofhir.server.repository.mapping.IMappingRepository
import io.tofhir.server.repository.schema.ISchemaRepository
import io.tofhir.server.service.SchemaDefinitionService
import org.json4s.JsonAST.JObject
import org.json4s.jackson.JsonMethods
import org.json4s.jackson.JsonMethods.compact

import java.net.{MalformedURLException, URL}
import scala.collection.mutable
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration}
import scala.concurrent.Future
import scala.concurrent.duration.{DurationInt, FiniteDuration}

class FhirDefinitionsService(fhirDefinitionsConfig: FhirDefinitionsConfig,schemaRepository: ISchemaRepository, mappingRepository: IMappingRepository) {
class FhirDefinitionsService(fhirDefinitionsConfig: FhirDefinitionsConfig) {

private val logger: Logger = Logger(this.getClass)
// service to manage schemas
private val schemaDefinitionService: SchemaDefinitionService = new SchemaDefinitionService(schemaRepository, mappingRepository)

// timeout for the proxy request to the FHIR validator
val timeout: FiniteDuration = 20.seconds
Expand Down Expand Up @@ -135,19 +130,7 @@ class FhirDefinitionsService(fhirDefinitionsConfig: FhirDefinitionsConfig,schema
val urlWithoutVersion = canonicalParts.head
val version = canonicalParts.drop(1).lastOption
// retrieve the simplified structure definitions from the cache, or simplify it using SimpleStructureDefinitionService if not cached
var simpleStructureDefinitions: Seq[SimpleStructureDefinition] = simplifiedStructureDefinitionCache.getOrElseUpdate(url, simpleStructureDefinitionService.simplifyStructureDefinition(urlWithoutVersion, version))
// if not found in SimpleStructureDefinitionService, check the Schema Repository
// This is because the schema can be created into the SchemaRepository through toFHIR API, and it is being used as the target schema for mapping definitions (e.g., flat schemas for FHIR-2-tabular mappings)
if(simpleStructureDefinitions.isEmpty){
// fetch the schema from the schema repository
val schema = Await.result(schemaDefinitionService.getSchemaByUrl(url), Duration.Inf)
// if the schema is found, get the field definitions
if(schema.nonEmpty){
simpleStructureDefinitions = schema.get.fieldDefinitions.get
}
}
// return the simplified structure definitions
simpleStructureDefinitions
simplifiedStructureDefinitionCache.getOrElseUpdate(url, simpleStructureDefinitionService.simplifyStructureDefinition(urlWithoutVersion, version))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package io.tofhir.server.endpoint
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes}
import io.tofhir.OnFhirTestContainer
import io.tofhir.common.model.Json4sSupport.formats
import io.tofhir.common.model.{DataTypeWithProfiles, SchemaDefinition, SimpleStructureDefinition}
import io.tofhir.common.model.{SchemaDefinition, SimpleStructureDefinition}
import io.tofhir.engine.util.MajorFhirVersion
import io.tofhir.server.BaseEndpointTest
import io.tofhir.server.endpoint.FhirDefinitionsEndpoint.{DefinitionsQuery, QUERY_PARAM_PROFILE, QUERY_PARAM_Q}
import org.json4s.JsonAST.{JString, JValue}
import org.json4s._
import org.json4s.jackson.JsonMethods
import org.json4s.jackson.Serialization.writePretty

import scala.io.Source

Expand Down Expand Up @@ -167,39 +166,6 @@ class FhirDefinitionsEndpointTest extends BaseEndpointTest with OnFhirTestContai
JsonMethods.parse(responseAs[String]).extract[Seq[SimpleStructureDefinition]].length shouldEqual 22
}
}

/**
* Test case to verify retrieval of simplified element definitions for a given schema.
*
* This test performs the following steps:
* 1. Creates a schema with two elements and posts it to the Schema Definition endpoint.
* 2. Sends a GET request to the FHIR Definitions endpoint with the schema URL and expects
* a sequence of SimpleStructureDefinition objects to be returned in the response.
*/
"retrieve simplified element definitions of a schema" in {
// create a schema with two elements
val schemaUrl: String = "https://example.com/fhir/StructureDefinition/schema"
val schema: SchemaDefinition = SchemaDefinition(url = schemaUrl, version = "1.4.2", `type` = "Ty", name = "name", description = Some("description"), rootDefinition = None, fieldDefinitions = Some(Seq(
SimpleStructureDefinition(id = "element-with-definition",
path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true,
isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None,
boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None,
sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None),
SimpleStructureDefinition(id = "element-with-no-definition",
path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true,
isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None,
boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None,
sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None)
)))
Post(s"/${webServerConfig.baseUri}/${ProjectEndpoint.SEGMENT_PROJECTS}/$projectId/${SchemaDefinitionEndpoint.SEGMENT_SCHEMAS}", HttpEntity(ContentTypes.`application/json`, writePretty(schema))) ~> route ~> check {
status shouldEqual StatusCodes.Created
}
// retrieve the simplified element definitions of this schema
Get(s"/${webServerConfig.baseUri}/${FhirDefinitionsEndpoint.SEGMENT_FHIR_DEFINITIONS}?$QUERY_PARAM_Q=${DefinitionsQuery.ELEMENTS}&$QUERY_PARAM_PROFILE=$schemaUrl") ~> route ~> check {
status shouldEqual StatusCodes.OK
JsonMethods.parse(responseAs[String]).extract[Seq[SimpleStructureDefinition]].length shouldEqual 2
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.tofhir.engine.util.FhirMappingJobFormatter.formats
import io.tofhir.engine.util.FileUtils
import io.tofhir.engine.util.FileUtils.FileExtensions
import io.tofhir.server.BaseEndpointTest
import io.tofhir.server.endpoint.SchemaDefinitionEndpoint.{QUERY_PARAM_TYPE, QUERY_PARAM_URL}
import io.tofhir.server.model.{ImportSchemaSettings, InferTask}
import io.tofhir.server.util.{FileOperations, TestUtil}
import org.json4s.JArray
Expand Down Expand Up @@ -597,6 +598,39 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer {
response should include("Detail: The schema with URL 'https://aiccelerate.eu/fhir/StructureDefinition/AIC-Condition' references a non-existent schema: 'https://aiccelerate.eu/fhir/StructureDefinition/AIC-Patient'. Ensure all referenced schemas exist.")
}
}

/**
* Test case to verify retrieval of simplified element definitions for a given schema.
*
* This test performs the following steps:
* 1. Creates a schema with two elements and posts it to the Schema Definition endpoint.
* 2. Sends a GET request to the Schema Definition endpoint with the schema URL and SimpleStructureDefinition format
* and expects a sequence of SimpleStructureDefinition objects to be returned in the response.
*/
"retrieve simplified element definitions of a schema" in {
// create a schema with two elements
val schemaUrl: String = "https://example.com/fhir/StructureDefinition/schema"
val schema: SchemaDefinition = SchemaDefinition(url = schemaUrl, version = "1.4.2", `type` = "Ty", name = "name", description = Some("description"), rootDefinition = None, fieldDefinitions = Some(Seq(
SimpleStructureDefinition(id = "element-with-definition",
path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true,
isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None,
boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None,
sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None),
SimpleStructureDefinition(id = "element-with-no-definition",
path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true,
isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None,
boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None,
sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None)
)))
Post(s"/${webServerConfig.baseUri}/${ProjectEndpoint.SEGMENT_PROJECTS}/$projectId/${SchemaDefinitionEndpoint.SEGMENT_SCHEMAS}", HttpEntity(ContentTypes.`application/json`, writePretty(schema))) ~> route ~> check {
status shouldEqual StatusCodes.Created
}
// retrieve the simplified element definitions of this schema
Get(s"/${webServerConfig.baseUri}/${ProjectEndpoint.SEGMENT_PROJECTS}/$projectId/${SchemaDefinitionEndpoint.SEGMENT_SCHEMAS}?$QUERY_PARAM_TYPE=${SchemaFormats.SIMPLE_STRUCTURE_DEFINITION}&$QUERY_PARAM_URL=$schemaUrl") ~> route ~> check {
status shouldEqual StatusCodes.OK
JsonMethods.parse(responseAs[String]).extract[Seq[SimpleStructureDefinition]].length shouldEqual 2
}
}
}

/**
Expand Down

0 comments on commit aee984d

Please sign in to comment.