From 17029eb9e2d0b1f5934525784ec961bf525dd90b Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 10 Dec 2024 20:52:35 +0300 Subject: [PATCH 1/2] Refactor usages of Json Parser for thread safety (#2749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor usages of Json Parser * Fix failing tests ✅ * Update JsonParser usages for Dao classes Spotless clean * Fix failing unit test ✅ --- .../android/fhir/db/impl/DatabaseImplTest.kt | 11 +++--- .../db/impl/EncryptedDatabaseErrorTest.kt | 14 ++------ .../fhir/db/impl/dao/LocalChangeDaoTest.kt | 16 ++++----- .../com/google/android/fhir/FhirServices.kt | 8 +---- .../android/fhir/db/impl/DatabaseImpl.kt | 34 ++++++++----------- .../fhir/db/impl/dao/LocalChangeDao.kt | 7 ++-- .../android/fhir/db/impl/dao/ResourceDao.kt | 20 +++++++---- .../upload/request/UrlRequestGenerator.kt | 12 +++---- .../android/fhir/impl/FhirEngineImplTest.kt | 8 +++-- gradle/libs.versions.toml | 4 +-- .../fhir/knowledge/KnowledgeManager.kt | 4 +-- 11 files changed, 62 insertions(+), 76 deletions(-) diff --git a/engine/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt b/engine/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt index cd568b023a..1807b4b2d6 100644 --- a/engine/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt +++ b/engine/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt @@ -118,6 +118,7 @@ class DatabaseImplTest { @JvmField @Parameterized.Parameter(0) var encrypted: Boolean = false private val context: Context = ApplicationProvider.getApplicationContext() + private val parser = FhirContext.forR4Cached().newJsonParser() private lateinit var services: FhirServices private lateinit var database: Database @@ -202,7 +203,7 @@ class DatabaseImplTest { fun getLocalChanges_withSingleLocaleChange_shouldReturnSingleLocalChanges() = runBlocking { val patient: Patient = readFromFile(Patient::class.java, "/date_test_patient.json") database.insert(patient) - val patientString = services.parser.encodeResourceToString(patient) + val patientString = parser.encodeResourceToString(patient) val resourceLocalChanges = database.getLocalChanges(patient.resourceType, patient.logicalId) assertThat(resourceLocalChanges.size).isEqualTo(1) with(resourceLocalChanges[0]) { @@ -269,7 +270,7 @@ class DatabaseImplTest { fun clearDatabase_shouldClearAllTablesData() = runBlocking { val patient: Patient = readFromFile(Patient::class.java, "/date_test_patient.json") database.insert(patient) - val patientString = services.parser.encodeResourceToString(patient) + val patientString = parser.encodeResourceToString(patient) val resourceLocalChanges = database.getLocalChanges(patient.resourceType, patient.logicalId) assertThat(resourceLocalChanges.size).isEqualTo(1) with(resourceLocalChanges[0]) { @@ -393,7 +394,7 @@ class DatabaseImplTest { @Test fun insert_shouldAddInsertLocalChange() = runBlocking { - val testPatient2String = services.parser.encodeResourceToString(TEST_PATIENT_2) + val testPatient2String = parser.encodeResourceToString(TEST_PATIENT_2) database.insert(TEST_PATIENT_2) val resourceLocalChanges = database.getAllLocalChanges().filter { it.resourceId.equals(TEST_PATIENT_2_ID) } @@ -481,7 +482,7 @@ class DatabaseImplTest { database.insert(patient) patient = readFromFile(Patient::class.java, "/update_test_patient_1.json") database.update(patient) - services.parser.encodeResourceToString(patient) + parser.encodeResourceToString(patient) val localChangeTokenIds = database .getAllLocalChanges() @@ -4101,7 +4102,7 @@ class DatabaseImplTest { val observationLocalChange = updatedObservationLocalChanges[0] assertThat(observationLocalChange.type).isEqualTo(LocalChange.Type.INSERT) val observationLocalChangePayload = - services.parser.parseResource(observationLocalChange.payload) as Observation + parser.parseResource(observationLocalChange.payload) as Observation assertThat(observationLocalChangePayload.subject.reference) .isEqualTo("Patient/$remotelyCreatedPatientResourceId") } diff --git a/engine/src/androidTest/java/com/google/android/fhir/db/impl/EncryptedDatabaseErrorTest.kt b/engine/src/androidTest/java/com/google/android/fhir/db/impl/EncryptedDatabaseErrorTest.kt index 412da27b45..76c359596f 100644 --- a/engine/src/androidTest/java/com/google/android/fhir/db/impl/EncryptedDatabaseErrorTest.kt +++ b/engine/src/androidTest/java/com/google/android/fhir/db/impl/EncryptedDatabaseErrorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2023-2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.context.FhirVersionEnum import ca.uhn.fhir.util.FhirTerser import com.google.android.fhir.DatabaseErrorStrategy.RECREATE_AT_OPEN import com.google.android.fhir.DatabaseErrorStrategy.UNSPECIFIED @@ -49,8 +48,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EncryptedDatabaseErrorTest { private val context: Context = ApplicationProvider.getApplicationContext() - private val parser = FhirContext.forR4().newJsonParser() - private val terser = FhirTerser(FhirContext.forCached(FhirVersionEnum.R4)) + private val terser = FhirTerser(FhirContext.forR4Cached()) private val resourceIndexer = ResourceIndexer(SearchParamDefinitionsProviderImpl()) @After @@ -66,7 +64,6 @@ class EncryptedDatabaseErrorTest { // GIVEN an unencrypted database. DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -84,7 +81,6 @@ class EncryptedDatabaseErrorTest { // THEN it should throw SQLiteException DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -115,7 +111,6 @@ class EncryptedDatabaseErrorTest { // GIVEN an unencrypted database. DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -139,7 +134,6 @@ class EncryptedDatabaseErrorTest { // THEN it should throw SQLiteException DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -169,7 +163,6 @@ class EncryptedDatabaseErrorTest { // GIVEN an unencrypted database. DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -193,7 +186,6 @@ class EncryptedDatabaseErrorTest { // THEN it should recreate the database DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -226,7 +218,6 @@ class EncryptedDatabaseErrorTest { // GIVEN an encrypted database. DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, @@ -244,7 +235,6 @@ class EncryptedDatabaseErrorTest { // THEN it should recreate database. DatabaseImpl( context, - parser, terser, DatabaseConfig( inMemory = false, diff --git a/engine/src/androidTest/java/com/google/android/fhir/db/impl/dao/LocalChangeDaoTest.kt b/engine/src/androidTest/java/com/google/android/fhir/db/impl/dao/LocalChangeDaoTest.kt index b4280911c0..1b89d8e951 100644 --- a/engine/src/androidTest/java/com/google/android/fhir/db/impl/dao/LocalChangeDaoTest.kt +++ b/engine/src/androidTest/java/com/google/android/fhir/db/impl/dao/LocalChangeDaoTest.kt @@ -49,6 +49,7 @@ import org.junit.runner.RunWith class LocalChangeDaoTest { private lateinit var database: ResourceDatabase private lateinit var localChangeDao: LocalChangeDao + private val iParser = FhirContext.forR4Cached().newJsonParser() @Before fun setupDatabase() { @@ -62,7 +63,6 @@ class LocalChangeDaoTest { localChangeDao = database.localChangeDao().also { - it.iParser = FhirContext.forCached(FhirVersionEnum.R4).newJsonParser() it.fhirTerser = FhirTerser(FhirContext.forCached(FhirVersionEnum.R4)) } } @@ -97,8 +97,7 @@ class LocalChangeDaoTest { assertThat(carePlanLocalChange1.resourceUuid).isEqualTo(carePlanResourceUuid) assertThat(carePlanLocalChange1.resourceId).isEqualTo(carePlan.id) assertThat(carePlanLocalChange1.type).isEqualTo(LocalChangeEntity.Type.INSERT) - assertThat(carePlanLocalChange1.payload) - .isEqualTo(localChangeDao.iParser.encodeResourceToString(carePlan)) + assertThat(carePlanLocalChange1.payload).isEqualTo(iParser.encodeResourceToString(carePlan)) val carePlanLocalChange1Id = carePlanLocalChange1.id val localChangeResourceReferences = @@ -150,7 +149,7 @@ class LocalChangeDaoTest { resourceId = originalCarePlan.logicalId, resourceType = originalCarePlan.resourceType, resourceUuid = carePlanResourceUuid, - serializedResource = localChangeDao.iParser.encodeResourceToString(originalCarePlan), + serializedResource = iParser.encodeResourceToString(originalCarePlan), ), updatedResource = modifiedCarePlan, timeOfLocalChange = carePlanUpdateTime, @@ -163,7 +162,7 @@ class LocalChangeDaoTest { assertThat(carePlanLocalChange1.resourceId).isEqualTo(originalCarePlan.id) assertThat(carePlanLocalChange1.type).isEqualTo(LocalChangeEntity.Type.INSERT) assertThat(carePlanLocalChange1.payload) - .isEqualTo(localChangeDao.iParser.encodeResourceToString(originalCarePlan)) + .isEqualTo(iParser.encodeResourceToString(originalCarePlan)) val carePlanLocalChange2 = carePlanLocalChanges[1] assertThat(carePlanLocalChange2.resourceUuid).isEqualTo(carePlanResourceUuid) @@ -224,8 +223,7 @@ class LocalChangeDaoTest { assertThat(carePlanLocalChange1.resourceUuid).isEqualTo(carePlanResourceUuid) assertThat(carePlanLocalChange1.resourceId).isEqualTo(carePlan.id) assertThat(carePlanLocalChange1.type).isEqualTo(LocalChangeEntity.Type.INSERT) - assertThat(carePlanLocalChange1.payload) - .isEqualTo(localChangeDao.iParser.encodeResourceToString(carePlan)) + assertThat(carePlanLocalChange1.payload).isEqualTo(iParser.encodeResourceToString(carePlan)) val carePlanLocalChange2 = carePlanLocalChanges[1] assertThat(carePlanLocalChange2.resourceUuid).isEqualTo(carePlanResourceUuid) @@ -285,7 +283,7 @@ class LocalChangeDaoTest { resourceId = originalCarePlan.logicalId, resourceType = originalCarePlan.resourceType, resourceUuid = carePlanResourceUuid, - serializedResource = localChangeDao.iParser.encodeResourceToString(originalCarePlan), + serializedResource = iParser.encodeResourceToString(originalCarePlan), ), updatedResource = modifiedCarePlan, timeOfLocalChange = carePlanUpdateTime, @@ -318,7 +316,7 @@ class LocalChangeDaoTest { activityFirstRep.detail.performer.add(Reference("Patient/$updatedPatientId")) } assertThat(carePlanLocalChange1.payload) - .isEqualTo(localChangeDao.iParser.encodeResourceToString(updatedReferencesCarePlan)) + .isEqualTo(iParser.encodeResourceToString(updatedReferencesCarePlan)) val carePlanLocalChange1Id = carePlanLocalChange1.id // assert that LocalChangeReferences are updated as well val localChange1ResourceReferences = diff --git a/engine/src/main/java/com/google/android/fhir/FhirServices.kt b/engine/src/main/java/com/google/android/fhir/FhirServices.kt index efa76f0536..bf95e983be 100644 --- a/engine/src/main/java/com/google/android/fhir/FhirServices.kt +++ b/engine/src/main/java/com/google/android/fhir/FhirServices.kt @@ -18,8 +18,6 @@ package com.google.android.fhir import android.content.Context import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.context.FhirVersionEnum -import ca.uhn.fhir.parser.IParser import ca.uhn.fhir.util.FhirTerser import com.google.android.fhir.db.Database import com.google.android.fhir.db.impl.DatabaseConfig @@ -38,7 +36,6 @@ import timber.log.Timber internal data class FhirServices( val fhirEngine: FhirEngine, - val parser: IParser, val database: Database, val remoteDataSource: DataSource? = null, val fhirDataStore: FhirDataStore, @@ -74,15 +71,13 @@ internal data class FhirServices( } fun build(): FhirServices { - val parser = FhirContext.forCached(FhirVersionEnum.R4).newJsonParser() - val terser = FhirTerser(FhirContext.forCached(FhirVersionEnum.R4)) + val terser = FhirTerser(FhirContext.forR4Cached()) val searchParamMap = searchParameters?.asMapOfResourceTypeToSearchParamDefinitions() ?: emptyMap() val provider = SearchParamDefinitionsProviderImpl(searchParamMap) val db = DatabaseImpl( context = context, - iParser = parser, fhirTerser = terser, DatabaseConfig(inMemory, enableEncryption, databaseErrorStrategy), resourceIndexer = ResourceIndexer(provider), @@ -100,7 +95,6 @@ internal data class FhirServices( } return FhirServices( fhirEngine = engine, - parser = parser, database = db, remoteDataSource = remoteDataSource, fhirDataStore = FhirDataStore(context), diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt b/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt index 03796f6466..1e8333ea5b 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt @@ -22,7 +22,6 @@ import androidx.room.Room import androidx.room.withTransaction import androidx.sqlite.db.SimpleSQLiteQuery import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.parser.IParser import ca.uhn.fhir.util.FhirTerser import com.google.android.fhir.DatabaseErrorStrategy import com.google.android.fhir.LocalChange @@ -56,7 +55,6 @@ import org.hl7.fhir.r4.model.ResourceType @Suppress("UNCHECKED_CAST") internal class DatabaseImpl( private val context: Context, - private val iParser: IParser, private val fhirTerser: FhirTerser, databaseConfig: DatabaseConfig, private val resourceIndexer: ResourceIndexer, @@ -122,18 +120,9 @@ internal class DatabaseImpl( .build() } - private val resourceDao by lazy { - db.resourceDao().also { - it.iParser = iParser - it.resourceIndexer = resourceIndexer - } - } + private val resourceDao by lazy { db.resourceDao().also { it.resourceIndexer = resourceIndexer } } - private val localChangeDao = - db.localChangeDao().also { - it.iParser = iParser - it.fhirTerser = fhirTerser - } + private val localChangeDao = db.localChangeDao().also { it.fhirTerser = fhirTerser } override suspend fun insert(vararg resource: R): List { val logicalIds = mutableListOf() @@ -191,10 +180,13 @@ internal class DatabaseImpl( db.withTransaction { resourceDao.getResourceEntity(oldResourceId, resourceType)?.let { oldResourceEntity -> val updatedResource = - (iParser.parseResource(oldResourceEntity.serializedResource) as Resource).apply { - idElement = IdType(newResourceId) - updateMeta(versionId, lastUpdatedRemote) - } + (FhirContext.forR4Cached() + .newJsonParser() + .parseResource(oldResourceEntity.serializedResource) as Resource) + .apply { + idElement = IdType(newResourceId) + updateMeta(versionId, lastUpdatedRemote) + } updateResourceAndReferences(oldResourceId, updatedResource) } } @@ -202,7 +194,7 @@ internal class DatabaseImpl( override suspend fun select(type: ResourceType, id: String): Resource { return resourceDao.getResource(resourceId = id, resourceType = type)?.let { - iParser.parseResource(it) as Resource + FhirContext.forR4Cached().newJsonParser().parseResource(it) as Resource } ?: throw ResourceNotFoundException(type.name, id) } @@ -317,7 +309,10 @@ internal class DatabaseImpl( ) { db.withTransaction { val currentResourceEntity = selectEntity(updatedResource.resourceType, currentResourceId) - val oldResource = iParser.parseResource(currentResourceEntity.serializedResource) as Resource + val oldResource = + FhirContext.forR4Cached() + .newJsonParser() + .parseResource(currentResourceEntity.serializedResource) as Resource val resourceUuid = currentResourceEntity.resourceUuid updateResourceEntity(resourceUuid, updatedResource) @@ -375,6 +370,7 @@ internal class DatabaseImpl( val updatedReferenceValue = "${updatedResource.resourceType.name}/${updatedResource.logicalId}" referringResourcesUuids.forEach { resourceUuid -> resourceDao.getResourceEntity(resourceUuid)?.let { + val iParser = FhirContext.forR4Cached().newJsonParser() val referringResource = iParser.parseResource(it.serializedResource) as Resource val updatedReferringResource = addUpdatedReferenceToResource( diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt b/engine/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt index 68069b8ab7..158067cb85 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt @@ -21,6 +21,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction +import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.parser.IParser import ca.uhn.fhir.util.FhirTerser import ca.uhn.fhir.util.ResourceReferenceInfo @@ -50,8 +51,6 @@ import timber.log.Timber */ @Dao internal abstract class LocalChangeDao { - - lateinit var iParser: IParser lateinit var fhirTerser: FhirTerser @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -70,7 +69,7 @@ internal abstract class LocalChangeDao { open suspend fun addInsert(resource: Resource, resourceUuid: UUID, timeOfLocalChange: Instant) { val resourceId = resource.logicalId val resourceType = resource.resourceType - val resourceString = iParser.encodeResourceToString(resource) + val resourceString = FhirContext.forR4Cached().newJsonParser().encodeResourceToString(resource) val localChangeEntity = LocalChangeEntity( @@ -128,6 +127,7 @@ internal abstract class LocalChangeDao { "Unexpected DELETE when updating $resourceType/$resourceId. UPDATE failed.", ) } + val iParser = FhirContext.forR4Cached().newJsonParser() val oldResource = iParser.parseResource(oldEntity.serializedResource) as Resource val jsonDiff = diff(iParser, oldResource, updatedResource) if (jsonDiff.length() == 0) { @@ -475,6 +475,7 @@ internal abstract class LocalChangeDao { oldReference: String, updatedReference: String, ): LocalChangeEntity { + val iParser = FhirContext.forR4Cached().newJsonParser() return when (localChange.type) { LocalChangeEntity.Type.INSERT -> { val insertResourcePayload = iParser.parseResource(localChange.payload) as Resource diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt b/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt index 0f219d84dd..65015fc9c8 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt @@ -24,7 +24,7 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.RawQuery import androidx.sqlite.db.SupportSQLiteQuery -import ca.uhn.fhir.parser.IParser +import ca.uhn.fhir.context.FhirContext import com.google.android.fhir.db.ResourceNotFoundException import com.google.android.fhir.db.impl.entities.DateIndexEntity import com.google.android.fhir.db.impl.entities.DateTimeIndexEntity @@ -55,7 +55,6 @@ import org.hl7.fhir.r4.model.ResourceType internal abstract class ResourceDao { // this is ugly but there is no way to inject these right now in Room as it is the one creating // the dao - lateinit var iParser: IParser lateinit var resourceIndexer: ResourceIndexer /** @@ -69,7 +68,8 @@ internal abstract class ResourceDao { getResourceEntity(resource.logicalId, resource.resourceType)?.let { val entity = it.copy( - serializedResource = iParser.encodeResourceToString(resource), + serializedResource = + FhirContext.forR4Cached().newJsonParser().encodeResourceToString(resource), lastUpdatedLocal = timeOfLocalChange, lastUpdatedRemote = resource.meta.lastUpdated?.toInstant() ?: it.lastUpdatedRemote, ) @@ -86,7 +86,8 @@ internal abstract class ResourceDao { val entity = it.copy( resourceId = updatedResource.logicalId, - serializedResource = iParser.encodeResourceToString(updatedResource), + serializedResource = + FhirContext.forR4Cached().newJsonParser().encodeResourceToString(updatedResource), lastUpdatedRemote = updatedResource.lastUpdated ?: it.lastUpdatedRemote, versionId = updatedResource.versionId ?: it.versionId, ) @@ -107,7 +108,8 @@ internal abstract class ResourceDao { getResourceEntity(resource.logicalId, resource.resourceType)?.let { val entity = it.copy( - serializedResource = iParser.encodeResourceToString(resource), + serializedResource = + FhirContext.forR4Cached().newJsonParser().encodeResourceToString(resource), lastUpdatedRemote = resource.meta.lastUpdated?.toInstant(), versionId = resource.versionId, ) @@ -267,7 +269,8 @@ internal abstract class ResourceDao { resourceType = resource.resourceType, resourceUuid = resourceUuid, resourceId = resource.logicalId, - serializedResource = iParser.encodeResourceToString(resource), + serializedResource = + FhirContext.forR4Cached().newJsonParser().encodeResourceToString(resource), versionId = resource.versionId, lastUpdatedRemote = resource.lastUpdated, lastUpdatedLocal = lastUpdatedLocal, @@ -297,7 +300,10 @@ internal abstract class ResourceDao { lastUpdatedRemote: Instant?, ) { getResourceEntity(resourceId, resourceType)?.let { oldResourceEntity -> - val resource = iParser.parseResource(oldResourceEntity.serializedResource) as Resource + val resource = + FhirContext.forR4Cached() + .newJsonParser() + .parseResource(oldResourceEntity.serializedResource) as Resource resource.updateMeta(versionId, lastUpdatedRemote) updateResourceWithUuid(oldResourceEntity.resourceUuid, resource) } diff --git a/engine/src/main/java/com/google/android/fhir/sync/upload/request/UrlRequestGenerator.kt b/engine/src/main/java/com/google/android/fhir/sync/upload/request/UrlRequestGenerator.kt index 8e576760c3..d868cf2467 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/upload/request/UrlRequestGenerator.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/upload/request/UrlRequestGenerator.kt @@ -17,7 +17,6 @@ package com.google.android.fhir.sync.upload.request import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.context.FhirVersionEnum import com.google.android.fhir.ContentTypes import com.google.android.fhir.sync.upload.patch.Patch import com.google.android.fhir.sync.upload.patch.PatchMapping @@ -58,8 +57,6 @@ internal class UrlRequestGenerator( companion object Factory { - private val parser = FhirContext.forCached(FhirVersionEnum.R4).newJsonParser() - private val createMapping = mapOf( HttpVerb.POST to this::postForCreateResource, @@ -107,21 +104,24 @@ internal class UrlRequestGenerator( UrlUploadRequest( httpVerb = HttpVerb.DELETE, url = "${patch.resourceType}/${patch.resourceId}", - resource = parser.parseResource(patch.payload) as Resource, + resource = + FhirContext.forR4Cached().newJsonParser().parseResource(patch.payload) as Resource, ) private fun postForCreateResource(patch: Patch) = UrlUploadRequest( httpVerb = HttpVerb.POST, url = patch.resourceType, - resource = parser.parseResource(patch.payload) as Resource, + resource = + FhirContext.forR4Cached().newJsonParser().parseResource(patch.payload) as Resource, ) private fun putForCreateResource(patch: Patch) = UrlUploadRequest( httpVerb = HttpVerb.PUT, url = "${patch.resourceType}/${patch.resourceId}", - resource = parser.parseResource(patch.payload) as Resource, + resource = + FhirContext.forR4Cached().newJsonParser().parseResource(patch.payload) as Resource, ) private fun patchForUpdateResource(patch: Patch) = diff --git a/engine/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt b/engine/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt index 31303f3e88..64bfc6ace4 100644 --- a/engine/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt +++ b/engine/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt @@ -17,6 +17,7 @@ package com.google.android.fhir.impl import androidx.test.core.app.ApplicationProvider +import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.rest.gclient.TokenClientParam import ca.uhn.fhir.rest.param.ParamPrefixEnum import com.google.android.fhir.FhirServices.Companion.builder @@ -74,6 +75,7 @@ import org.robolectric.RobolectricTestRunner class FhirEngineImplTest { private val services = builder(ApplicationProvider.getApplicationContext()).inMemory().build() private val fhirEngine = services.fhirEngine + private val parser = FhirContext.forR4Cached().newJsonParser() @Before fun setUp(): Unit = runBlocking { fhirEngine.create(TEST_PATIENT_1) } @@ -388,7 +390,7 @@ class FhirEngineImplTest { assertThat(resourceType).isEqualTo(ResourceType.Patient.toString()) assertThat(resourceId).isEqualTo(TEST_PATIENT_1.id) assertThat(type).isEqualTo(Type.INSERT) - assertThat(payload).isEqualTo(services.parser.encodeResourceToString(TEST_PATIENT_1)) + assertThat(payload).isEqualTo(parser.encodeResourceToString(TEST_PATIENT_1)) } assertThat(emittedProgress).hasSize(2) @@ -446,7 +448,7 @@ class FhirEngineImplTest { fun `getLocalChanges() should return single local change`() = runBlocking { val patient: Patient = readFromFile(Patient::class.java, "/date_test_patient.json") fhirEngine.create(patient) - val patientString = services.parser.encodeResourceToString(patient) + val patientString = parser.encodeResourceToString(patient) val resourceLocalChanges = fhirEngine.getLocalChanges(patient.resourceType, patient.logicalId) with(resourceLocalChanges) { assertThat(size).isEqualTo(1) @@ -497,7 +499,7 @@ class FhirEngineImplTest { fun `clearDatabase() should clear all tables data`() = runBlocking { val patient: Patient = readFromFile(Patient::class.java, "/date_test_patient.json") fhirEngine.create(patient) - val patientString = services.parser.encodeResourceToString(patient) + val patientString = parser.encodeResourceToString(patient) val resourceLocalChanges = fhirEngine.getLocalChanges(patient.resourceType, patient.logicalId) with(resourceLocalChanges) { assertThat(size).isEqualTo(1) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f273e2bd1..79fc02d4d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ android-fhir-common = "0.1.0-alpha05" android-fhir-engine = "0.1.0-beta05" android-fhir-knowledge = "0.1.0-beta01" -androidx-acivity = "1.7.2" +androidx-activity = "1.7.2" androidx-appcompat = "1.6.1" androidx-arch-core = "2.2.0" androidx-benchmark = "1.1.1" @@ -37,7 +37,7 @@ material = "1.9.0" android-fhir-common = { module = "com.google.android.fhir:common", version.ref = "android-fhir-common" } android-fhir-engine = { module = "com.google.android.fhir:engine", version.ref = "android-fhir-engine" } android-fhir-knowledge = { module = "com.google.android.fhir:knowledge", version.ref = "android-fhir-knowledge" } -androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-acivity" } +androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-arch-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidx-arch-core" } androidx-benchmark-junit4 = { module = "androidx.benchmark:benchmark-junit4", version.ref = "androidx-benchmark" } diff --git a/knowledge/src/main/java/com/google/android/fhir/knowledge/KnowledgeManager.kt b/knowledge/src/main/java/com/google/android/fhir/knowledge/KnowledgeManager.kt index 8066cda7d9..953b936e26 100644 --- a/knowledge/src/main/java/com/google/android/fhir/knowledge/KnowledgeManager.kt +++ b/knowledge/src/main/java/com/google/android/fhir/knowledge/KnowledgeManager.kt @@ -19,7 +19,6 @@ package com.google.android.fhir.knowledge import android.content.Context import androidx.room.Room import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.parser.IParser import com.google.android.fhir.knowledge.db.KnowledgeDatabase import com.google.android.fhir.knowledge.db.entities.ImplementationGuideEntity import com.google.android.fhir.knowledge.db.entities.ResourceMetadataEntity @@ -63,7 +62,6 @@ internal constructor( knowledgeDatabase: KnowledgeDatabase, private val npmFileManager: NpmFileManager, private val npmPackageDownloader: NpmPackageDownloader, - private val jsonParser: IParser = FhirContext.forR4().newJsonParser(), ) { private val knowledgeDao = knowledgeDatabase.knowledgeDao() @@ -296,7 +294,7 @@ internal constructor( private suspend fun readResourceOrNull(file: File): IBaseResource? = withContext(Dispatchers.IO) { try { - FileInputStream(file).use(jsonParser::parseResource) + FileInputStream(file).use(FhirContext.forR4Cached().newJsonParser()::parseResource) } catch (e: Exception) { Timber.e(e, "Unable to load resource from $file") null From dc44a44a6ab380b3ed73e9234376ae951f281b0f Mon Sep 17 00:00:00 2001 From: santosh-pingle <86107848+santosh-pingle@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:07:47 +0530 Subject: [PATCH 2/2] perform parsing on datetype value. (#2723) Co-authored-by: Santosh Pingle --- .../com/google/android/fhir/demo/PatientListViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/src/main/java/com/google/android/fhir/demo/PatientListViewModel.kt b/demo/src/main/java/com/google/android/fhir/demo/PatientListViewModel.kt index 55c85a4d33..fbf7b43fae 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/PatientListViewModel.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/PatientListViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2023-2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import com.google.android.fhir.search.StringFilterModifier import com.google.android.fhir.search.count import com.google.android.fhir.search.search import java.time.LocalDate -import java.time.format.DateTimeFormatter +import java.time.ZoneId import kotlinx.coroutines.launch import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.RiskAssessment @@ -183,7 +183,7 @@ internal fun Patient.toPatientItem(position: Int): PatientListViewModel.PatientI val gender = if (hasGenderElement()) genderElement.valueAsString else "" val dob = if (hasBirthDateElement()) { - LocalDate.parse(birthDateElement.valueAsString, DateTimeFormatter.ISO_DATE) + birthDateElement.value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() } else { null }