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

Ig manager integration with the workflow library #1854

Merged
merged 13 commits into from
Apr 4, 2023
Merged
1 change: 1 addition & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ dependencies {
androidTestImplementation(Dependencies.truth)

androidTestImplementation(project(":engine"))
androidTestImplementation(project(":implementationguide"))
androidTestImplementation(project(":workflow"))
androidTestImplementation(project(":workflow-testing"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,22 @@

package com.google.android.fhir.benchmark

import android.content.Context
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import com.google.android.fhir.FhirEngineProvider
import com.google.android.fhir.workflow.FhirOperator
import com.google.android.fhir.implementationguide.IgManager
import com.google.android.fhir.workflow.FhirOperatorBuilder
import com.google.common.truth.Truth.assertThat
import java.io.File
import java.io.InputStream
import kotlinx.coroutines.runBlocking
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Library
import org.hl7.fhir.r4.model.Parameters
import org.junit.Rule
import org.junit.Test
Expand All @@ -48,20 +52,30 @@ class G_CqlEvaluatorBenchmark {
val fhirOperator = runWithTimingDisabled {
val fhirContext = FhirContext.forCached(FhirVersionEnum.R4)
val jsonParser = fhirContext.newJsonParser()
val context: Context = ApplicationProvider.getApplicationContext()

val patientImmunizationHistory =
jsonParser.parseResource(open("/immunity-check/ImmunizationHistory.json")) as Bundle
val fhirEngine = FhirEngineProvider.getInstance(ApplicationProvider.getApplicationContext())
val igManager = IgManager.createInMemory(context)
val lib = jsonParser.parseResource(open("/immunity-check/ImmunityCheck.json")) as Library

runBlocking {
for (entry in patientImmunizationHistory.entry) {
fhirEngine.create(entry.resource)
}
igManager.install(
File(context.filesDir, lib.name).apply {
writeText(jsonParser.encodeResourceToString(lib))
}
)
}

val lib = jsonParser.parseResource(open("/immunity-check/ImmunityCheck.json")) as Bundle

FhirOperator(fhirContext, fhirEngine).also { it.loadLibs(lib) }
FhirOperatorBuilder(context)
.withFhirContext(fhirContext)
.withFhirEngine(fhirEngine)
.withIgManager(igManager)
.build()
}

val results =
Expand Down
4 changes: 2 additions & 2 deletions implementationguide/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ android {
}

sourceSets {
getByName("androidTest").apply { resources.setSrcDirs(listOf("sampledata")) }
getByName("androidTest").apply { resources.setSrcDirs(listOf("testdata")) }

getByName("test").apply { resources.setSrcDirs(listOf("sampledata")) }
getByName("test").apply { resources.setSrcDirs(listOf("testdata")) }
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,27 @@ import com.google.android.fhir.implementationguide.db.impl.entities.ResourceMeta
import com.google.android.fhir.implementationguide.db.impl.entities.toEntity
import java.io.File
import java.io.FileInputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.hl7.fhir.instance.model.api.IBaseResource
import org.hl7.fhir.r4.model.MetadataResource
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
import timber.log.Timber

/** Responsible for importing, accessing and deleting Implementation Guides. */
class IgManager internal constructor(igDatabase: ImplementationGuideDatabase) {
class IgManager internal constructor(private val igDatabase: ImplementationGuideDatabase) {

private val igDao = igDatabase.implementationGuideDao()
private val jsonParser = FhirContext.forR4().newJsonParser()

/**
* * Checks if the [igDependencies] are present in DB. If necessary, downloads the dependencies
* from NPM and imports data from the package manager (populates the metadata of the FHIR
* Resorces)
* * Checks if the [implementationGuides] are present in DB. If necessary, downloads the
* dependencies from NPM and imports data from the package manager (populates the metadata of the
* FHIR Resources)
*/
suspend fun install(vararg igDependencies: ImplementationGuide) {
TODO("not implemented yet")
suspend fun install(vararg implementationGuides: ImplementationGuide) {
TODO("[1937]Not implemented yet ")
}

/**
Expand All @@ -68,20 +70,28 @@ class IgManager internal constructor(igDatabase: ImplementationGuideDatabase) {

/** Imports the IG from the provided [file] to the default dependency. */
suspend fun install(file: File) {
TODO("not implemented yet")
val defaultIgId =
igDao
.getImplementationGuide(DEFAULT_DEPENDENCY.packageId, DEFAULT_DEPENDENCY.version)
?.implementationGuideId
?: igDao.insert(DEFAULT_DEPENDENCY.toEntity(File("NotARealDirectory")))

importFile(defaultIgId, file)
}

/** Loads resources from IGs listed in dependencies. */
suspend fun loadResources(
resourceType: String,
url: String? = null,
id: String? = null,
name: String? = null,
version: String? = null,
): Iterable<IBaseResource> {
val resType = ResourceType.fromCode(resourceType)
val resourceEntities =
when {
url != null -> listOfNotNull(igDao.getResourceWithUrl(url))
id != null -> listOfNotNull(igDao.getResourceWithUrlLike("%$id"))
name != null && version != null ->
listOfNotNull(igDao.getResourcesWithNameAndVersion(resType, name, version))
name != null -> igDao.getResourcesWithName(resType, name)
Expand All @@ -94,8 +104,24 @@ class IgManager internal constructor(igDatabase: ImplementationGuideDatabase) {
suspend fun delete(vararg igDependencies: ImplementationGuide) {
igDependencies.forEach { igDependency ->
val igEntity = igDao.getImplementationGuide(igDependency.packageId, igDependency.version)
igDao.deleteImplementationGuide(igEntity)
igEntity.rootDirectory.deleteRecursively()
if (igEntity != null) {
igDao.deleteImplementationGuide(igEntity)
igEntity.rootDirectory.deleteRecursively()
}
}
}

private suspend fun importFile(igId: Long, file: File) {
val resource =
withContext(Dispatchers.IO) {
try {
FileInputStream(file).use(jsonParser::parseResource)
} catch (exception: Exception) {
Timber.d(exception, "Unable to import file: $file. Parsing to FhirResource failed.")
}
}
when (resource) {
is Resource -> importResource(igId, resource, file)
}
}

Expand All @@ -117,8 +143,18 @@ class IgManager internal constructor(igDatabase: ImplementationGuideDatabase) {
return jsonParser.parseResource(FileInputStream(resourceEntity.resourceFile))
}

fun close() {
igDatabase.close()
}

companion object {
private const val DB_NAME = "implementationguide.db"
val DEFAULT_DEPENDENCY =
ktarasenko marked this conversation as resolved.
Show resolved Hide resolved
ImplementationGuide(
"com.google.android.fhir",
"1.0.0",
"http://github.com/google/android-fhir"
)

/** Creates an [IgManager] backed by the Room DB. */
fun create(context: Context) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ abstract class ImplementationGuideDao {
@Transaction
internal open suspend fun deleteImplementationGuide(name: String, version: String) {
val igEntity = getImplementationGuide(name, version)
deleteImplementationGuide(igEntity)
deleteOrphanedResources()
if (igEntity != null) {
deleteImplementationGuide(igEntity)
deleteOrphanedResources()
}
}

@Query(
Expand All @@ -71,7 +73,7 @@ abstract class ImplementationGuideDao {
internal abstract suspend fun getImplementationGuide(
packageId: String,
version: String?,
): ImplementationGuideEntity
): ImplementationGuideEntity?

@Query("SELECT * from ResourceMetadataEntity")
internal abstract suspend fun getResources(): List<ResourceMetadataEntity>
Expand All @@ -85,6 +87,11 @@ abstract class ImplementationGuideDao {
internal abstract suspend fun getResourceWithUrl(
url: String,
): ResourceMetadataEntity?
// Remove after https://github.com/google/android-fhir/issues/1920
@Query("SELECT * from ResourceMetadataEntity WHERE url LIKE :urlPart")
internal abstract suspend fun getResourceWithUrlLike(
urlPart: String,
): ResourceMetadataEntity?

@Query(
"SELECT * from ResourceMetadataEntity WHERE resourceType = :resourceType AND name = :name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ internal data class ResourceMetadataEntity(
@PrimaryKey(autoGenerate = true) val resourceMetadataId: Long,
val resourceType: ResourceType,
val url: String?,
val version: String?,
val name: String?,
val version: String?,
/** Location of the JSON file with a full Resource. */
val resourceFile: File,
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ internal class IgManagerTest {
@Test
fun `importing IG creates entries in DB`() = runTest {
igManager.install(implementationGuide, dataFolder)
val implementationGuideId =
igDb
.implementationGuideDao()
.getImplementationGuide("anc-cds", "0.3.0")!!
.implementationGuideId

assertThat(
igDb
.implementationGuideDao()
.getImplementationGuidesWithResources(
igDb
.implementationGuideDao()
.getImplementationGuide("anc-cds", "0.3.0")
.implementationGuideId
)
.getImplementationGuidesWithResources(implementationGuideId)
?.resources
)
.hasSize(6)
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"contained": [ {
"resourceType": "RequestGroup",
"id": "MedRequest-Example",
"instantiatesCanonical":[
"http://localhost/PlanDefinition/MedRequest-Example"
],
"status": "draft",
"intent": "proposal",
"subject": {
Expand All @@ -24,6 +27,9 @@
"reference": "Patient/Patient-Example"
}
} ],
"instantiatesCanonical": [
"http://localhost/PlanDefinition/MedRequest-Example"
],
"status": "draft",
"intent": "proposal",
"subject": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"resource": {
"resourceType" : "PlanDefinition",
"id" : "MedRequest-Example",
"url" : "http://localhost/PlanDefinition/MedRequest-Example",
"title" : "This example illustrates a medication request",
"status" : "active",
"action" : [{
Expand Down
8 changes: 8 additions & 0 deletions workflow/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ android {
testInstrumentationRunnerArguments["package"] = "com.google.android.fhir.workflow"
}

sourceSets {
getByName("androidTest").apply { resources.setSrcDirs(listOf("sampledata")) }
ktarasenko marked this conversation as resolved.
Show resolved Hide resolved

getByName("test").apply { resources.setSrcDirs(listOf("sampledata")) }
}

// Added this for fixing out of memory issue in running test cases
tasks.withType<Test>().configureEach {
maxParallelForks = (Runtime.getRuntime().availableProcessors() - 1).takeIf { it > 0 } ?: 1
Expand Down Expand Up @@ -110,6 +116,7 @@ dependencies {
implementation(Dependencies.Cql.translatorElmJackson) // Necessary to import XML/JSON CQL Libs
implementation(Dependencies.Cql.translatorModel) // Overrides HAPI's old versions
implementation(Dependencies.Cql.translatorModelJackson) // Necessary to import XML/JSON ModelInfos
implementation(Dependencies.timber)

// Forces the most recent version of jackson, ignoring what dependencies use.
// Remove these lines when HAPI 6.4 becomes available.
Expand All @@ -131,6 +138,7 @@ dependencies {
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.xerces)
implementation(project(":engine"))
implementation(project(":implementationguide"))

testImplementation(Dependencies.AndroidxTest.core)
testImplementation(Dependencies.jsonAssert)
Expand Down
Loading