diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt index 309161e04aa5d4fc8448a66f905017529bbf677e..4496d0d7de6169531a33e1305a7c692c93f288ab 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt @@ -29,6 +29,7 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.header import io.ktor.http.URLProtocol import org.koin.core.context.GlobalContext.get +import org.koin.core.parameter.parametersOf import kotlin.time.DurationUnit import co.touchlab.kermit.Logger as KermitLogger import io.ktor.client.plugins.logging.Logger as KtorLogger @@ -70,7 +71,7 @@ internal object HttpClientFactory { when (config.httpClientLoggingBlock) { null -> { install(Logging) { - val kermit = get().get<KermitLogger>() + val kermit = get().get<KermitLogger> { parametersOf(null, config) } logger = object : KtorLogger { override fun log(message: String) { kermit.d(message) diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/KoinAppFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/KoinAppFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..f0f4c01a323ffd8e4700c0e455044e8a50127db2 --- /dev/null +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/KoinAppFactory.kt @@ -0,0 +1,49 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.core + +import edu.ucsc.its.temerity.di.libModule +import edu.ucsc.its.temerity.di.loggerModule +import edu.ucsc.its.temerity.platformModule +import org.koin.core.Koin +import org.koin.core.KoinApplication +import org.koin.dsl.koinApplication + +/** + * Factory providing a Koin application for the Temerity library. + */ +internal object KoinAppFactory { + /** + * This function initializes the Koin dependency injection framework + */ + internal fun createKoinApp() = object : TemerityKoinContext() { + override val koinApp = koinApplication { + modules( + loggerModule(), + platformModule(), + libModule, + ) + } + override val koin: Koin = koinApp.koin + } +} + +internal abstract class TemerityKoinContext { + abstract val koinApp: KoinApplication + abstract val koin: Koin +} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt index e3647ab3d541946f77285d55431b5ead0b9086b9..71b6a69b02e3e5d28374df57b7db51c105122bd0 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt @@ -31,8 +31,6 @@ import edu.ucsc.its.temerity.TemerityApi import edu.ucsc.its.temerity.api.PlatformApi import edu.ucsc.its.temerity.core.JsonFactory.buildJson import edu.ucsc.its.temerity.createJobScope -import edu.ucsc.its.temerity.di.libModule -import edu.ucsc.its.temerity.di.loggerModule import edu.ucsc.its.temerity.extensions.applyAuditLogFormat import edu.ucsc.its.temerity.extensions.applyScheduledSessionDateFormat import edu.ucsc.its.temerity.model.AuditLogEntry @@ -47,7 +45,6 @@ import edu.ucsc.its.temerity.model.User import edu.ucsc.its.temerity.model.UserGroup import edu.ucsc.its.temerity.model.UserRecordingSession import edu.ucsc.its.temerity.model.UserUpdate -import edu.ucsc.its.temerity.platformModule import edu.ucsc.its.temerity.sortByCreationDate import io.ktor.client.statement.HttpResponse import io.ktor.util.cio.toByteArray @@ -63,10 +60,10 @@ import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.koin.core.Koin -import org.koin.core.KoinApplication +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import org.koin.core.context.GlobalContext.get import org.koin.core.parameter.parametersOf -import org.koin.dsl.koinApplication import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import co.touchlab.kermit.Logger as KermitLogger @@ -76,7 +73,8 @@ import co.touchlab.kermit.Logger as KermitLogger */ public class Temerity internal constructor( private val config: TemClientConfig, -) : TemerityApi { +) : TemerityApi, + KoinComponent { /** * Constructor for Temerity */ @@ -93,10 +91,9 @@ public class Temerity internal constructor( override fun version(): String = BuildConfig.LIB_VERSION private val json: Json = buildJson() + internal val koinContext = KoinAppFactory.createKoinApp() + override fun getKoin(): Koin = koinContext.koin - @Suppress("MemberVisibilityCanBePrivate") - internal val temerityKoinApp: KoinApplication - internal val koin: Koin private var platformApi: PlatformApi private var cachedUserRoleList: ConcurrentMutableMap<Int, String> private var clientCoroutineScope: CoroutineScope @@ -110,22 +107,14 @@ public class Temerity internal constructor( "Service url must be provided. Set it using TemClientConfig.serviceUrl(url)" } - temerityKoinApp = koinApplication { - modules( - loggerModule(), - platformModule(), - libModule, - ) - } - koin = temerityKoinApp.koin - clientCoroutineScope = koin.get<CoroutineScope> { + clientCoroutineScope = get<CoroutineScope> { when (val optThreadCount = config.threadCount) { null -> parametersOf(DispatcherFactory.calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT)) else -> parametersOf(DispatcherFactory.setThreadCount(optThreadCount)) } } - platformApi = koin.get<PlatformApi> { + platformApi = get<PlatformApi> { parametersOf(config) } @@ -164,7 +153,7 @@ public class Temerity internal constructor( when (exception) { is SerializationException -> { // TODO: Implement platform API version checking - get().get<KermitLogger>().d { "Returned JSON object: ${exception.message}" } + get<KermitLogger>().d { "Returned JSON object: ${exception.message}" } error("Encountered error decoding response from platform API. You likely need to choose a Temerity release that supports the API version implemented by your instance. \nPlatform API response data : $apiResponsePayload") } @@ -252,7 +241,7 @@ public class Temerity internal constructor( when (e) { is BreakException -> { if (config.optDebugEnabled) { - get().get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading groups from API: ${e.page} pages read") + get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading groups from API: ${e.page} pages read") } return returnedGroups } @@ -286,7 +275,7 @@ public class Temerity internal constructor( when (e) { is BreakException -> { if (config.optDebugEnabled) { - get().get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading groups from API: $e") + get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading groups from API: $e") } return returnedCourses } @@ -368,7 +357,7 @@ public class Temerity internal constructor( when (e) { is BreakException -> { if (config.optDebugEnabled) { - get().get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading audit log entries from API for specific window: ${e.page} pages read") + get<KermitLogger>().d("Caught BreakException() from decodeResponseCatching() notifying we're done reading audit log entries from API for specific window: ${e.page} pages read") } break } diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt index 86610e3637007d1b16091aaa1a256278ef39e36b..097d4815cfeb93ff7465d47d8e4a59a92609171c 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt @@ -21,6 +21,7 @@ import co.touchlab.kermit.Logger import com.skydoves.sandwich.StatusCode import com.skydoves.sandwich.ktor.getStatusCode import edu.ucsc.its.temerity.AuditLogSortOrder.NEW_FIRST +import edu.ucsc.its.temerity.core.TemClientConfig import edu.ucsc.its.temerity.core.Temerity import edu.ucsc.its.temerity.currentDate import edu.ucsc.its.temerity.di.loggerModule @@ -46,6 +47,7 @@ import org.koin.core.component.inject import org.koin.core.parameter.parametersOf import org.koin.test.KoinTest import java.io.File +import kotlin.test.assertNotEquals import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -55,7 +57,7 @@ class TemerityDevTest : KoinTest { override fun extensions(): List<Extension> = listOf(koinExtension(loggerModule())) - private val kermit: Logger by inject { parametersOf("TemerityDevTest") } + private val kermit: Logger by inject { parametersOf("TemerityDevTest", TemClientConfig { supportKtxNotebook = false }) } init { val dotenv = dotenvVault() @@ -76,6 +78,16 @@ class TemerityDevTest : assert(returnedVersion.isPreRelease) } + test("Temerity client returns different Koin Application instances on each creation") { + val firstInstance = Temerity { + configureDevEnvironment(dotenv) + } + val secondInstance = Temerity { + configureDevEnvironment(dotenv) + } + assertNotEquals(firstInstance.koinContext, secondInstance.koinContext) + } + test("Get all audit log entries of type \"New Login\" from the past month") { runBlocking { val currentDate = currentDate()