Skip to content

Commit

Permalink
♻️ Introduce a RepositoryManager and its file-based implementation to…
Browse files Browse the repository at this point in the history
… achieve separation of cocerns and and make it the single responsible for the repositories required by the services.
  • Loading branch information
sinaci committed Nov 29, 2024
1 parent c7b8b00 commit 08e6acd
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package io.tofhir.common.model
*/
trait ICachedRepository {
/**
* Invalidate the internal cache and refresh the cache with the FhirMappings directly from their source
* Invalidate the internal cache and refresh the cache content directly from their source
*/
def invalidate(): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,20 @@ import com.typesafe.scalalogging.LazyLogging
import io.tofhir.engine.Execution.actorSystem.dispatcher
import io.tofhir.server.common.model.ToFhirRestCall
import io.tofhir.server.endpoint.ReloadEndpoint.SEGMENT_RELOAD
import io.tofhir.server.repository.{FolderDBInitializer, IRepositoryManager}
import io.tofhir.server.repository.job.JobFolderRepository
import io.tofhir.server.repository.mapping.ProjectMappingFolderRepository
import io.tofhir.server.repository.mappingContext.MappingContextFolderRepository
import io.tofhir.server.repository.schema.SchemaFolderRepository
import io.tofhir.server.repository.terminology.TerminologySystemFolderRepository
import io.tofhir.server.service.ReloadService
import io.tofhir.server.service.db.FolderDBInitializer

/**
* Endpoint to reload resources from the file system.
* */
class ReloadEndpoint(mappingRepository: ProjectMappingFolderRepository,
schemaRepository: SchemaFolderRepository,
mappingJobRepository: JobFolderRepository,
mappingContextRepository: MappingContextFolderRepository,
terminologySystemFolderRepository: TerminologySystemFolderRepository,
folderDBInitializer: FolderDBInitializer) extends LazyLogging {
class ReloadEndpoint(repositoryManager: IRepositoryManager) extends LazyLogging {

val reloadService: ReloadService = new ReloadService(
mappingRepository,
schemaRepository,
mappingJobRepository,
mappingContextRepository,
terminologySystemFolderRepository,
folderDBInitializer
)
val reloadService: ReloadService = new ReloadService(repositoryManager)

def route(request: ToFhirRestCall): Route = {
pathPrefix(SEGMENT_RELOAD) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import com.typesafe.scalalogging.LazyLogging
import io.onfhir.definitions.common.model.Json4sSupport._
import io.tofhir.engine.Execution.actorSystem.dispatcher
import io.tofhir.server.common.model.{ResourceNotFound, ToFhirRestCall}
import io.tofhir.server.endpoint.TerminologyServiceManagerEndpoint._
import io.onfhir.definitions.common.model.Json4sSupport._
import io.tofhir.server.model.TerminologySystem
import io.tofhir.server.repository.job.JobFolderRepository
import io.tofhir.server.repository.job.IJobRepository
import io.tofhir.server.repository.terminology.ITerminologySystemRepository
import io.tofhir.server.service.terminology.TerminologySystemService
import io.tofhir.server.repository.terminology.codesystem.ICodeSystemRepository
import io.tofhir.server.repository.terminology.conceptmap.IConceptMapRepository
import io.tofhir.server.service.terminology.TerminologySystemService

import scala.concurrent.Future

class TerminologyServiceManagerEndpoint(terminologySystemRepository: ITerminologySystemRepository, conceptMapRepository: IConceptMapRepository,
codeSystemRepository: ICodeSystemRepository, mappingJobRepository: JobFolderRepository) extends LazyLogging {
class TerminologyServiceManagerEndpoint(terminologySystemRepository: ITerminologySystemRepository,
conceptMapRepository: IConceptMapRepository,
codeSystemRepository: ICodeSystemRepository,
mappingJobRepository: IJobRepository) extends LazyLogging {

private val terminologySystemService: TerminologySystemService = new TerminologySystemService(terminologySystemRepository, mappingJobRepository)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import io.tofhir.server.repository.mappingContext.MappingContextFolderRepository
import io.tofhir.server.repository.project.{IProjectRepository, ProjectFolderRepository}
import io.tofhir.server.repository.schema.SchemaFolderRepository
import io.tofhir.server.repository.terminology.{ITerminologySystemRepository, TerminologySystemFolderRepository}
import io.tofhir.server.repository.terminology.codesystem.{CodeSystemRepository, ICodeSystemRepository}
import io.tofhir.server.repository.terminology.conceptmap.{ConceptMapRepository, IConceptMapRepository}
import io.tofhir.server.service.db.FolderDBInitializer
import io.tofhir.server.repository.terminology.codesystem.{CodeSystemFolderRepository, ICodeSystemRepository}
import io.tofhir.server.repository.terminology.conceptmap.{ConceptMapFolderRepository, IConceptMapRepository}
import io.tofhir.server.util.ToFhirRejectionHandler
import io.onfhir.definitions.fhirpath.endpoint.FhirPathFunctionsEndpoint
import io.tofhir.server.repository.{FolderDBInitializer, FolderRepositoryManager, IRepositoryManager}

import java.util.UUID

Expand All @@ -30,27 +30,33 @@ import java.util.UUID
*/
class ToFhirServerEndpoint(toFhirEngineConfig: ToFhirEngineConfig, webServerConfig: WebServerConfig, fhirDefinitionsConfig: FhirDefinitionsConfig, redCapServiceConfig: Option[RedCapServiceConfig]) extends ICORSHandler with IErrorHandler {

val projectRepository: IProjectRepository = new ProjectFolderRepository(toFhirEngineConfig) // creating the repository instance globally as weed a singleton instance
val mappingRepository: ProjectMappingFolderRepository = new ProjectMappingFolderRepository(toFhirEngineConfig.mappingRepositoryFolderPath, projectRepository)
val schemaRepository: SchemaFolderRepository = new SchemaFolderRepository(toFhirEngineConfig.schemaRepositoryFolderPath, projectRepository)
val mappingJobRepository: JobFolderRepository = new JobFolderRepository(toFhirEngineConfig.jobRepositoryFolderPath, projectRepository)
val mappingContextRepository: MappingContextFolderRepository = new MappingContextFolderRepository(toFhirEngineConfig.mappingContextRepositoryFolderPath, projectRepository)
val terminologySystemRepository: ITerminologySystemRepository = new TerminologySystemFolderRepository(toFhirEngineConfig.terminologySystemFolderPath)
val conceptMapRepository: IConceptMapRepository = new ConceptMapRepository(toFhirEngineConfig.terminologySystemFolderPath)
val codeSystemRepository: ICodeSystemRepository = new CodeSystemRepository(toFhirEngineConfig.terminologySystemFolderPath)
private val repositoryManager: IRepositoryManager = new FolderRepositoryManager(toFhirEngineConfig)
// Initialize repositories by reading the resources available in the file system
repositoryManager.init()

// Initialize the projects by reading the resources available in the file system
val folderDBInitializer = new FolderDBInitializer(schemaRepository, mappingRepository, mappingJobRepository, projectRepository.asInstanceOf[ProjectFolderRepository], mappingContextRepository)
folderDBInitializer.init()
private val projectEndpoint = new ProjectEndpoint(
repositoryManager.schemaRepository,
repositoryManager.mappingRepository,
repositoryManager.mappingJobRepository,
repositoryManager.mappingContextRepository,
repositoryManager.projectRepository
)

val terminologyServiceManagerEndpoint = new TerminologyServiceManagerEndpoint(
repositoryManager.terminologySystemRepository,
repositoryManager.conceptMapRepository,
repositoryManager.codeSystemRepository,
repositoryManager.mappingJobRepository
)

val projectEndpoint = new ProjectEndpoint(schemaRepository, mappingRepository, mappingJobRepository, mappingContextRepository, projectRepository)
val fhirDefinitionsEndpoint = new FhirDefinitionsEndpoint(fhirDefinitionsConfig)
val fhirPathFunctionsEndpoint = new FhirPathFunctionsEndpoint(Seq("io.onfhir.path", "io.tofhir.engine.mapping"))
val redcapEndpoint = redCapServiceConfig.map(config => new RedCapEndpoint(config))
val fileSystemTreeStructureEndpoint = new FileSystemTreeStructureEndpoint()
val terminologyServiceManagerEndpoint = new TerminologyServiceManagerEndpoint(terminologySystemRepository, conceptMapRepository, codeSystemRepository, mappingJobRepository)
val metadataEndpoint = new MetadataEndpoint(toFhirEngineConfig, webServerConfig, fhirDefinitionsConfig, redCapServiceConfig)
val reloadEndpoint= new ReloadEndpoint(mappingRepository, schemaRepository, mappingJobRepository, mappingContextRepository, terminologySystemRepository.asInstanceOf[TerminologySystemFolderRepository], folderDBInitializer)

val reloadEndpoint= new ReloadEndpoint(repositoryManager)

// Custom rejection handler to send proper messages to user
val toFhirRejectionHandler: RejectionHandler = ToFhirRejectionHandler.getRejectionHandler()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.tofhir.server.service.db
package io.tofhir.server.repository

import com.typesafe.scalalogging.Logger
import io.onfhir.definitions.common.model.Json4sSupport.formats
Expand All @@ -23,10 +23,10 @@ import scala.language.postfixOps
/**
* Folder/Directory based database initializer implementation.
* */
class FolderDBInitializer(schemaFolderRepository: SchemaFolderRepository,
class FolderDBInitializer(projectFolderRepository: ProjectFolderRepository,
schemaFolderRepository: SchemaFolderRepository,
mappingFolderRepository: ProjectMappingFolderRepository,
mappingJobFolderRepository: JobFolderRepository,
projectFolderRepository: ProjectFolderRepository,
mappingContextRepository: MappingContextFolderRepository) {

private val logger: Logger = Logger(this.getClass)
Expand All @@ -40,12 +40,10 @@ class FolderDBInitializer(schemaFolderRepository: SchemaFolderRepository,

val parsedProjects = if (file.exists()) {
val projects: JArray = FileOperations.readFileIntoJson(file).asInstanceOf[JArray]
val projectMap: Map[String, Project] = projects.arr.map(p => {
val project: Project = initProjectFromMetadata(p.asInstanceOf[JObject])
project.id -> project
})
.toMap
collection.mutable.Map(projectMap.toSeq: _*)
projects.arr.map(p => {
val project: Project = initProjectFromMetadata(p.asInstanceOf[JObject])
project.id -> project
}).toMap
} else {
logger.debug(s"There does not exist a metadata file (${ProjectFolderRepository.PROJECTS_JSON}) for projects. Creating it...")
file.createNewFile()
Expand All @@ -55,7 +53,6 @@ class FolderDBInitializer(schemaFolderRepository: SchemaFolderRepository,
}
// Inject the parsed projects to the repository
projectFolderRepository.setProjects(parsedProjects)
projectFolderRepository.updateProjectsMetadata() // update the metadata file after initialization
}

/**
Expand Down Expand Up @@ -127,7 +124,7 @@ class FolderDBInitializer(schemaFolderRepository: SchemaFolderRepository,
*
* @return
*/
private def initProjectsWithResources(): mutable.Map[String, Project] = {
private def initProjectsWithResources(): Map[String, Project] = {
// Map keeping the projects. It uses the project name as a key.
val projects: mutable.Map[String, Project] = mutable.Map.empty

Expand Down Expand Up @@ -171,7 +168,7 @@ class FolderDBInitializer(schemaFolderRepository: SchemaFolderRepository,
projects.put(projectId, project.copy(mappingContexts = mappingContextIdList))
}

projects
projects.toMap
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.tofhir.server.repository

import io.tofhir.engine.config.ToFhirEngineConfig
import io.tofhir.server.repository.job.JobFolderRepository
import io.tofhir.server.repository.mapping.ProjectMappingFolderRepository
import io.tofhir.server.repository.mappingContext.MappingContextFolderRepository
import io.tofhir.server.repository.project.ProjectFolderRepository
import io.tofhir.server.repository.schema.SchemaFolderRepository
import io.tofhir.server.repository.terminology.TerminologySystemFolderRepository
import io.tofhir.server.repository.terminology.codesystem.CodeSystemFolderRepository
import io.tofhir.server.repository.terminology.conceptmap.ConceptMapFolderRepository

/**
* Folder/file based implementation of the RepositoryManager where all managed repositories are folder-based.
*
* @param toFhirEngineConfig
*/
class FolderRepositoryManager(toFhirEngineConfig: ToFhirEngineConfig) extends IRepositoryManager {

override val projectRepository: ProjectFolderRepository = new ProjectFolderRepository(toFhirEngineConfig)
override val mappingRepository: ProjectMappingFolderRepository = new ProjectMappingFolderRepository(toFhirEngineConfig.mappingRepositoryFolderPath, projectRepository)
override val schemaRepository: SchemaFolderRepository = new SchemaFolderRepository(toFhirEngineConfig.schemaRepositoryFolderPath, projectRepository)
override val mappingJobRepository: JobFolderRepository = new JobFolderRepository(toFhirEngineConfig.jobRepositoryFolderPath, projectRepository)
override val mappingContextRepository: MappingContextFolderRepository = new MappingContextFolderRepository(toFhirEngineConfig.mappingContextRepositoryFolderPath, projectRepository)

override val terminologySystemRepository: TerminologySystemFolderRepository = new TerminologySystemFolderRepository(toFhirEngineConfig.terminologySystemFolderPath)
override val conceptMapRepository: ConceptMapFolderRepository = new ConceptMapFolderRepository(toFhirEngineConfig.terminologySystemFolderPath)
override val codeSystemRepository: CodeSystemFolderRepository = new CodeSystemFolderRepository(toFhirEngineConfig.terminologySystemFolderPath)

private val folderDBInitializer = new FolderDBInitializer(
projectRepository,
schemaRepository,
mappingRepository,
mappingJobRepository,
mappingContextRepository
)

/**
* Initializes the Repository Manager's internal database (the projects.json file) after initialization of
* each individual repository.
*/
override def init(): Unit = {
folderDBInitializer.init()
}

/**
* Deletes the internal repository database (the projects.json file) for a fresh start (usually after cache invalidate operations)
*/
override def clear(): Unit = {
folderDBInitializer.removeProjectsJsonFile()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.tofhir.server.repository

import io.tofhir.server.repository.job.IJobRepository
import io.tofhir.server.repository.mapping.IMappingRepository
import io.tofhir.server.repository.mappingContext.IMappingContextRepository
import io.tofhir.server.repository.project.IProjectRepository
import io.tofhir.server.repository.schema.ISchemaRepository
import io.tofhir.server.repository.terminology.ITerminologySystemRepository
import io.tofhir.server.repository.terminology.codesystem.ICodeSystemRepository
import io.tofhir.server.repository.terminology.conceptmap.IConceptMapRepository

/**
* Manage the repositories throughout toFHIR
*/
trait IRepositoryManager {
val projectRepository: IProjectRepository
val mappingRepository: IMappingRepository
val schemaRepository: ISchemaRepository
val mappingJobRepository: IJobRepository
val mappingContextRepository: IMappingContextRepository

val terminologySystemRepository: ITerminologySystemRepository
val conceptMapRepository: IConceptMapRepository
val codeSystemRepository: ICodeSystemRepository

/**
* Initialize the repository
*/
def init(): Unit

/**
* Clean-up the repository database
*/
def clear(): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ trait IMappingRepository extends IFhirMappingRepository with IProjectList[FhirMa
*
* @param projectId The unique identifier of the project for which mappings should be deleted.
*/
def deleteProjectMappings(projectId: String): Future[Unit]
def deleteAllMappings(projectId: String): Future[Unit]

/**
* Retrieves the identifiers of mappings referencing the given schema in their definitions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class ProjectMappingFolderRepository(mappingRepositoryFolderPath: String, projec
*
* @param projectId The unique identifier of the project for which mappings should be deleted.
*/
override def deleteProjectMappings(projectId: String): Future[Unit] = {
override def deleteAllMappings(projectId: String): Future[Unit] = {
Future {
// delete mapping definitions for the project
org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.getPath(mappingRepositoryFolderPath, projectId).toFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ trait IMappingContextRepository extends ICachedRepository with IProjectList[Stri
*
* @param projectId The unique identifier of the project for which mapping contexts should be deleted.
*/
def deleteProjectMappingContexts(projectId: String): Unit
def deleteAllMappingContexts(projectId: String): Unit

/**
* Update the mapping context header by its id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class MappingContextFolderRepository(mappingContextRepositoryFolderPath: String,
*
* @param projectId The unique identifier of the project for which mapping contexts should be deleted.
*/
override def deleteProjectMappingContexts(projectId: String): Unit = {
override def deleteAllMappingContexts(projectId: String): Unit = {
Future {
// delete mapping context definitions for the project
org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.getPath(mappingContextRepositoryFolderPath, projectId).toFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ trait IProjectRepository {
*/
def getAllProjects: Future[Seq[Project]]



/**
* Save project to the repository.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ class ProjectFolderRepository(config: ToFhirEngineConfig) extends IProjectReposi
private val logger: Logger = Logger(this.getClass)

// Project cache keeping the up-to-date list of projects
private var projects: mutable.Map[String, Project] = mutable.Map.empty
private val projects: mutable.Map[String, Project] = mutable.Map.empty

/**
* Initializes the projects cache
* Initializes the projects cache with the given Map of projects and persist to the file (projects.json)
*
* @param projects
*/
def setProjects(projects: mutable.Map[String, Project]): Unit = {
this.projects = projects
def setProjects(vProjects: Map[String, Project]): Unit = {
this.projects.clear()
this.projects ++= vProjects
this.updateProjectsMetadata()
}

/**
Expand Down Expand Up @@ -326,7 +328,7 @@ class ProjectFolderRepository(config: ToFhirEngineConfig) extends IProjectReposi
/**
* Updates the projects metadata with project included in the cache.
*/
def updateProjectsMetadata(): Unit = {
private def updateProjectsMetadata(): Unit = {
val file = FileUtils.getPath(ProjectFolderRepository.PROJECTS_JSON).toFile
// when projects metadata file does not exist, create it
if (!file.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ trait ISchemaRepository extends IFhirSchemaLoader with ICachedRepository with IP
*
* @param projectId The unique identifier of the project for which schemas should be deleted.
*/
def deleteProjectSchemas(projectId: String): Future[Unit]
def deleteAllSchemas(projectId: String): Future[Unit]

/**
* Retrieve the Structure Definition of the schema identified by its id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class SchemaFolderRepository(schemaRepositoryFolderPath: String, projectReposito
*
* @param projectId The unique identifier of the project for which schemas should be deleted.
*/
override def deleteProjectSchemas(projectId: String): Future[Unit] = {
override def deleteAllSchemas(projectId: String): Future[Unit] = {
Future {
// delete schema definitions for the project
org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.getPath(schemaRepositoryFolderPath, projectId).toFile)
Expand Down
Loading

0 comments on commit 08e6acd

Please sign in to comment.