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

Implement StructureMap-based extraction #566

Merged
merged 23 commits into from
Jul 21, 2021

Conversation

ekigamba
Copy link
Contributor

@ekigamba ekigamba commented Jun 21, 2021

IMPORTANT: All PRs must be linked to an issue (except for extremely trivial and straightforward changes).

Fixes #543

Description

  • Refactor ResourceMapper#extract function to support both definition-based and StructureMap-based
  • Add interface to allow external implementation of fetching StructureMap
  • Add tests for StructureMap-based extraction
  • Add ca.uhn.hapi.fhir:org.hl7.fhir.validation:5.3.0 dependency for running tests
  • Change ResourceMapper#extract signature to return Bundle
  • Add overloaded ResourceMapper#extract method with extra StructureMapProvider param
  • Update ResourceMapperTest methods descriptions

Alternative(s) considered
No, this is a FHIR SDC extraction specification

Type
Feature

Screenshots (if applicable)
NA

Checklist

  • I have read and acknowledged the Code of conduct
  • I have read How to Contribute
  • I have read the Developer's guide
  • I have signed the Google Individual CLA, or I am covered by my company's Corporate CLA
  • I have discussed my proposed solution with code owners in the linked issue(s) and we have agreed upon the general approach
  • I have run ./gradlew spotlessApply and ./gradlew spotlessCheck to check my code follows the style guide of this project
  • I have run ./gradlew check and ./gradlew connectedCheck to test my changes locally
  • I have built and run the reference app(s) to verify my change fixes the issue and/or does not break the reference app(s)

@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch 2 times, most recently from 6e00788 to 323ffaf Compare June 21, 2021 17:21
@ekigamba ekigamba marked this pull request as ready for review June 22, 2021 08:01
@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch from 976ead4 to 73748c5 Compare June 22, 2021 13:51
@@ -126,6 +128,7 @@ object Dependencies {
const val material = "1.3.0"
const val retrofit = "2.7.2"
const val truth = "1.0.1"
const val hapiFhirValidations = "5.3.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alphabetical order please

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this can probably just be merged with the structures version number anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I'll change that

Comment on lines 65 to 111
* Extract a FHIR resource from the [questionnaire] and [questionnaireResponse].
*
* This method assumes there is only one FHIR resource to be extracted from the given
* `questionnaire` and `questionnaireResponse`.
* This method only supports definition-based extraction. StructureMap-based extraction will be
* invoked if the [Questionnaire] declares a targetStructureMap extension. However, the returned
* [Bundle] will be empty since the [StructureMapProvider] is not provided
*
* @return [Bundle] containing the extracted [Resource]s or empty Bundle if the extraction fails.
* An exception might also be thrown in a few cases
*/
fun extract(questionnaire: Questionnaire, questionnaireResponse: QuestionnaireResponse): Bundle {
return extract(questionnaire, questionnaireResponse, null)
}

/**
* Extract a FHIR resource from the [questionnaire] and [questionnaireResponse].
*
* This method supports both Definition-based and StructureMap-based extraction.
* StructureMap-based extraction will fail and an empty [Bundle] will be returned if the
* [structureMapProvider] is not passed.
*/
fun extract(questionnaire: Questionnaire, questionnaireResponse: QuestionnaireResponse): Base {
fun extract(
questionnaire: Questionnaire,
questionnaireResponse: QuestionnaireResponse,
structureMapProvider: StructureMapProvider?
): Bundle {
return if (questionnaire.targetStructureMap == null)
extractByDefinitionBased(questionnaire, questionnaireResponse)
else extractByStructureMapBased(questionnaire, questionnaireResponse, structureMapProvider)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make the structure map provider an optional param in this API and merge these two functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, forgot about the Kotlin default param

structureMapProvider: StructureMapProvider?
): Bundle {
if (structureMapProvider == null) return Bundle()
val pcm = FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val pcm = FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION)
val packageCacheManager = FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

val structureMapUrl = questionnaire.targetStructureMap ?: return Bundle()
val structureMap = structureMapProvider.getStructureMap(structureMapUrl)

val scu = StructureMapUtilities(contextR4)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val scu = StructureMapUtilities(contextR4)
val structureMapUtilities = StructureMapUtilities(contextR4)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

isCanRunWithoutTerminology = true
}

val structureMapUrl = questionnaire.targetStructureMap ?: return Bundle()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has already been checked in the wrapper function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -461,7 +470,7 @@ class ResourceMapperTest {
}

@Test
fun `extract() should allow extracting with unanswered questions`() {
fun `extract() should allow extracting correct Patient resource with unanswered questions using definition-based extraction`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun `extract() should allow extracting correct Patient resource with unanswered questions using definition-based extraction`() {
fun `extract() should perform definition-based extraction with unanswered questions `() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

private fun String.toDateFromFormatYyyyMmDd(): Date? {
return SimpleDateFormat("yyyy-MM-dd").parse(this)
@Test
fun `extract() should allow StructureMap-based extraction and return correct Patient resource`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun `extract() should allow StructureMap-based extraction and return correct Patient resource`() {
fun `extract() should perform StructureMap-based extraction`() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
""".trimIndent()

val pcm = FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as before - avoid using abbreviations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 957 to 958
// Package name manually checked from
// https://simplifier.net/packages?Text=hl7.fhir.core&fhirVersion=All+FHIR+Versions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicates

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

val contextR4 = SimpleWorkerContext.fromPackage(pcm.loadPackage("hl7.fhir.r4.core", "4.0.1"))
contextR4.isCanRunWithoutTerminology = true
val scu = StructureMapUtilities(contextR4)
val map: StructureMap =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did this map come from somewhere? or did you author this?

if it's an example from somewhere can you add a link? thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I authored this

- Refactor ResourceMapper#extract function to support both definition-based and StructureMap-based
- Add interface to allow external implementation of fetching StructureMap
- Add tests for StructureMap-based extraction
- Add ca.uhn.hapi.fhir:org.hl7.fhir.validation:5.3.0 dependency for running tests
- Change ResourceMapper#extract signature to return Bundle
- Add overloaded ResourceMapper#extract method with extra StructureMapProvider param
- Update ResourceMapperTest methods descriptions

Fixes #543
- Use local version of hl7.fhir.r4.core provided with the library aar
- Use private app storage as the local cache of the NPM package
- Add zipping utility used to decompress package into private app storage
- Fix test names in ResourceMapper
@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch from 7eee7b5 to 890f9aa Compare June 29, 2021 22:26
Comment on lines 360 to 371
/**
* The map from the `name`s to `expression`s in the
* [item extraction context extension](http://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-itemExtractionContext.html)
* s.
*/
val Questionnaire.targetStructureMap: String?
get() {
val extensionValue =
this.extension.singleOrNull { it.url == TARGET_STRUCTURE_MAP }?.value ?: return null
return if (extensionValue is CanonicalType) extensionValue.valueAsString else null
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you create a new file called MoreQuestionnaires.kt in the datacapture package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

class ResourceMapperTest {
@Test
fun extract() {
fun `extract() should allow perform definition-based extraction`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun `extract() should allow perform definition-based extraction`() {
fun `extract() should perform definition-based extraction`() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

): Bundle {
if (structureMapProvider == null || context == null) return Bundle()

// Load the npm package
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment doesn't add much -- partly because the function has got a good name.

if you want to explain why this is needed, or how this is done, etc, please update this comment. otherwise, remove this comment if it's simply repeating the function name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

private fun extractByStructureMapBased(
questionnaire: Questionnaire,
questionnaireResponse: QuestionnaireResponse,
structureMapProvider: StructureMapProvider?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
structureMapProvider: StructureMapProvider?,
structureMapProvider: (String) -> StructureMap,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 489 to 492
interface StructureMapProvider {

fun getStructureMap(fullUrl: String): StructureMap?
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ekigamba and others added 4 commits June 30, 2021 21:44
- The SimpleWorkerContext will only be loaded once
- Move Questionnaire extension to MoreQuestionnaire.kt file
- Add tests for MoreQuestionnaire.kt and NpmPackageProvider.kt
@ekigamba ekigamba requested a review from jingtang10 July 5, 2021 20:13
return if (questionnaire.targetStructureMap == null)
extractByDefinitionBased(questionnaire, questionnaireResponse)
else
extractByStructureMapBased(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extractByStructureMapBased(
extractByStructureMap(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds better. Done

context: Context? = null
): Bundle {
return if (questionnaire.targetStructureMap == null)
extractByDefinitionBased(questionnaire, questionnaireResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extractByDefinitionBased(questionnaire, questionnaireResponse)
extractByDefinitions(questionnaire, questionnaireResponse)

Copy link
Contributor Author

@ekigamba ekigamba Jul 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds better. Done

Comment on lines 144 to 145
val structureMapUrl = questionnaire.targetStructureMap!!
val structureMap = structureMapProvider(structureMapUrl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val structureMapUrl = questionnaire.targetStructureMap!!
val structureMap = structureMapProvider(structureMapUrl)
val structureMap = structureMapProvider(questionnaire.targetStructureMap!!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import org.hl7.fhir.r4.context.SimpleWorkerContext
import org.hl7.fhir.utilities.npm.NpmPackage

object NpmPackageProvider {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave this as public?

The whole initial setup process takes around 3 minutes on a good device and I plan to have this happening after installation and application start so that extraction is fast having done this previously.

The target application would have to setup the module after installation and load the modules at application start

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 minutes! that's mad... can you add a documentation here to say this process is very time consuming

Copy link
Contributor Author

@ekigamba ekigamba Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is is already documented here.

I'll add extra documentation on the specific methods and some guiding documentation on the class itself

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

general comment: the file IO functions should be suspend functions

lateinit var npmPackage: NpmPackage
lateinit var contextR4: SimpleWorkerContext

fun loadSimpleWorkerContextWithPackage(context: Context): SimpleWorkerContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun loadSimpleWorkerContextWithPackage(context: Context): SimpleWorkerContext {
fun loadSimpleWorkerContext(context: Context): SimpleWorkerContext {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import org.hl7.fhir.r4.model.Questionnaire
import org.junit.Test

class MoreQuestionnairesKtTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class MoreQuestionnairesKtTest {
class MoreQuestionnairesTest {

Copy link
Contributor Author

@ekigamba ekigamba Jul 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, 🤦 my mistake

Comment on lines 45 to 53
Assert.assertThrows(UninitializedPropertyAccessException::class.java) {
NpmPackageProvider::contextR4.get()
}

val npmPackage = NpmPackageProvider.loadNpmPackage(ApplicationProvider.getApplicationContext())

NpmPackageProvider.loadSimpleWorkerContext(npmPackage)

Truth.assertThat(NpmPackageProvider.contextR4).isNotNull()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are two test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda but logically it's one. The aim of the test is make sure that the method loadSimpleWorkerContext can initialize contextR4 when not previously initialized.

We need to confirm that contextR4 is not initialized before starting the test. Is this what you meant?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you can have a test case for the exception if you don't call the load function. and another test case for if you call the function.

these are fundamentally 2 different cases -- i think for unit tests we try to keep each test case really really simple and follows the 3 steps -> arrange act and assert.

here's some more background: https://livebook.manning.com/book/effective-unit-testing/chapter-4/112

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also import assertThat

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I'll remove the exception asserts in all the test cases and import assertThat

Comment on lines 58 to 64
Assert.assertThrows(UninitializedPropertyAccessException::class.java) {
NpmPackageProvider::npmPackage.get()
}

NpmPackageProvider.loadNpmPackage(ApplicationProvider.getApplicationContext())

Truth.assertThat(NpmPackageProvider.npmPackage).isNotNull()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again two test cases -- same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda but logically it's one. The aim of the test is make sure that the method loadSimpleWorkerContext can initialize npmPackage when not previously initialized.

We need to confirm that npmPackage is not initialized before starting the test. Is this what you meant?


The same applies for the test below in loadSimpleWorkerContext() should cache SimpleWorkerContext

@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch from 980799a to d535675 Compare July 9, 2021 09:34
@ekigamba ekigamba requested a review from jingtang10 July 9, 2021 13:27
ekigamba and others added 2 commits July 9, 2021 17:04
- The default package for the StructureMap is a Bundle. This is no longer inferred from the StructureMap. This helps support declaring multiple targets eg. Bundle, Patient, RelatedPerson
Copy link
Collaborator

@jingtang10 jingtang10 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still prefer to split the tests -- see comments in file

@ekigamba ekigamba requested a review from jingtang10 July 12, 2021 14:03
package com.google.android.fhir.datacapture.mapping

/** Thrown to indicate that the FHIR core package could not be initialized successfully */
class NpmPackageInitializationError : RuntimeException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this should be a checked exception rather than unchecked. What's the reason to choose RuntimeException instead of Exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

packageDirectory.delete()
}

Log.e(ResourceMapper::class.java.name, e.stackTraceToString())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we need this if throwing exception - also strange to use ResourceMapper class name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Comment on lines 29 to 30
* Provides utility for decompressing tar.gz or tgz files using the commons-io and commons-compress
* libraries
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Provides utility for decompressing tar.gz or tgz files using the commons-io and commons-compress
* libraries
* Utilities for decompressing tar.gz or tgz files using the commons-io and commons-compress
* libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed


@Throws(IOException::class)
fun decompress(compressedFilePath: String?, out: File?) {
TarArchiveInputStream(GzipCompressorInputStream(FileInputStream(compressedFilePath))).use { fin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does fin stand for? maybe use a more readable name?

Suggested change
TarArchiveInputStream(GzipCompressorInputStream(FileInputStream(compressedFilePath))).use { fin
TarArchiveInputStream(GzipCompressorInputStream(FileInputStream(compressedFilePath))).use { stream

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* The whole process can take 1-2 minutes.
*/
private fun setupNpmPackage(context: Context) {
val filename = "packages.fhir.org-hl7.fhir.r4.core-4.0.1.tgz"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a static const.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 106 to 107
IOUtils.closeQuietly(inputStream)
IOUtils.closeQuietly(outputStream)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be in finally { ... } block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 138 to 143
val structureMap = structureMapProvider(questionnaire.targetStructureMap!!)

val structureMapUtilities = StructureMapUtilities(contextR4)
val targetResource = Bundle()
structureMapUtilities.transform(contextR4, questionnaireResponse, structureMap, targetResource)
return targetResource
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val structureMap = structureMapProvider(questionnaire.targetStructureMap!!)
val structureMapUtilities = StructureMapUtilities(contextR4)
val targetResource = Bundle()
structureMapUtilities.transform(contextR4, questionnaireResponse, structureMap, targetResource)
return targetResource
return Bundle().apply {
StructureMapUtilities(contextR4).transform(contextR4, questionnaireResponse, structureMapProvider(questionnaire.targetStructureMap!!), this)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import org.hl7.fhir.r4.context.SimpleWorkerContext
import org.hl7.fhir.utilities.npm.NpmPackage

object NpmPackageProvider {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

general comment: the file IO functions should be suspend functions

lateinit var npmPackage: NpmPackage
lateinit var contextR4: SimpleWorkerContext

fun loadSimpleWorkerContext(context: Context): SimpleWorkerContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you call this function, you'll end up calling setupNpmPackage without checking if it's been loaded before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, setupNpmPackage does the check itself and only proceeds if not previously loaded

@ekigamba
Copy link
Contributor Author

#566 (comment)

@jingtang10

general comment: the file IO functions should be suspend functions

Correct, we will generally want to change the ResourceMapper extraction functions to suspend functions

@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch from 6c55329 to e3f77f1 Compare July 13, 2021 19:23
@ekigamba ekigamba force-pushed the ek/sdc-structure-map-extraction-implementation branch from e3f77f1 to cc9f44e Compare July 13, 2021 20:44
// Delete the folders
val packageDirectory = File(outDir)
if (packageDirectory.exists()) {
packageDirectory.delete()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you handle it and add a logging statement?

Comment on lines 138 to 141
val outDir =
Environment.getDataDirectory().getAbsolutePath() +
"/data/${context.applicationContext.packageName}/npm_packages/"
return outDir
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val outDir =
Environment.getDataDirectory().getAbsolutePath() +
"/data/${context.applicationContext.packageName}/npm_packages/"
return outDir
return
Environment.getDataDirectory().getAbsolutePath() +
"/data/${context.applicationContext.packageName}/npm_packages/"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

)
}

/** Generate the path to the local npm packages directory */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Generate the path to the local npm packages directory */
/** Generates the path to the local npm packages directory. */

same with other function documentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 97 to 98
const val commonsCompress = "org.apache.commons:commons-compress:${Versions.commonsCompress}"
const val commonsIo = "commons-io:commons-io:${Versions.commonsIo}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const val commonsCompress = "org.apache.commons:commons-compress:${Versions.commonsCompress}"
const val commonsIo = "commons-io:commons-io:${Versions.commonsIo}"
const val apacheCommonsCompress = "org.apache.commons:commons-compress:${Versions.apacheCommonsCompress}"
const val apacheCommonsIo = "commons-io:commons-io:${Versions.apacheCommonsIo}"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

context: Context?
): Bundle {
if (structureMapProvider == null || context == null) return Bundle()
val contextR4 = NpmPackageProvider.loadSimpleWorkerContext(context)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val contextR4 = NpmPackageProvider.loadSimpleWorkerContext(context)
val simpleWorkerContext = NpmPackageProvider.loadSimpleWorkerContext(context)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 143 to 146
contextR4,
questionnaireResponse,
structureMapProvider(questionnaire.targetStructureMap!!),
this
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it easier to read by annotating the params

Suggested change
contextR4,
questionnaireResponse,
structureMapProvider(questionnaire.targetStructureMap!!),
this
appInfo = simpleWorkerContext,
source = questionnaireResponse,
map = ...,
target = this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried this and I get the error Named arguments are not allowed for non-Kotlin functions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case add comments:

/* appInfo= */ simpleWorkerContext,
/* source= */ questionnaireResponse,
...

* loaded.
*/
lateinit var npmPackage: NpmPackage
lateinit var contextR4: SimpleWorkerContext
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lateinit var contextR4: SimpleWorkerContext
lateinit var simpleWorkerContext: SimpleWorkerContext

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 40 to 42
if (entry.isDirectory) {
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hold on - is this going to create an infinite loop if the entry is directory?

Suggested change
if (entry.isDirectory) {
continue
}
if (entry.isDirectory) {
entry = stream.nextTarEntry
continue
}

Copy link
Contributor Author

@ekigamba ekigamba Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, nice catch

import org.hl7.fhir.r4.context.SimpleWorkerContext
import org.hl7.fhir.utilities.npm.NpmPackage

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at this file i'm still slightly confused by the apis.

it still feels like it's doing too much. can we maybe have 2 classes

  • NpmPackageProvider
  • SimpleWorkerContextProvider

so in the former you have getNpmPackage(context: Context) and in the latter you have getSimpleWorkerContext(context: Context). The latter depends on the former. And both caches what needs to be cached within themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have split the functionalities into separate files

*
* The operations takes 20 seconds to 1 minute.
*/
fun loadSimpleWorkerContext(npmPackage: NpmPackage): SimpleWorkerContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you just get rid of this function? (i.e. inline it)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really feels like one of these two apis can go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* The whole process can take 1-3 minutes on a clean installation, otherwise, it should take 20
* seconds to 1 minute .
*/
fun loadSimpleWorkerContext(context: Context): SimpleWorkerContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these need to be suspend functions. do you plan to do this in a follow up PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

*/
object SimpleWorkerContextProvider {

lateinit var simpleWorkerContext: SimpleWorkerContext
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this private -- the cache shouldn't be exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

)

assertThat(generatedSimpleWorkerContext)
.isEqualTo(SimpleWorkerContextProvider.simpleWorkerContext)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.isEqualTo(SimpleWorkerContextProvider.simpleWorkerContext)
.isEqualTo(SimpleWorkerContextProvider.loadSimpleWorkerContext())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import org.robolectric.annotation.Config
import org.robolectric.util.ReflectionHelpers

@RunWith(RobolectricTestRunner::class)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this class testing? is it not just testing your shadow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, at this point that's correct. I'm going to remove this and in case there any extra changes then you can point them out when you review again

Copy link
Collaborator

@jingtang10 jingtang10 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! 👌

* Call [loadNpmPackage] to load it. The method handles skips the operation if it's already
* loaded.
*/
lateinit var npmPackage: NpmPackage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be private also

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ekigamba ekigamba merged commit bac35db into master Jul 21, 2021
@ekigamba ekigamba deleted the ek/sdc-structure-map-extraction-implementation branch July 21, 2021 11:14
@ekigamba
Copy link
Contributor Author

Great work!

Thanks 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

StructureMap-based extraction
2 participants