From 3f4ed4187b0a623c940929039fc8facb1fe3a9d2 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 1 Oct 2020 13:54:09 +0200 Subject: [PATCH 1/8] Bump dev version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a1b2f4a..5fe265e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { allprojects { group = 'org.radarbase' - version = '0.3.1' + version = '0.3.2-SNAPSHOT' ext { jerseyVersion = "2.32" From 0e2fef2c11f0a98c21b339a642b93c982c39d107 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 1 Oct 2020 14:08:10 +0200 Subject: [PATCH 2/8] Test Accept header to /health endpoint --- .../jersey/hibernate/DatabaseHealthMetricsTest.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/radar-jersey-hibernate/src/test/kotlin/org/radarbase/jersey/hibernate/DatabaseHealthMetricsTest.kt b/radar-jersey-hibernate/src/test/kotlin/org/radarbase/jersey/hibernate/DatabaseHealthMetricsTest.kt index d533fea..c7dbaf7 100644 --- a/radar-jersey-hibernate/src/test/kotlin/org/radarbase/jersey/hibernate/DatabaseHealthMetricsTest.kt +++ b/radar-jersey-hibernate/src/test/kotlin/org/radarbase/jersey/hibernate/DatabaseHealthMetricsTest.kt @@ -3,7 +3,10 @@ package org.radarbase.jersey.hibernate import okhttp3.OkHttpClient import okhttp3.Request import org.hamcrest.MatcherAssert +import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.radarbase.jersey.GrizzlyServer @@ -38,8 +41,8 @@ internal class DatabaseHealthMetricsTest { client.newCall(Request.Builder() .url("http://localhost:9091/health") .build()).execute().use { response -> - MatcherAssert.assertThat(response.isSuccessful, Matchers.`is`(true)) - MatcherAssert.assertThat(response.body?.string(), Matchers.equalTo("{\"status\":\"UP\",\"db\":{\"status\":\"UP\"}}")) + assertThat(response.isSuccessful, `is`(true)) + assertThat(response.body?.string(), equalTo("{\"status\":\"UP\",\"db\":{\"status\":\"UP\"}}")) } } finally { server.shutdown() @@ -92,8 +95,8 @@ internal class DatabaseHealthMetricsTest { client.newCall(Request.Builder() .url("http://localhost:9091/health") .build()).execute().use { response -> - MatcherAssert.assertThat(response.isSuccessful, Matchers.`is`(true)) - MatcherAssert.assertThat(response.body?.string(), Matchers.equalTo("{\"status\":\"UP\",\"db\":{\"status\":\"UP\"}}")) + assertThat(response.isSuccessful, `is`(true)) + assertThat(response.body?.string(), equalTo("{\"status\":\"UP\",\"db\":{\"status\":\"UP\"}}")) } // Disable database. Connections should now fail @@ -103,8 +106,8 @@ internal class DatabaseHealthMetricsTest { client.newCall(Request.Builder() .url("http://localhost:9091/health") .build()).execute().use { response -> - MatcherAssert.assertThat(response.isSuccessful, Matchers.`is`(true)) - MatcherAssert.assertThat(response.body?.string(), Matchers.equalTo("{\"status\":\"DOWN\",\"db\":{\"status\":\"DOWN\"}}")) + assertThat(response.isSuccessful, `is`(true)) + assertThat(response.body?.string(), equalTo("{\"status\":\"DOWN\",\"db\":{\"status\":\"DOWN\"}}")) } } finally { server.shutdown() From 66d0a4f752783b75f4922a3a4f9be126edb60959 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 12:58:42 +0200 Subject: [PATCH 3/8] Make transactions more extensible --- .../jersey/hibernate/HibernateRepository.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/radar-jersey-hibernate/src/main/kotlin/org/radarbase/jersey/hibernate/HibernateRepository.kt b/radar-jersey-hibernate/src/main/kotlin/org/radarbase/jersey/hibernate/HibernateRepository.kt index c9bb1b8..d155d13 100644 --- a/radar-jersey-hibernate/src/main/kotlin/org/radarbase/jersey/hibernate/HibernateRepository.kt +++ b/radar-jersey-hibernate/src/main/kotlin/org/radarbase/jersey/hibernate/HibernateRepository.kt @@ -8,27 +8,34 @@ import javax.persistence.EntityManager import javax.persistence.EntityTransaction open class HibernateRepository( - @Suppress("MemberVisibilityCanBePrivate") - protected val entityManager: Provider + private val entityManagerProvider: Provider ) { + @Suppress("MemberVisibilityCanBePrivate") + protected val entityManager: EntityManager + get() = entityManagerProvider.get() + + @Suppress("unused") + fun transact(transactionOperation: EntityManager.() -> T) = entityManager.transact(transactionOperation) + @Suppress("unused") + fun createTransaction(transactionOperation: EntityManager.(CloseableTransaction) -> T) = entityManager.createTransaction(transactionOperation) + /** * Run a transaction and commit it. If an exception occurs, the transaction is rolled back. */ - open fun transact(transactionOperation: EntityManager.() -> T) = createTransaction { + open fun EntityManager.transact(transactionOperation: EntityManager.() -> T) = createTransaction { it.use { transactionOperation() } } /** * Start a transaction without committing it. If an exception occurs, the transaction is rolled back. */ - open fun createTransaction(transactionOperation: EntityManager.(CloseableTransaction) -> T): T { - val entityManager = entityManager.get() - val currentTransaction = entityManager.transaction + open fun EntityManager.createTransaction(transactionOperation: EntityManager.(CloseableTransaction) -> T): T { + val currentTransaction = transaction ?: throw HttpInternalServerException("transaction_not_found", "Cannot find a transaction from EntityManager") currentTransaction.begin() try { - return entityManager.transactionOperation(object : CloseableTransaction { + return transactionOperation(object : CloseableTransaction { override val transaction: EntityTransaction = currentTransaction override fun close() { From 6ecb390c454caa2efb20bcc408520347ada544ed Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 12:59:20 +0200 Subject: [PATCH 4/8] Ensure ManagementPortal URL ends with slash --- src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt | 8 ++++++++ .../radarbase/jersey/service/managementportal/MPClient.kt | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt b/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt index 724d8c0..9af0792 100644 --- a/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt @@ -10,6 +10,8 @@ package org.radarbase.jersey.auth import com.fasterxml.jackson.annotation.JsonIgnore +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.radarbase.jersey.config.ConfigLoader.copyEnv import org.radarbase.jersey.config.ConfigLoader.copyOnChange import java.time.Duration @@ -49,6 +51,12 @@ data class MPConfig( /** Interval after which the list of subjects in a project should be refreshed (minutes). */ val syncParticipantsIntervalMin: Long = 5, ) { + @JsonIgnore + val httpUrl: HttpUrl? = url?.toHttpUrlOrNull() + ?.let { url -> + url.newBuilder().addPathSegment("").build() + } + /** Interval after which the list of projects should be refreshed. */ @JsonIgnore val syncProjectsInterval: Duration = Duration.ofMinutes(syncProjectsIntervalMin) diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt index dd6087c..48e1ecf 100644 --- a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt @@ -25,8 +25,11 @@ class MPClient( ?: throw IllegalArgumentException("Cannot configure managementportal client without client ID") private val clientSecret: String = config.managementPortal.clientSecret ?: throw IllegalArgumentException("Cannot configure managementportal client without client secret") - private val baseUrl: HttpUrl = config.managementPortal.url?.toHttpUrlOrNull() - ?: throw MalformedURLException("Cannot parse base URL ${config.managementPortal.url} as an URL") + private val baseUrl: HttpUrl = (config.managementPortal.url?.toHttpUrlOrNull() + ?: throw MalformedURLException("Cannot parse base URL ${config.managementPortal.url} as an URL")) + .newBuilder() + .addPathSegment("") + .build() private val projectListReader = objectMapper.readerFor(object : TypeReference>() {}) private val userListReader = objectMapper.readerFor(object : TypeReference>() {}) From 4a758f1b909db5048fbaee4404dc9d61e017f7a2 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 14:29:31 +0200 Subject: [PATCH 5/8] Add readClients to MPClient --- .../service/managementportal/MPClient.kt | 21 ++++++++++++++----- .../service/managementportal/MPOAuthClient.kt | 7 +++++++ .../service/managementportal/MPProject.kt | 3 ++- .../jersey/service/managementportal/MPUser.kt | 3 ++- 4 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/org/radarbase/jersey/service/managementportal/MPOAuthClient.kt diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt index 48e1ecf..71e9909 100644 --- a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt @@ -2,8 +2,8 @@ package org.radarbase.jersey.service.managementportal import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectReader import okhttp3.* import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.radarbase.jersey.auth.Auth @@ -31,15 +31,16 @@ class MPClient( .addPathSegment("") .build() - private val projectListReader = objectMapper.readerFor(object : TypeReference>() {}) - private val userListReader = objectMapper.readerFor(object : TypeReference>() {}) - private val tokenReader = objectMapper.readerFor(RestOauth2AccessToken::class.java) + private val projectListReader: ObjectReader by lazy { objectMapper.readerForListOf(MPProject::class.java) } + private val userListReader: ObjectReader by lazy { objectMapper.readerForListOf(MPUser::class.java) } + private val clientListReader: ObjectReader by lazy { objectMapper.readerForListOf(MPOAuthClient::class.java) } + private val tokenReader: ObjectReader by lazy { objectMapper.readerFor(RestOauth2AccessToken::class.java) } @Volatile private var token: RestOauth2AccessToken? = null private val validToken: RestOauth2AccessToken? - get() = token?.takeIf { it.isValid() } + get() = token?.takeIf { it.isValid() } private fun ensureToken(): String = (validToken ?: requestToken().also { token = it }) @@ -86,6 +87,16 @@ class MPClient( .map { it.copy(projectId = projectId) } } + @Suppress("unused") + fun readClients(): List { + val request = Request.Builder().apply { + url(baseUrl.resolve("api/oauth-clients")!!) + header("Authorization", "Bearer ${ensureToken()}") + }.build() + + return httpClient.requestJson(request, clientListReader) + } + @JsonIgnoreProperties(ignoreUnknown = true) data class RestOauth2AccessToken( @JsonProperty("access_token") val accessToken: String, diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPOAuthClient.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPOAuthClient.kt new file mode 100644 index 0000000..316ea51 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPOAuthClient.kt @@ -0,0 +1,7 @@ +package org.radarbase.jersey.service.managementportal + +import com.fasterxml.jackson.annotation.JsonProperty + +data class MPOAuthClient( + @JsonProperty("clientId") val id: String, +) diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPProject.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPProject.kt index c2c7109..1239389 100644 --- a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPProject.kt +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPProject.kt @@ -15,4 +15,5 @@ data class MPProject( /** Project description. */ val description: String? = null, /** Any other attributes. */ - val attributes: Map = emptyMap()) + val attributes: Map = emptyMap(), +) diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPUser.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPUser.kt index fbe870d..cfa4210 100644 --- a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPUser.kt +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPUser.kt @@ -13,4 +13,5 @@ data class MPUser( /** User status in the project. */ val status: String = "DEACTIVATED", /** Additional attributes of the user. */ - val attributes: Map = emptyMap()) + val attributes: Map = emptyMap(), +) From c3f0a651bebf04c5da5921502f82a1d3efa98623 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 14:29:50 +0200 Subject: [PATCH 6/8] Make okhttpClient and ObjectMapper customizable --- .../config/RadarJerseyResourceEnhancer.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt index a82af2b..68c0832 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt @@ -34,26 +34,35 @@ import javax.ws.rs.ext.ContextResolver class RadarJerseyResourceEnhancer( private val config: AuthConfig ): JerseyResourceEnhancer { + var mapper: ObjectMapper = ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .registerModule(JavaTimeModule()) + .registerModule(KotlinModule()) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + var client: OkHttpClient = OkHttpClient().newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + override val classes = arrayOf( AuthenticationFilter::class.java, AuthorizationFeature::class.java) override fun ResourceConfig.enhance() { - register(ContextResolver { OBJECT_MAPPER }) + register(ContextResolver { mapper }) } override fun AbstractBinder.enhance() { bind(config.withEnv()) .to(AuthConfig::class.java) - bind(OkHttpClient().newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build()) + bind(client) .to(OkHttpClient::class.java) - bind(OBJECT_MAPPER) + bind(mapper) .to(ObjectMapper::class.java) // Bind factories. @@ -63,13 +72,4 @@ class RadarJerseyResourceEnhancer( .to(Auth::class.java) .`in`(RequestScoped::class.java) } - - companion object { - private val OBJECT_MAPPER: ObjectMapper = ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .registerModule(JavaTimeModule()) - .registerModule(KotlinModule()) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - } } From 07b83ce5f4e4cc13d90b6df42d7fafb6dc0c1039 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 14:30:54 +0200 Subject: [PATCH 7/8] Bump version --- README.md | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f8b2cb..7f81ddd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ repositories { } dependencies { - api("org.radarbase:radar-jersey:0.3.1") + api("org.radarbase:radar-jersey:0.3.2") } ``` diff --git a/build.gradle b/build.gradle index 5fe265e..60eb9c4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { allprojects { group = 'org.radarbase' - version = '0.3.2-SNAPSHOT' + version = '0.3.2' ext { jerseyVersion = "2.32" From 9def2af09369504805aa5ca19fb864b1e7e28546 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 7 Oct 2020 14:33:34 +0200 Subject: [PATCH 8/8] Fix indentation --- .../org/radarbase/jersey/service/managementportal/MPClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt index 71e9909..6bcab3e 100644 --- a/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt +++ b/src/main/kotlin/org/radarbase/jersey/service/managementportal/MPClient.kt @@ -40,7 +40,7 @@ class MPClient( private var token: RestOauth2AccessToken? = null private val validToken: RestOauth2AccessToken? - get() = token?.takeIf { it.isValid() } + get() = token?.takeIf { it.isValid() } private fun ensureToken(): String = (validToken ?: requestToken().also { token = it })