Skip to content

Commit de1432a

Browse files
committed
feat: add support of any ValueLoader for LazyCache
Now any type of ValueLoader can be used as a loader for LazyCache - default loaders, custom loaders, any value loaders including stateful ones.
1 parent 39918da commit de1432a

11 files changed

Lines changed: 155 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The full documentation is available [here](https://docs.uandcode.com/container).
3434
Add the following line to your `build.gradle` file:
3535

3636
```
37-
implementation "com.elveum:container:2.1.0-beta04"
37+
implementation "com.elveum:container:2.1.0-beta05"
3838
```
3939

4040
## Core Concepts

container/src/main/java/com/elveum/container/cache/LazyCache.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.elveum.container.factory.DEFAULT_CACHE_TIMEOUT_MILLIS
88
import com.elveum.container.factory.DEFAULT_RELOAD_DEPENDENCIES_PERIOD_MILLIS
99
import com.elveum.container.subject.ContainerConfiguration
1010
import com.elveum.container.subject.LazyFlowSubject
11+
import com.elveum.container.subject.ValueLoader
1112
import com.elveum.container.subject.transformation.ContainerTransformation
1213
import com.elveum.container.subject.transformation.EmptyContainerTransformation
1314
import kotlinx.coroutines.flow.Flow
@@ -121,8 +122,38 @@ public interface LazyCache<Arg, T> {
121122
reloadDependenciesPeriodMillis = reloadDependenciesPeriodMillis,
122123
coroutineScopeFactory = coroutineScopeFactory,
123124
transformation = transformation,
124-
valueLoader = valueLoader,
125+
valueLoaderFactory = ValueLoaderFactory { arg ->
126+
ValueLoader { valueLoader.invoke(this, arg) }
127+
},
125128
)
126129
}
130+
131+
/**
132+
* Create a new instance of [LazyCache] using [ValueLoaderFactory].
133+
*
134+
* @param Arg the type of the argument used to identify cached entries.
135+
* @param T the type of values held in the cache.
136+
* @param cacheTimeoutMillis how much time cached values remain in cache if there is no collectors
137+
* @param reloadDependenciesPeriodMillis how often dependencies are checked for reload triggers
138+
* @param coroutineScopeFactory factory used to create coroutine scopes for loading
139+
* @param transformation optional transformation applied to loaded containers
140+
* @param valueLoaderFactory function that creates a separate value loader for the specific argument
141+
*/
142+
public fun <Arg, T> createFromFactory(
143+
cacheTimeoutMillis: Long = DEFAULT_CACHE_TIMEOUT_MILLIS,
144+
reloadDependenciesPeriodMillis: Long = DEFAULT_RELOAD_DEPENDENCIES_PERIOD_MILLIS,
145+
coroutineScopeFactory: CoroutineScopeFactory = CoroutineScopeFactory,
146+
transformation: ContainerTransformation<T> = EmptyContainerTransformation(),
147+
valueLoaderFactory: ValueLoaderFactory<Arg, T>,
148+
): LazyCache<Arg, T> {
149+
return LazyCacheImpl(
150+
cacheTimeoutMillis = cacheTimeoutMillis,
151+
reloadDependenciesPeriodMillis = reloadDependenciesPeriodMillis,
152+
coroutineScopeFactory = coroutineScopeFactory,
153+
transformation = transformation,
154+
valueLoaderFactory = valueLoaderFactory,
155+
)
156+
}
157+
127158
}
128159
}

container/src/main/java/com/elveum/container/cache/LazyCacheExtensions.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import com.elveum.container.Container
44
import com.elveum.container.LoadConfig
55
import com.elveum.container.SourceType
66
import com.elveum.container.factory.DEFAULT_CACHE_TIMEOUT_MILLIS
7+
import com.elveum.container.subject.ContainerConfiguration
78
import com.elveum.container.transform
9+
import kotlinx.coroutines.flow.Flow
810

911
public typealias SimpleCacheValueLoader<Arg, T> = suspend (Arg) -> T
1012

@@ -78,3 +80,23 @@ public inline fun <Arg, T> LazyCache<Arg, T>.updateIfSuccess(
7880
)
7981
}
8082
}
83+
84+
/**
85+
* Listen for values loaded by the cache for the specific [arg] and
86+
* for each emitted container:
87+
* - attach a valid reload function
88+
* - re-emit background load indicator if data is being reloaded.
89+
*/
90+
public fun <Arg, T> LazyCache<Arg, T>.listenReloadable(
91+
arg: Arg,
92+
emitReloadFunction: Boolean = true,
93+
emitBackgroundLoads: Boolean = true,
94+
): Flow<Container<T>> {
95+
return listen(
96+
arg = arg,
97+
configuration = ContainerConfiguration(
98+
emitBackgroundLoads = emitBackgroundLoads,
99+
emitReloadFunction = emitReloadFunction,
100+
)
101+
)
102+
}

container/src/main/java/com/elveum/container/cache/LazyCacheImpl.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ internal class LazyCacheImpl<Arg, T>(
2626
private val coroutineScopeFactory: CoroutineScopeFactory,
2727
private val reloadDependenciesPeriodMillis: Long = DEFAULT_RELOAD_DEPENDENCIES_PERIOD_MILLIS,
2828
transformation: ContainerTransformation<T> = EmptyContainerTransformation(),
29-
private val valueLoader: CacheValueLoader<Arg, T>,
29+
private val valueLoaderFactory: ValueLoaderFactory<Arg, T>,
3030
private val subjectFactory: LazyFlowSubjectFactory<T> = LazyFlowSubjectFactory.Default(
3131
cacheTimeoutMillis, reloadDependenciesPeriodMillis, coroutineScopeFactory, transformation,
3232
)
@@ -78,9 +78,9 @@ internal class LazyCacheImpl<Arg, T>(
7878

7979
private fun registerRecord(arg: Arg): CacheRecord<T> = synchronized(this) {
8080
val record = cacheSlots.getOrPut(arg) {
81-
val subject = subjectFactory.create {
82-
valueLoader.invoke(this, arg)
83-
}
81+
val subject = subjectFactory.create(
82+
valueLoader = valueLoaderFactory.create(arg)
83+
)
8484
CacheRecord(subject)
8585
}
8686
record.count++
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.elveum.container.cache
2+
3+
import com.elveum.container.subject.ValueLoader
4+
5+
/**
6+
* Create a new [ValueLoader] instance for the specific input argument.
7+
*/
8+
public fun interface ValueLoaderFactory<Arg, T> {
9+
public fun create(arg: Arg): ValueLoader<T>
10+
}

container/src/main/java/com/elveum/container/factory/DefaultSubjectFactory.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.elveum.container.factory
22

33
import com.elveum.container.cache.CacheValueLoader
4+
import com.elveum.container.cache.ValueLoaderFactory
45
import com.elveum.container.cache.LazyCache
56
import com.elveum.container.subject.LazyFlowSubject
67
import com.elveum.container.subject.ValueLoader
@@ -45,4 +46,20 @@ public open class DefaultSubjectFactory(
4546
)
4647
}
4748

49+
override fun <Arg, T> createCacheFromFactory(
50+
cacheTimeoutMillis: Long?,
51+
reloadDependenciesPeriodMillis: Long?,
52+
coroutineScopeFactory: CoroutineScopeFactory?,
53+
transformation: ContainerTransformation<T>?,
54+
valueLoaderFactory: ValueLoaderFactory<Arg, T>
55+
): LazyCache<Arg, T> {
56+
return LazyCache.createFromFactory(
57+
valueLoaderFactory = valueLoaderFactory,
58+
cacheTimeoutMillis = cacheTimeoutMillis ?: this.cacheTimeoutMillis,
59+
reloadDependenciesPeriodMillis = reloadDependenciesPeriodMillis ?: this.reloadDependenciesPeriodMillis,
60+
coroutineScopeFactory = coroutineScopeFactory ?: this.coroutineScopeFactory,
61+
transformation = transformation ?: transformationFactory.create(),
62+
)
63+
}
64+
4865
}

container/src/main/java/com/elveum/container/factory/SubjectFactory.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.elveum.container.factory
22

33
import com.elveum.container.cache.CacheValueLoader
4+
import com.elveum.container.cache.ValueLoaderFactory
45
import com.elveum.container.cache.LazyCache
56
import com.elveum.container.subject.LazyFlowSubject
67
import com.elveum.container.subject.ValueLoader
@@ -38,6 +39,18 @@ public interface SubjectFactory {
3839
valueLoader: CacheValueLoader<Arg, T>,
3940
): LazyCache<Arg, T>
4041

42+
/**
43+
* Create a new [LazyCache] instance which creates a separate
44+
* value loader for every argument using [ValueLoaderFactory].
45+
*/
46+
public fun <Arg, T> createCacheFromFactory(
47+
cacheTimeoutMillis: Long? = null,
48+
reloadDependenciesPeriodMillis: Long? = null,
49+
coroutineScopeFactory: CoroutineScopeFactory? = null,
50+
transformation: ContainerTransformation<T>? = null,
51+
valueLoaderFactory: ValueLoaderFactory<Arg, T>,
52+
): LazyCache<Arg, T>
53+
4154
public companion object : SubjectFactory {
4255

4356
@Volatile
@@ -65,6 +78,17 @@ public interface SubjectFactory {
6578
coroutineScopeFactory, transformation, valueLoader)
6679
}
6780

81+
override fun <Arg, T> createCacheFromFactory(
82+
cacheTimeoutMillis: Long?,
83+
reloadDependenciesPeriodMillis: Long?,
84+
coroutineScopeFactory: CoroutineScopeFactory?,
85+
transformation: ContainerTransformation<T>?,
86+
valueLoaderFactory: ValueLoaderFactory<Arg, T>
87+
): LazyCache<Arg, T> {
88+
return instance.createCacheFromFactory(cacheTimeoutMillis, reloadDependenciesPeriodMillis,
89+
coroutineScopeFactory, transformation, valueLoaderFactory)
90+
}
91+
6892
/**
6993
* Replace the default factory by the custom one.
7094
*/

container/src/test/java/com/elveum/container/cache/LazyCacheExtensionsTest.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.elveum.container.Emitter
55
import com.elveum.container.LoadConfig
66
import com.elveum.container.errorContainer
77
import com.elveum.container.pendingContainer
8+
import com.elveum.container.subject.ContainerConfiguration
89
import com.elveum.container.successContainer
910
import io.mockk.MockKAnnotations
1011
import io.mockk.coVerify
@@ -15,6 +16,7 @@ import io.mockk.mockkObject
1516
import io.mockk.slot
1617
import io.mockk.unmockkObject
1718
import io.mockk.verify
19+
import kotlinx.coroutines.flow.MutableStateFlow
1820
import kotlinx.coroutines.flow.emptyFlow
1921
import kotlinx.coroutines.test.runTest
2022
import org.junit.Assert.assertSame
@@ -132,4 +134,34 @@ class LazyCacheExtensionsTest {
132134
}
133135
}
134136

135-
}
137+
@Test
138+
fun listenReloadable_withDefaultArgs_delegatesArgs() {
139+
val expectedResult = MutableStateFlow(successContainer(""))
140+
every { lazyCache.listen(any(), any()) } returns expectedResult
141+
142+
val result = lazyCache.listenReloadable(arg = "1")
143+
144+
assertSame(expectedResult, result)
145+
verify(exactly = 1) {
146+
lazyCache.listen("1", ContainerConfiguration(true, true))
147+
}
148+
}
149+
150+
@Test
151+
fun listenReloadable_withCustomArgs_delegatesArgs() {
152+
val expectedResult = MutableStateFlow(successContainer(""))
153+
every { lazyCache.listen(any(), any()) } returns expectedResult
154+
155+
val result = lazyCache.listenReloadable(
156+
arg = "1",
157+
emitReloadFunction = false,
158+
emitBackgroundLoads = false,
159+
)
160+
161+
assertSame(expectedResult, result)
162+
verify(exactly = 1) {
163+
lazyCache.listen("1", ContainerConfiguration(false, false))
164+
}
165+
}
166+
167+
}

container/src/test/java/com/elveum/container/cache/LazyCacheIntegrationTest.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.elveum.container.Container
44
import com.elveum.container.Emitter
55
import com.elveum.container.LoadConfig
66
import com.elveum.container.factory.CoroutineScopeFactory
7+
import com.elveum.container.subject.ValueLoader
78
import com.elveum.container.successContainer
89
import com.elveum.container.utils.raw
910
import com.uandcode.flowtest.CollectStatus
@@ -53,7 +54,11 @@ class LazyCacheIntegrationTest {
5354
lazyCache = LazyCacheImpl(
5455
cacheTimeoutMillis = timeoutMillis,
5556
coroutineScopeFactory = coroutineScopeFactory,
56-
valueLoader = this.loader,
57+
valueLoaderFactory = { arg ->
58+
ValueLoader {
59+
loader(this, arg)
60+
}
61+
},
5762
)
5863
}
5964

container/src/test/java/com/elveum/container/cache/LazyCacheTest.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ class LazyCacheTest {
6060
lazyCache = LazyCacheImpl(
6161
cacheTimeoutMillis = timeoutMillis,
6262
coroutineScopeFactory = coroutineScopeFactory,
63-
valueLoader = loader,
63+
valueLoaderFactory = { arg ->
64+
ValueLoader {
65+
loader(this, arg)
66+
}
67+
},
6468
subjectFactory = subjectFactory,
6569
)
6670
}

0 commit comments

Comments
 (0)