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

Prototype to create a separate sync module #429

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
val kotlin_version by extra("1.4.31")
repositories {
google()
mavenCentral()
Expand All @@ -10,6 +11,7 @@ buildscript {
classpath(Plugins.kotlinGradlePlugin)
classpath(Plugins.navSafeArgsGradlePlugin)
classpath(Plugins.spotlessGradlePlugin)
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}

Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ object Dependencies {
const val navigation = "2.3.4"
const val recyclerView = "1.1.0"
const val room = "2.2.5"
const val workRuntimeKtx = "2.3.4"
const val workRuntimeKtx = "2.5.0"
}

object Cql {
Expand Down
1 change: 0 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ dependencies {

implementation(Dependencies.Room.runtime)
implementation(Dependencies.Room.ktx)
implementation(Dependencies.Androidx.workRuntimeKtx)
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.caffeine)
implementation(Dependencies.guava)
Expand Down
18 changes: 4 additions & 14 deletions core/src/main/java/com/google/android/fhir/FhirEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
package com.google.android.fhir

import com.google.android.fhir.search.Search
import com.google.android.fhir.sync.PeriodicSyncConfiguration
import com.google.android.fhir.sync.Result
import com.google.android.fhir.sync.SyncConfiguration
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

/** The FHIR Engine interface that handles the local storage of FHIR resources. */
interface FhirEngine {
Expand Down Expand Up @@ -55,17 +53,9 @@ interface FhirEngine {
*/
suspend fun <R : Resource> remove(clazz: Class<R>, id: String)

/**
* One time sync.
*
* @param syncConfiguration
* - configuration of data that needs to be synchronised
*/
suspend fun sync(syncConfiguration: SyncConfiguration): Result

suspend fun periodicSync(): Result
suspend fun <R : Resource> search(search: Search): List<R>

fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration)
suspend fun syncDownload(download: suspend (suspend (ResourceType) -> String?) -> List<Resource>)

suspend fun <R : Resource> search(search: Search): List<R>
suspend fun syncUpload(upload: (suspend (List<Resource>) -> Unit)?)
}
17 changes: 2 additions & 15 deletions core/src/main/java/com/google/android/fhir/FhirEngineBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,10 @@
package com.google.android.fhir

import android.content.Context
import com.google.android.fhir.sync.FhirDataSource
import com.google.android.fhir.sync.PeriodicSyncConfiguration

/** The builder for [FhirEngine] instance */
class FhirEngineBuilder constructor(dataSource: FhirDataSource, context: Context) {
private val services = FhirServices.builder(dataSource, context)

/** Sets the database file name for the FhirEngine to use. */
fun databaseName(name: String) = apply { services.databaseName(name) }

/** Instructs the FhirEngine to use an in memory database which can be useful for tests. */
internal fun inMemory() = apply { services.inMemory() }

/** Configures the FhirEngine periodic sync. */
fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply {
services.periodicSyncConfiguration(config)
}
class FhirEngineBuilder constructor(context: Context) {
private val services = FhirServices.builder(context)

/** Builds a new instance of the [FhirEngine]. */
fun build() = services.build().fhirEngine
Expand Down
13 changes: 2 additions & 11 deletions core/src/main/java/com/google/android/fhir/FhirServices.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,32 @@ import ca.uhn.fhir.parser.IParser
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.impl.DatabaseImpl
import com.google.android.fhir.impl.FhirEngineImpl
import com.google.android.fhir.sync.FhirDataSource
import com.google.android.fhir.sync.PeriodicSyncConfiguration

internal data class FhirServices(
val fhirEngine: FhirEngine,
val parser: IParser,
val database: Database
) {
class Builder(private val dataSource: FhirDataSource, private val context: Context) {
class Builder( private val context: Context) {
private var databaseName: String? = "fhirEngine"
private var periodicSyncConfiguration: PeriodicSyncConfiguration? = null

fun inMemory() = apply { databaseName = null }

fun databaseName(name: String) = apply { databaseName = name }

fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply {
periodicSyncConfiguration = config
}

fun build(): FhirServices {
val parser = FhirContext.forR4().newJsonParser()
val db = DatabaseImpl(context = context, iParser = parser, databaseName = databaseName)
val engine =
FhirEngineImpl(
database = db,
periodicSyncConfiguration = periodicSyncConfiguration,
dataSource = dataSource,
context = context
)
return FhirServices(fhirEngine = engine, parser = parser, database = db)
}
}

companion object {
fun builder(dataSource: FhirDataSource, context: Context) = Builder(dataSource, context)
fun builder(context: Context) = Builder(context)
}
}
4 changes: 2 additions & 2 deletions core/src/main/java/com/google/android/fhir/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

/** The interface for the FHIR resource database. */
interface Database {
internal interface Database {
/**
* Inserts a list of local `resources` into the FHIR resource database. If any of the resources
* already exists, it will be overwritten.
Expand Down Expand Up @@ -71,7 +71,7 @@ interface Database {
* @param syncedResourceEntity The synced resource
*/
suspend fun insertSyncedResources(
syncedResourceEntity: SyncedResourceEntity,
syncedResourceEntity: List<SyncedResourceEntity>,
jingtang10 marked this conversation as resolved.
Show resolved Hide resolved
resources: List<Resource>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ internal class DatabaseImpl(context: Context, private val iParser: IParser, data

@Transaction
override suspend fun insertSyncedResources(
syncedResourceEntity: SyncedResourceEntity,
syncedResourceEntity: List<SyncedResourceEntity>,
resources: List<Resource>
) {
syncedResourceDao.insert(syncedResourceEntity)
syncedResourceDao.insertAll(syncedResourceEntity)
insertRemote(*resources.toTypedArray())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,21 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import ca.uhn.fhir.rest.annotation.Transaction
import com.google.android.fhir.db.impl.entities.SyncedResourceEntity
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

@Dao
interface SyncedResourceDao {

@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(entity: SyncedResourceEntity)

@Transaction
suspend fun insertAll(resources: List<SyncedResourceEntity>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use kotlin var args for API consistency?

resources.forEach { resource -> insert(resource) }
}

/**
* We will always have 1 entry for each [ResourceType] as it's the primary key, so we can limit
* the result to 1. If there is no entry for that [ResourceType] then `null` will be returned.
Expand Down
72 changes: 14 additions & 58 deletions core/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,21 @@
package com.google.android.fhir.impl

import android.content.Context
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.ResourceNotFoundException
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.ResourceNotFoundInDbException
import com.google.android.fhir.db.impl.entities.SyncedResourceEntity
import com.google.android.fhir.resource.getResourceType
import com.google.android.fhir.search.Search
import com.google.android.fhir.search.execute
import com.google.android.fhir.sync.FhirDataSource
import com.google.android.fhir.sync.FhirSynchronizer
import com.google.android.fhir.sync.PeriodicSyncConfiguration
import com.google.android.fhir.sync.Result
import com.google.android.fhir.sync.SyncConfiguration
import com.google.android.fhir.sync.SyncWorkType
import com.google.android.fhir.toTimeZoneString
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

/** Implementation of [FhirEngine]. */
class FhirEngineImpl
constructor(
private val database: Database,
private var periodicSyncConfiguration: PeriodicSyncConfiguration?,
private val dataSource: FhirDataSource,
private val context: Context
) : FhirEngine {

init {
periodicSyncConfiguration?.let { config -> triggerInitialDownload(config) }
}

internal class FhirEngineImpl
constructor(private val database: Database, private val context: Context) : FhirEngine {
override suspend fun <R : Resource> save(vararg resource: R) {
database.insert(*resource)
}
Expand All @@ -69,49 +53,21 @@ constructor(
database.delete(clazz, id)
}

override suspend fun sync(syncConfiguration: SyncConfiguration): Result {
return FhirSynchronizer(syncConfiguration, dataSource, database).sync()
}

override suspend fun periodicSync(): Result {
val syncConfig =
periodicSyncConfiguration
?: throw java.lang.UnsupportedOperationException("Periodic sync configuration was not set")
val syncResult = FhirSynchronizer(syncConfig.syncConfiguration, dataSource, database).sync()
setupNextDownload(syncConfig)
return syncResult
}

override fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration) {
periodicSyncConfiguration = syncConfig
setupNextDownload(syncConfig)
}

override suspend fun <R : Resource> search(search: Search): List<R> {
return search.execute(database)
}

private fun setupNextDownload(syncConfig: PeriodicSyncConfiguration) {
setupDownload(syncConfig = syncConfig, withInitialDelay = true)
override suspend fun syncDownload(download: suspend (suspend (ResourceType) -> String?) -> List<Resource>) {
val stuff = download(database::lastUpdate)
jingtang10 marked this conversation as resolved.
Show resolved Hide resolved
val timeStamps =
stuff.groupBy { it.resourceType }.entries.map {
SyncedResourceEntity(it.key, it.value.last().meta.lastUpdated.toTimeZoneString())
}
database.insertSyncedResources(timeStamps, stuff)
}

private fun triggerInitialDownload(syncConfig: PeriodicSyncConfiguration) {
setupDownload(syncConfig = syncConfig, withInitialDelay = false)
override suspend fun syncUpload(upload: (suspend (List<Resource>) -> Unit)?) {
TODO("Not yet implemented")
}

private fun setupDownload(syncConfig: PeriodicSyncConfiguration, withInitialDelay: Boolean) {
val workerClass = syncConfig.periodicSyncWorker
val downloadRequest =
if (withInitialDelay) {
OneTimeWorkRequest.Builder(workerClass)
.setConstraints(syncConfig.syncConstraints)
.setInitialDelay(syncConfig.repeat.interval, syncConfig.repeat.timeUnit)
.build()
} else {
OneTimeWorkRequest.Builder(workerClass).setConstraints(syncConfig.syncConstraints).build()
}

WorkManager.getInstance(context)
.enqueueUniqueWork(SyncWorkType.DOWNLOAD.workerName, ExistingWorkPolicy.KEEP, downloadRequest)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.google.android.fhir.db.Database
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

suspend fun <R : Resource> Search.execute(database: Database): List<R> {
internal suspend fun <R : Resource> Search.execute(database: Database): List<R> {
return database.search(getQuery())
}

Expand Down

This file was deleted.

Loading