diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index ecc019b0a8..ad88f4bc6e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -1130,7 +1130,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat // Results in a new questionnaire state being generated synchronously, i.e., the current // thread will be suspended until the new state is generated. modificationCount.update { it + 1 } - forceValidation = false + //forceValidation = false } if ( diff --git a/engine/src/main/java/com/google/android/fhir/FhirEngine.kt b/engine/src/main/java/com/google/android/fhir/FhirEngine.kt index 4b7ff2cb45..e336dad6a5 100644 --- a/engine/src/main/java/com/google/android/fhir/FhirEngine.kt +++ b/engine/src/main/java/com/google/android/fhir/FhirEngine.kt @@ -178,6 +178,8 @@ interface FhirEngine { */ suspend fun getLocalChanges(type: ResourceType, id: String): List + suspend fun getUnsyncedLocalChanges(): List + /** * Purges a resource from the database without deleting data from the server. * 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 c6b3d0ad71..dd96469ef5 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 @@ -43,6 +43,9 @@ import com.google.android.fhir.toLocalChange import com.google.android.fhir.updateMeta import java.time.Instant import java.util.UUID +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.Dispatchers import org.hl7.fhir.r4.model.IdType import org.hl7.fhir.r4.model.Resource @@ -315,8 +318,8 @@ internal class DatabaseImpl( .newJsonParser() .parseResource(currentResourceEntity.serializedResource) as Resource val resourceUuid = currentResourceEntity.resourceUuid - updateResourceEntity(resourceUuid, updatedResource) - + updateResourceEntity(resourceUuid, updatedResource) + if (currentResourceId == updatedResource.logicalId) { return@withTransaction } @@ -445,6 +448,11 @@ internal class DatabaseImpl( } } + /** Implementation of a parallelized map */ + suspend fun Iterable.pmap(f: suspend (A) -> B): List = coroutineScope { + map { async { f(it) } }.awaitAll() + } + companion object { /** * The name for unencrypted database. diff --git a/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt b/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt index 595f433d9d..cff20843d3 100644 --- a/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt +++ b/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt @@ -74,6 +74,8 @@ internal class FhirEngineImpl(private val database: Database, private val contex override suspend fun getLocalChanges(type: ResourceType, id: String) = withContext(Dispatchers.IO) { database.getLocalChanges(type, id) } + override suspend fun getUnsyncedLocalChanges(): List = database.getAllLocalChanges() + override suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean) = withContext(Dispatchers.IO) { database.purge(type, setOf(id), forcePurge) } diff --git a/engine/src/main/java/com/google/android/fhir/sync/FhirSyncWorker.kt b/engine/src/main/java/com/google/android/fhir/sync/FhirSyncWorker.kt index 45320ce810..c6051d2c14 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/FhirSyncWorker.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/FhirSyncWorker.kt @@ -21,6 +21,7 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.WorkerParameters import androidx.work.workDataOf +import ca.uhn.fhir.context.FhirContext import com.google.android.fhir.FhirEngine import com.google.android.fhir.FhirEngineProvider import com.google.android.fhir.OffsetDateTimeTypeAdapter @@ -32,11 +33,15 @@ import com.google.android.fhir.sync.upload.request.UploadRequestGeneratorFactory import com.google.gson.ExclusionStrategy import com.google.gson.FieldAttributes import com.google.gson.GsonBuilder +import java.nio.charset.StandardCharsets import java.time.OffsetDateTime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import org.apache.commons.io.IOUtils +import org.hl7.fhir.r4.model.OperationOutcome +import retrofit2.HttpException import timber.log.Timber /** @@ -131,6 +136,8 @@ abstract class FhirSyncWorker(appContext: Context, workerParams: WorkerParameter } val result = synchronizer.synchronize() + if (result is SyncJobStatus.Failed) onFailedSyncJobResult(result) + val output = buildWorkData(result) // await/join is needed to collect states completely @@ -151,6 +158,34 @@ abstract class FhirSyncWorker(appContext: Context, workerParams: WorkerParameter } } + open fun onFailedSyncJobResult(failedSyncJobStatus: SyncJobStatus.Failed) { + try { + val jsonParser = FhirContext.forR4().newJsonParser() + val exceptions = (failedSyncJobStatus).exceptions + + exceptions.forEach { resourceSyncException -> + val operationOutcome = + jsonParser.parseResource( + IOUtils.toString( + (resourceSyncException.exception as HttpException) + .response() + ?.errorBody() + ?.byteStream(), + StandardCharsets.UTF_8, + ), + ) as OperationOutcome + + operationOutcome.issue.forEach { operationOutcome -> + Timber.e( + "SERVER ${operationOutcome.severity} - HTTP ${resourceSyncException.exception.code()} | Code - ${operationOutcome.code} | Diagnostics - ${operationOutcome.diagnostics}", + ) + } + } + } catch (e: Exception) { + Timber.e(e) + } + } + private fun buildWorkData(state: SyncJobStatus): Data { return workDataOf( // send serialized state and type so that consumer can convert it back diff --git a/engine/src/main/java/com/google/android/fhir/sync/upload/request/UploadRequestGenerator.kt b/engine/src/main/java/com/google/android/fhir/sync/upload/request/UploadRequestGenerator.kt index 86af7a4b64..f54e1aabf3 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/upload/request/UploadRequestGenerator.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/upload/request/UploadRequestGenerator.kt @@ -62,6 +62,7 @@ internal object UploadRequestGeneratorFactory { mode.httpVerbToUseForCreate, mode.httpVerbToUseForUpdate, mode.bundleSize, + useETagForUpload = false ) } } diff --git a/engine/src/main/java/com/google/android/fhir/testing/Utilities.kt b/engine/src/main/java/com/google/android/fhir/testing/Utilities.kt index 1b85a71382..d07298a7f3 100644 --- a/engine/src/main/java/com/google/android/fhir/testing/Utilities.kt +++ b/engine/src/main/java/com/google/android/fhir/testing/Utilities.kt @@ -203,6 +203,10 @@ internal object TestFhirEngineImpl : FhirEngine { ) } + override suspend fun getUnsyncedLocalChanges(): List { + TODO("Not yet implemented") + } + override suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean) {} override suspend fun purge(type: ResourceType, ids: Set, forcePurge: Boolean) {}