Skip to content

Commit

Permalink
Merge pull request #22 from RADAR-base/release-0.4.1
Browse files Browse the repository at this point in the history
Release 0.4.1
  • Loading branch information
blootsvoets authored Oct 20, 2020
2 parents feb89b5 + 66c53de commit d2b10f2
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repositories {
}
dependencies {
api("org.radarbase:radar-jersey:0.4.0")
api("org.radarbase:radar-jersey:0.4.1")
}
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {

allprojects {
group = 'org.radarbase'
version = '0.4.0'
version = '0.4.1'

ext {
jerseyVersion = "2.32"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,54 +47,53 @@ class MPClient(
.accessToken

private fun requestToken(): RestOauth2AccessToken {
val request = Request.Builder().apply {
url(baseUrl.resolve("oauth/token")!!)
return request(tokenReader, { addPathSegments("api/projects") }) {
post(FormBody.Builder().apply {
add("grant_type", "client_credentials")
add("client_id", clientId)
add("client_secret", clientSecret)
}.build())
header("Authorization", Credentials.basic(clientId, clientSecret))
}.build()

return httpClient.requestJson(request, tokenReader)
}
}

/** Read list of projects from ManagementPortal. */
fun readProjects(): List<MPProject> {
logger.debug("Requesting for projects")
val request = Request.Builder().apply {
url(baseUrl.resolve("api/projects")!!)
header("Authorization", "Bearer ${ensureToken()}")
}.build()

return httpClient.requestJson(request, projectListReader)
return request(projectListReader,
{
addPathSegments("api/projects")
addQueryParameter("page", "0")
addQueryParameter("size", Int.MAX_VALUE.toString())
})
}

/** Read list of participants from ManagementPortal project. The [projectId] is the name that
* the project is identified by. */
fun readParticipants(projectId: String): List<MPUser> {
val request = Request.Builder().apply {
url(baseUrl.newBuilder()
.addPathSegments("api/projects/$projectId/subjects")
.addQueryParameter("page", "0")
.addQueryParameter("size", Int.MAX_VALUE.toString())
.build())
header("Authorization", "Bearer ${ensureToken()}")
}.build()

return httpClient.requestJson<List<MPUser>>(request, userListReader)
return request<List<MPUser>>(userListReader,
{
addPathSegments("api/projects/$projectId/subjects")
addQueryParameter("page", "0")
addQueryParameter("size", Int.MAX_VALUE.toString())
})
.map { it.copy(projectId = projectId) }
}

@Suppress("unused")
fun readClients(): List<MPOAuthClient> {
return request(clientListReader, { addPathSegments("api/oauth-clients") })
}

fun <T> request(reader: ObjectReader, urlBuilder: HttpUrl.Builder.() -> Unit, requestBuilder: (Request.Builder.() -> Unit)? = null): T {
val request = Request.Builder().apply {
url(baseUrl.resolve("api/oauth-clients")!!)
url(baseUrl.newBuilder().apply {
urlBuilder()
}.build())
header("Authorization", "Bearer ${ensureToken()}")
if (requestBuilder != null) requestBuilder()
}.build()

return httpClient.requestJson(request, clientListReader)
return httpClient.requestJson(request, reader)
}

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down
33 changes: 28 additions & 5 deletions src/main/kotlin/org/radarbase/jersey/util/CachedValue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@ import java.util.concurrent.Semaphore
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantReadWriteLock

/**
* Cached value. This value is refreshed after a refresh period has elapsed, or when the
* cache returns an undesirable result and a retry period has elapsed. Using this class ensures
* that the underlying supplier is not taxed too much. Any exceptions during initialization will
* not be caught.
*/
open class CachedValue<T: Any>(
protected val cacheConfig: CacheConfig = CacheConfig(),
/** How to update the cache. */
/**
* How to update the cache. If no initial value is given, this will
* be called as initialization.
*/
private val supplier: () -> T,
/**
* Initial value of the cache. The value generated by this function is
* considered invalid, but it will still be returned if another thread
* is already computing the new value.
*/
initialValue: (() -> T)? = null,
) {
constructor(
Expand All @@ -29,9 +43,6 @@ open class CachedValue<T: Any>(
private val writeLock = refreshLock.writeLock()
private val computeSemaphore = Semaphore(cacheConfig.maxSimultaneousCompute)

private var lastUpdateNanos: Long = Long.MIN_VALUE
private var _exception: Exception? = null

val exception: Exception?
get() = readLock.locked { _exception }

Expand All @@ -40,7 +51,19 @@ open class CachedValue<T: Any>(
System.nanoTime() >= lastUpdateNanos + cacheConfig.staleNanos
}

private var cache: T = initialValue?.invoke() ?: supplier()
private var cache: T
private var lastUpdateNanos: Long
private var _exception: Exception? = null

init {
if (initialValue != null) {
cache = initialValue()
lastUpdateNanos = Long.MIN_VALUE
} else {
cache = supplier()
lastUpdateNanos = System.nanoTime()
}
}

val value: T
get() = readLock.locked { cache }
Expand Down
18 changes: 15 additions & 3 deletions src/test/kotlin/org/radarbase/jersey/util/CachedValueTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class CachedValueTest {
refreshDuration = Duration.ofMillis(10),
staleThresholdDuration = Duration.ofMillis(10),
), supplier = { listOf("something") })
assertThat("Initial value is stale", cache.isStale, `is`(true))
assertThat("Initial value from supplier is not stale", cache.isStale, `is`(false))
cache.get()
assertThat("After get, cache is not stale", cache.isStale, `is`(false))
Thread.sleep(10)
Expand All @@ -32,6 +32,18 @@ internal class CachedValueTest {
assertThat("After refresh + stale duration, cache is stale", cache.isStale, `is`(true))
}

@Test
fun isStaleInitial() {
val cache = CachedValue(
CacheConfig(
refreshDuration = Duration.ofMillis(10),
staleThresholdDuration = Duration.ofMillis(10),
), supplier = { listOf("something") }, { emptyList() })
assertThat("Initial value from initialValue is stale", cache.isStale, `is`(true))
cache.get()
assertThat("After get, cache is not stale", cache.isStale, `is`(false))
}

@Test
fun get() {
val cache = CachedValue(
Expand Down Expand Up @@ -218,12 +230,12 @@ internal class CachedValueTest {

assertThat(cache.get(), `is`(1))
assertThat(cache.get(), `is`(1))
Thread.sleep(20L)
Thread.sleep(21L)
assertThrows<IllegalStateException> { cache.get() }
assertThat(cache.exception, not(nullValue()))
assertThat(cache.exception, instanceOf(IllegalStateException::class.java))
assertThrows<Exception> { cache.get() }
Thread.sleep(10L)
Thread.sleep(11L)
assertThat(cache.get(), `is`(3))
}
}

0 comments on commit d2b10f2

Please sign in to comment.