From 66203d522510d8526ca563b680ab4afce00ce157 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 16 Oct 2024 11:27:17 -0400 Subject: [PATCH 1/2] Extract session cache building into a factory + bump core and tests --- gradle.properties | 2 +- http-client-tests/tests/preset-queries.http | 4 +- .../validation/GuavaSessionCacheAdapter.kt | 6 ++- .../validation/SessionCacheFactory.kt | 7 +++ .../validation/SessionCacheFactoryImpl.kt | 49 +++++++++++++++++++ .../ValidationServiceFactoryImpl.kt | 27 +++------- src/jvmMain/resources/version.properties | 6 +-- 7 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 src/jvmMain/kotlin/controller/validation/SessionCacheFactory.kt create mode 100644 src/jvmMain/kotlin/controller/validation/SessionCacheFactoryImpl.kt diff --git a/gradle.properties b/gradle.properties index e38d150..59235fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official kotlin.js.generate.executable.default=false # versions -fhirCoreVersion= 6.3.27 +fhirCoreVersion= 6.3.32 junitVersion=5.7.1 mockk_version=1.10.2 diff --git a/http-client-tests/tests/preset-queries.http b/http-client-tests/tests/preset-queries.http index 386ce5b..f792978 100644 --- a/http-client-tests/tests/preset-queries.http +++ b/http-client-tests/tests/preset-queries.http @@ -87,7 +87,7 @@ Content-Type: application/json client.test("Issues are Correct", function() { let issues = response.body.outcomes[0].issues client.log("issues:" + issues.length) - client.assert(issues.length === 45); + client.assert(issues.length === 44); client.assert(containsIssue(issues, 1, 2, "The Snomed CT code 373270004 (Substance with penicillin structure and antibacterial mechanism of action) is not a member of the IPS free set", "BUSINESSRULE", "INFORMATION")) client.assert(containsIssue(issues, 1, 2, "The Snomed CT code 108774000 (Product containing anastrozole (medicinal product)) is not a member of the IPS free set", "BUSINESSRULE", "INFORMATION")) @@ -112,7 +112,7 @@ Content-Type: application/json client.test("Issues are Correct", function() { let issues = response.body.outcomes[0].issues client.log("issues:" + issues.length) - client.assert(issues.length === 53); + client.assert(issues.length === 49); client.assert(containsIssue(issues, 1, 2, "The Snomed CT code 373270004 (Substance with penicillin structure and antibacterial mechanism of action) is not a member of the IPS free set", "BUSINESSRULE", "INFORMATION")) client.assert(containsIssue(issues, 1, 2, "The Snomed CT code 108774000 (Product containing anastrozole (medicinal product)) is not a member of the IPS free set", "BUSINESSRULE", "INFORMATION")) client.assert(containsIssue(issues, 314, 4, "This element does not match any known slice defined in the profile http://hl7.org.au/fhir/ips/StructureDefinition/Bundle-au-ips|0.0.1 (this may not be a problem, but you should check that it's not intended to match a slice)", "INFORMATIONAL", "INFORMATION")) diff --git a/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt b/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt index cf9b9c4..8467370 100644 --- a/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt +++ b/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt @@ -8,7 +8,11 @@ import java.util.* import java.util.concurrent.TimeUnit class GuavaSessionCacheAdapter(cacheSize : Long, cacheDuration: Long) : SessionCache { - private val cache: Cache = CacheBuilder.newBuilder().expireAfterAccess(cacheDuration, TimeUnit.MINUTES).maximumSize(cacheSize).build() + private val cache: Cache = if (cacheDuration > 0) { + CacheBuilder.newBuilder().expireAfterAccess(cacheDuration, TimeUnit.MINUTES).maximumSize(cacheSize).build() + } else { + CacheBuilder.newBuilder().maximumSize(cacheSize).build() + } override fun cacheSession(validationEngine: ValidationEngine): String { val generatedId = generateID() diff --git a/src/jvmMain/kotlin/controller/validation/SessionCacheFactory.kt b/src/jvmMain/kotlin/controller/validation/SessionCacheFactory.kt new file mode 100644 index 0000000..577cfa0 --- /dev/null +++ b/src/jvmMain/kotlin/controller/validation/SessionCacheFactory.kt @@ -0,0 +1,7 @@ +package controller.validation + +import org.hl7.fhir.validation.cli.services.SessionCache + +interface SessionCacheFactory { + fun getSessionCache(): SessionCache +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/controller/validation/SessionCacheFactoryImpl.kt b/src/jvmMain/kotlin/controller/validation/SessionCacheFactoryImpl.kt new file mode 100644 index 0000000..66ad97d --- /dev/null +++ b/src/jvmMain/kotlin/controller/validation/SessionCacheFactoryImpl.kt @@ -0,0 +1,49 @@ +package controller.validation + +import org.hl7.fhir.validation.cli.services.PassiveExpiringSessionCache +import org.hl7.fhir.validation.cli.services.SessionCache +import java.util.concurrent.TimeUnit + +private const val SESSION_CACHE_DURATION_ENV_KEY = "SESSION_CACHE_DURATION" +private const val SESSION_DEFAULT_DURATION: Long = 60 + +private const val SESSION_CACHE_SIZE_ENV_KEY = "SESSION_CACHE_SIZE" +private const val SESSION_DEFAULT_SIZE: Long = 4 + +private const val SESSION_CACHE_IMPLEMENTATION_ENV_KEY = "SESSION_CACHE_IMPLEMENTATION" + +private const val GUAVA_SESSION_CACHE_IMPLEMENTATION = "GuavaSessionCacheAdapter" +private const val PASSIVE_EXPIRING_SESSION_CACHE_IMPLEMENTATION = "PassiveExpiringSessionCache" + +private const val SESSION_DEFAULT_CACHE_IMPLEMENTATION: String = GUAVA_SESSION_CACHE_IMPLEMENTATION + +class SessionCacheFactoryImpl : SessionCacheFactory { + + override fun getSessionCache(): SessionCache { + /* Session cache configuration from environment variables. + * + * SESSION_CACHE_IMPLEMENTATION can be either the deprecated PassiveExpiringSessionCache or the preferred + * GuavaSessionCacheAdapter, and will be GuavaSessionCacheAdapter if unspecified. + * + * SESSION_CACHE_DURATION is the duration in minutes that a session will be kept in the cache. If negative, + * sessions will not expire. If unspecified, the default is 60 minutes. + * + * SESSION_CACHE_SIZE (only available in GuavaSessionCacheAdapter) is the maximum number of sessions that will + * be kept in the cache in last accessed last out order. If unspecified, the default is 4. + * + * TODO this should be encapsulated in a session cache configuration class, and use a cleaner method of + * configuration. These env vars are overloaded, and should be split per cache implementation. + */ + val sessionCacheDuration = System.getenv(SESSION_CACHE_DURATION_ENV_KEY)?.toLong() ?: SESSION_DEFAULT_DURATION; + val sessionCacheSize = System.getenv(SESSION_CACHE_SIZE_ENV_KEY)?.toLong() ?: SESSION_DEFAULT_SIZE + val sessionCacheImplementation = + System.getenv(SESSION_CACHE_IMPLEMENTATION_ENV_KEY) ?: SESSION_DEFAULT_CACHE_IMPLEMENTATION; + val sessionCache: SessionCache = + if (sessionCacheImplementation == PASSIVE_EXPIRING_SESSION_CACHE_IMPLEMENTATION) { + PassiveExpiringSessionCache(sessionCacheDuration, TimeUnit.MINUTES).setResetExpirationAfterFetch(true); + } else { + GuavaSessionCacheAdapter(sessionCacheSize, sessionCacheDuration) + } + return sessionCache + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/controller/validation/ValidationServiceFactoryImpl.kt b/src/jvmMain/kotlin/controller/validation/ValidationServiceFactoryImpl.kt index a26defa..45a871e 100644 --- a/src/jvmMain/kotlin/controller/validation/ValidationServiceFactoryImpl.kt +++ b/src/jvmMain/kotlin/controller/validation/ValidationServiceFactoryImpl.kt @@ -1,37 +1,22 @@ package controller.validation -import com.typesafe.config.ConfigFactory import constants.Preset -import io.ktor.server.config.* -import model.PackageInfo -import org.hl7.fhir.validation.cli.services.PassiveExpiringSessionCache import org.hl7.fhir.validation.cli.services.SessionCache import org.hl7.fhir.validation.cli.services.ValidationService -import java.util.concurrent.TimeUnit import kotlin.concurrent.thread - -private const val SESSION_DEFAULT_DURATION: Long = 60 -private const val SESSION_DEFAULT_SIZE: Long = 4 -private const val SESSION_DEFAULT_CACHE_IMPLEMENTATION: String = "GuavaSessionCacheAdapter" - class ValidationServiceFactoryImpl : ValidationServiceFactory { + private var sessionCacheFactory: SessionCacheFactory private var validationService: ValidationService init { + sessionCacheFactory = SessionCacheFactoryImpl() validationService = createValidationServiceInstance(); - } fun createValidationServiceInstance(): ValidationService { - val sessionCacheDuration = System.getenv("SESSION_CACHE_DURATION")?.toLong() ?: SESSION_DEFAULT_DURATION; - val sessionCacheSize = System.getenv("SESSION_CACHE_SIZE")?.toLong() ?: SESSION_DEFAULT_SIZE - val sessionCacheImplementation = System.getenv("SESSION_CACHE_IMPLEMENTATION") ?: SESSION_DEFAULT_CACHE_IMPLEMENTATION; - val sessionCache: SessionCache = if (sessionCacheImplementation == "PassiveExpiringSessionCache") { - PassiveExpiringSessionCache(sessionCacheDuration, TimeUnit.MINUTES).setResetExpirationAfterFetch(true); - } else { - GuavaSessionCacheAdapter(sessionCacheSize, sessionCacheDuration) - } + val sessionCache: SessionCache = sessionCacheFactory.getSessionCache() + val validationService = ValidationService(sessionCache); thread { Preset.values().forEach { @@ -49,7 +34,9 @@ class ValidationServiceFactoryImpl : ValidationServiceFactory { } return validationService } - + + + override fun getValidationService() : ValidationService { val engineReloadThreshold = (System.getenv("ENGINE_RELOAD_THRESHOLD") ?: "250000000").toLong() if (java.lang.Runtime.getRuntime().freeMemory() < engineReloadThreshold) { diff --git a/src/jvmMain/resources/version.properties b/src/jvmMain/resources/version.properties index 063e968..ed643c9 100644 --- a/src/jvmMain/resources/version.properties +++ b/src/jvmMain/resources/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu Aug 29 22:33:32 UTC 2024 +#Mon Sep 23 16:48:02 UTC 2024 version.buildmeta= version.major=1 version.minor=0 -version.patch=56 +version.patch=57 version.prerelease=SNAPSHOT -version.semver=1.0.56-SNAPSHOT +version.semver=1.0.57-SNAPSHOT From fb89eff2031f9b3fe815db4e79d04d03b2f144f1 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 16 Oct 2024 17:37:33 -0400 Subject: [PATCH 2/2] Update src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt Co-authored-by: Dylan Hall --- .../validation/GuavaSessionCacheAdapter.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt b/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt index 8467370..ab99aa7 100644 --- a/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt +++ b/src/jvmMain/kotlin/controller/validation/GuavaSessionCacheAdapter.kt @@ -8,10 +8,16 @@ import java.util.* import java.util.concurrent.TimeUnit class GuavaSessionCacheAdapter(cacheSize : Long, cacheDuration: Long) : SessionCache { - private val cache: Cache = if (cacheDuration > 0) { - CacheBuilder.newBuilder().expireAfterAccess(cacheDuration, TimeUnit.MINUTES).maximumSize(cacheSize).build() - } else { - CacheBuilder.newBuilder().maximumSize(cacheSize).build() + private val cache: Cache; + init { + val cacheBuilder = CacheBuilder.newBuilder() + if (cacheDuration > 0) { + cacheBuilder.expireAfterAccess(cacheDuration, TimeUnit.MINUTES) + } + if (cacheSize >= 0) { + cacheBuilder.maximumSize(cacheSize) + } + cache = cacheBuilder.build() } override fun cacheSession(validationEngine: ValidationEngine): String {