From 91a8ff2d03cc8726d6b85be783de3e3a3609607d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 22 Apr 2024 16:38:27 +0200 Subject: [PATCH] refactor: Move caching into KnoraUser and KnoraProjectRepo and update ehcache to v3 (#3201) --- .../04-publishing-deployment/configuration.md | 1 - .../test/scala/org/knora/sipi/SipiIT.scala | 6 +- .../org/knora/webapi/core/LayersTest.scala | 10 +- .../v2/OntologyResponderV2Spec.scala | 27 --- .../webapi/routing/AuthenticatorSpec.scala | 4 +- .../webapi/store/cache/CacheServiceSpec.scala | 116 ------------ .../webapi/util/cache/CacheUtilSpec.scala | 32 ---- project/Dependencies.scala | 2 +- webapi/src/main/resources/application.conf | 53 ------ .../org/knora/webapi/config/AppConfig.scala | 27 --- .../org/knora/webapi/core/AppServer.scala | 21 +-- .../org/knora/webapi/core/LayersLive.scala | 10 +- .../knora/webapi/core/domain/AppState.scala | 29 ++- .../CacheServiceMessages.scala | 23 --- .../responders/v2/OntologyResponderV2.scala | 21 +-- .../responders/v2/StandoffResponderV2.scala | 29 ++- .../knora/webapi/routing/Authenticator.scala | 13 +- .../webapi/routing/InvalidTokenCache.scala | 23 +++ .../webapi/slice/admin/AdminModule.scala | 3 +- .../slice/admin/api/AdminApiModule.scala | 41 ++++- .../admin/api/service/StoreRestService.scala | 6 +- .../admin/domain/AdminDomainModule.scala | 4 +- .../domain/service/KnoraProjectService.scala | 5 +- .../domain/service/KnoraUserService.scala | 4 +- .../service/KnoraUserToUserConverter.scala | 3 + .../admin/domain/service/ProjectService.scala | 11 +- .../admin/domain/service/UserService.scala | 35 +--- .../slice/admin/repo/AdminRepoModule.scala | 7 +- .../repo/service/CachingEntityRepo.scala | 24 +++ .../admin/repo/service/EntityCache.scala | 32 ++++ .../repo/service/KnoraProjectRepoLive.scala | 12 +- .../repo/service/KnoraUserRepoLive.scala | 12 +- .../slice/infrastructure/CacheManager.scala | 60 +++++++ .../slice/infrastructure/JwtService.scala | 12 +- .../api/ManagementEndpoints.scala | 29 ++- .../repo/model/OntologyCacheData.scala | 13 ++ .../ontology/repo/service/OntologyCache.scala | 84 ++------- .../webapi/store/cache/CacheService.scala | 157 ---------------- .../CacheServiceRequestMessageHandler.scala | 53 ------ .../knora/webapi/util/cache/CacheUtil.scala | 167 ------------------ .../AuthorizationRestServiceSpec.scala | 4 +- .../api/service/MaintenanceServiceSpec.scala | 2 - .../domain/service/GroupServiceSpec.scala | 4 +- .../service/KnoraProjectRepoLiveSpec.scala | 14 +- .../repo/service/KnoraUserRepoLiveSpec.scala | 76 ++++---- .../infrastructure/JwtServiceLiveSpec.scala | 32 ++-- .../api/TriplestoreServiceInMemory.scala | 3 + 47 files changed, 364 insertions(+), 992 deletions(-) delete mode 100644 integration/src/test/scala/org/knora/webapi/store/cache/CacheServiceSpec.scala delete mode 100644 integration/src/test/scala/org/knora/webapi/util/cache/CacheUtilSpec.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/messages/store/cacheservicemessages/CacheServiceMessages.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/routing/InvalidTokenCache.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/CachingEntityRepo.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/EntityCache.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/slice/infrastructure/CacheManager.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/store/cache/CacheService.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/store/cache/CacheServiceRequestMessageHandler.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/util/cache/CacheUtil.scala diff --git a/docs/04-publishing-deployment/configuration.md b/docs/04-publishing-deployment/configuration.md index 815178547e..c353bf8d7b 100644 --- a/docs/04-publishing-deployment/configuration.md +++ b/docs/04-publishing-deployment/configuration.md @@ -59,7 +59,6 @@ A number of core settings is additionally configurable through system environmen | app.triplestore.fuseki.repository-name | KNORA_WEBAPI_TRIPLESTORE_FUSEKI_REPOSITORY_NAME | knora-test | | app.triplestore.fuseki.username | KNORA_WEBAPI_TRIPLESTORE_FUSEKI_USERNAME | admin | | app.triplestore.fuseki.password | KNORA_WEBAPI_TRIPLESTORE_FUSEKI_PASSWORD | test | -| app.cache-service.enabled | KNORA_WEBAPI_CACHE_SERVICE_ENABLED | true | ## Selectively Disabling Routes diff --git a/integration/src/test/scala/org/knora/sipi/SipiIT.scala b/integration/src/test/scala/org/knora/sipi/SipiIT.scala index 5cc486593c..d661fc90e6 100644 --- a/integration/src/test/scala/org/knora/sipi/SipiIT.scala +++ b/integration/src/test/scala/org/knora/sipi/SipiIT.scala @@ -29,14 +29,15 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentif import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortnameIdentifier import org.knora.webapi.messages.util.KnoraSystemInstances.Users.SystemUser +import org.knora.webapi.routing.InvalidTokenCache import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.common.repo.service.CrudRepository +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.slice.infrastructure.JwtServiceLive -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.testcontainers.SharedVolumes import org.knora.webapi.testcontainers.SipiTestContainer @@ -118,9 +119,10 @@ object SipiIT extends ZIOSpecDefault { .map(_.jwtString) .provide( JwtServiceLive.layer, + InvalidTokenCache.layer, AppConfig.layer, + CacheManager.layer, KnoraProjectService.layer, - CacheService.layer, KnoraProjectRepoInMemory.layer, ) diff --git a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala index 0572525dce..1d6292fdb8 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -42,6 +42,7 @@ import org.knora.webapi.slice.admin.domain.service.ProjectExportStorageService import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.common.api._ import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.slice.infrastructure.api.ManagementEndpoints @@ -58,9 +59,6 @@ import org.knora.webapi.slice.resourceinfo.ResourceInfoLayers import org.knora.webapi.slice.resourceinfo.domain.IriConverter import org.knora.webapi.slice.search.api.SearchApiRoutes import org.knora.webapi.slice.search.api.SearchEndpoints -import org.knora.webapi.store.cache.CacheService -import org.knora.webapi.store.cache.CacheServiceRequestMessageHandler -import org.knora.webapi.store.cache.CacheServiceRequestMessageHandlerLive import org.knora.webapi.store.iiif.IIIFRequestMessageHandler import org.knora.webapi.store.iiif.IIIFRequestMessageHandlerLive import org.knora.webapi.store.iiif.api.SipiService @@ -95,7 +93,7 @@ object LayersTest { AssetPermissionsResponder & Authenticator & AuthorizationRestService & - CacheServiceRequestMessageHandler & + CacheManager & CardinalityHandler & ConstructResponseUtilV2 & DspIngestClient & @@ -106,6 +104,7 @@ object LayersTest { HttpServer & IIIFRequestMessageHandler & InferenceOptimizationService & + InvalidTokenCache & IriConverter & JwtService & KnoraUserToUserConverter & @@ -155,8 +154,6 @@ object LayersTest { AuthenticatorLive.layer, AuthorizationRestService.layer, BaseEndpoints.layer, - CacheService.layer, - CacheServiceRequestMessageHandlerLive.layer, CardinalityHandlerLive.layer, CardinalityService.layer, ConstructResponseUtilV2Live.layer, @@ -168,6 +165,7 @@ object LayersTest { HttpServer.layer, IIIFRequestMessageHandlerLive.layer, InferenceOptimizationService.layer, + InvalidTokenCache.layer, IriConverter.layer, IriService.layer, JwtServiceLive.layer, diff --git a/integration/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala b/integration/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala index 2f8bdc5b64..6c9842d551 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala @@ -21,9 +21,6 @@ import org.knora.webapi.messages.IriConversions._ import org.knora.webapi.messages.OntologyConstants import org.knora.webapi.messages.SmartIri import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.projectsmessages.Project -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM -import org.knora.webapi.messages.store.cacheservicemessages.CacheServiceGetProjectADM import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.v2.responder.CanDoResponseV2 @@ -36,8 +33,6 @@ import org.knora.webapi.messages.v2.responder.resourcemessages.CreateValueInNewR import org.knora.webapi.messages.v2.responder.valuemessages.IntegerValueContentV2 import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.sharedtestdata.SharedTestDataADM -import org.knora.webapi.sharedtestdata.SharedTestDataADM.imagesProject -import org.knora.webapi.slice.admin.api.service.ProjectRestService import org.knora.webapi.slice.ontology.domain.model.Cardinality._ import org.knora.webapi.slice.ontology.repo.service.OntologyCache import org.knora.webapi.store.triplestore.api.TriplestoreService @@ -159,28 +154,6 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender { ) } - "invalidate cached project information when adding an ontology to a project" in { - // Ensure that the project is cached - UnsafeZioRun.runOrThrow(ZIO.serviceWithZIO[ProjectRestService](_.findById(imagesProject.projectIri))) - - appActor ! CacheServiceGetProjectADM(ProjectIdentifierADM.IriIdentifier.unsafeFrom(imagesProjectIri.toString)) - val cachedProjectBefore = expectMsgType[Option[Project]](timeout) - assert(cachedProjectBefore.isDefined) - // create an ontology - appActor ! CreateOntologyRequestV2( - ontologyName = "foo-two", - projectIri = imagesProjectIri, - label = "The foo-two ontology", - apiRequestID = UUID.randomUUID, - requestingUser = imagesUser, - ) - expectMsgType[ReadOntologyMetadataV2](timeout) - // ensure that the project is no longer cached - appActor ! CacheServiceGetProjectADM(ProjectIdentifierADM.IriIdentifier.unsafeFrom(imagesProjectIri.toString)) - val cachedProject = expectMsgType[Option[Project]](timeout) - assert(cachedProject.isEmpty) - } - "change the label in the metadata of 'foo'" in { val newLabel = "The modified foo ontology" diff --git a/integration/src/test/scala/org/knora/webapi/routing/AuthenticatorSpec.scala b/integration/src/test/scala/org/knora/webapi/routing/AuthenticatorSpec.scala index de16a4de09..9a75fe63ae 100644 --- a/integration/src/test/scala/org/knora/webapi/routing/AuthenticatorSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/routing/AuthenticatorSpec.scala @@ -15,12 +15,10 @@ import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.v2.routing.authenticationmessages.CredentialsIdentifier import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraJWTTokenCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraPasswordCredentialsV2 -import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA -import org.knora.webapi.util.cache.CacheUtil object AuthenticatorSpec { private val rootUser = SharedTestDataADM.rootUser @@ -74,7 +72,7 @@ class AuthenticatorSpec extends CoreSpec with ImplicitSender with PrivateMethodT .run(for { token <- createJwtTokenString(testUserAdmFromIri("http://rdfh.ch/users/X-T8IkfQTKa86UWuISpbOA")) tokenCreds = KnoraJWTTokenCredentialsV2(token) - _ = CacheUtil.put(AUTHENTICATION_INVALIDATION_CACHE_NAME, tokenCreds.jwtToken, tokenCreds.jwtToken) + _ <- ZIO.serviceWith[InvalidTokenCache](_.put(tokenCreds.jwtToken)) result <- ZIO.serviceWithZIO[Authenticator](_.authenticateCredentialsV2(Some(tokenCreds))) } yield result) assertFailsWithA[BadCredentialsException](actual) diff --git a/integration/src/test/scala/org/knora/webapi/store/cache/CacheServiceSpec.scala b/integration/src/test/scala/org/knora/webapi/store/cache/CacheServiceSpec.scala deleted file mode 100644 index 0edefbd7b3..0000000000 --- a/integration/src/test/scala/org/knora/webapi/store/cache/CacheServiceSpec.scala +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.store.cache - -import zio.ZIO -import zio.test._ - -import org.knora.webapi.messages.admin.responder.projectsmessages.Project -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ -import org.knora.webapi.sharedtestdata.SharedTestDataADM -import org.knora.webapi.slice.admin.domain.model._ - -object CacheServiceSpec extends ZIOSpecDefault { - - private val user: User = SharedTestDataADM.imagesUser01 - - private val userWithApostrophe = User( - id = "http://rdfh.ch/users/aaaaaab71e7b0e01", - username = "user_with_apostrophe", - email = "userWithApostrophe@example.org", - givenName = """M\\"Given 'Name""", - familyName = """M\\tFamily Name""", - status = true, - lang = "en", - ) - - val project: Project = SharedTestDataADM.imagesProject - - private val cacheService = ZIO.serviceWithZIO[CacheService] - - private val userTests = suite("User")( - test("successfully store a user and retrieve by UserIri") { - for { - _ <- cacheService(_.putUser(user)) - retrievedUser <- cacheService(_.getUserByIri(UserIri.unsafeFrom(user.id))) - } yield assertTrue(retrievedUser.contains(user)) - }, - test("successfully store a user and retrieve by Username")( - for { - _ <- cacheService(_.putUser(user)) - retrievedUser <- cacheService(_.getUserByUsername(Username.unsafeFrom(user.username))) - } yield assertTrue(retrievedUser.contains(user)), - ), - test("successfully store a user and retrieve by Email")( - for { - _ <- cacheService(_.putUser(user)) - retrievedUser <- cacheService(_.getUserByEmail(Email.unsafeFrom(user.email))) - } yield assertTrue(retrievedUser.contains(user)), - ), - test("successfully store and retrieve a user with special characters in their name")( - for { - _ <- cacheService(_.putUser(userWithApostrophe)) - retrievedUser <- cacheService(_.getUserByIri(userWithApostrophe.userIri)) - } yield assertTrue(retrievedUser.contains(userWithApostrophe)), - ), - test("given when successfully stored and invalidated a user then the cache should not contain the user")( - for { - _ <- cacheService(_.putUser(userWithApostrophe)) - _ <- cacheService(_.invalidateUser(userWithApostrophe.userIri)) - userByIri <- cacheService(_.getUserByIri(userWithApostrophe.userIri)) - userByEmail <- cacheService(_.getUserByEmail(Email.unsafeFrom(userWithApostrophe.email))) - userByUsername <- cacheService(_.getUserByUsername(Username.unsafeFrom(userWithApostrophe.username))) - } yield assertTrue(userByIri.isEmpty, userByEmail.isEmpty, userByUsername.isEmpty), - ), - ) - - private val projectTests = suite("ProjectADM")( - test("successfully store a project and retrieve by IRI")( - for { - _ <- cacheService(_.putProjectADM(project)) - retrievedProject <- cacheService(_.getProjectADM(IriIdentifier.from(project.projectIri))) - } yield assertTrue(retrievedProject.contains(project)), - ), - test("successfully store a project and retrieve by SHORTCODE")( - for { - _ <- cacheService(_.putProjectADM(project)) - retrievedProject <- cacheService(_.getProjectADM(ShortcodeIdentifier.from(project.getShortcode))) - } yield assertTrue(retrievedProject.contains(project)), - ), - test("successfully store a project and retrieve by SHORTNAME")( - for { - _ <- cacheService(_.putProjectADM(project)) - retrievedProject <- cacheService(_.getProjectADM(ShortnameIdentifier.from(project.getShortname))) - } yield assertTrue(retrievedProject.contains(project)), - ), - ) - - private val clearCacheSuite = suite("clearCache")( - test("successfully clears the cache")( - for { - _ <- cacheService(_.putUser(user)) - _ <- cacheService(_.putProjectADM(project)) - _ <- cacheService(_.clearCache()) - userByIri <- cacheService(_.getUserByIri(user.userIri)) - userByUsername <- cacheService(_.getUserByUsername(user.getUsername)) - userByEmail <- cacheService(_.getUserByEmail(user.getEmail)) - projectByIri <- cacheService(_.getProjectADM(IriIdentifier.from(project.projectIri))) - projectByShortcode <- cacheService(_.getProjectADM(ShortcodeIdentifier.from(project.getShortcode))) - projectByShortname <- cacheService(_.getProjectADM(ShortnameIdentifier.from(project.getShortname))) - } yield assertTrue( - userByIri.isEmpty, - userByUsername.isEmpty, - userByEmail.isEmpty, - projectByIri.isEmpty, - projectByShortcode.isEmpty, - projectByShortname.isEmpty, - ), - ), - ) - - def spec: Spec[Any, Throwable] = - suite("CacheServiceLive")(userTests, projectTests, clearCacheSuite).provide(CacheService.layer) -} diff --git a/integration/src/test/scala/org/knora/webapi/util/cache/CacheUtilSpec.scala b/integration/src/test/scala/org/knora/webapi/util/cache/CacheUtilSpec.scala deleted file mode 100644 index 8e21e61651..0000000000 --- a/integration/src/test/scala/org/knora/webapi/util/cache/CacheUtilSpec.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.util.cache - -import org.knora.webapi.CoreSpec -import org.knora.webapi.routing.Authenticator -import org.knora.webapi.sharedtestdata.SharedTestDataADM2 - -class CacheUtilSpec extends CoreSpec { - - private val cacheName = Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME - private val sessionId = java.lang.System.currentTimeMillis().toString - - "Caching" should { - - "allow to set and get the value " in { - CacheUtil.removeAllCaches() - CacheUtil.createCaches(appConfig.cacheConfigs) - CacheUtil.put(cacheName, sessionId, SharedTestDataADM2.rootUser) - CacheUtil.get(cacheName, sessionId) should be(Some(SharedTestDataADM2.rootUser)) - } - - "return none if key is not found " in { - CacheUtil.removeAllCaches() - CacheUtil.createCaches(appConfig.cacheConfigs) - CacheUtil.get(cacheName, 213.toString) should be(None) - } - } -} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a7369c81a8..33690eac0b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -84,7 +84,7 @@ object Dependencies { val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15to18" % "1.78" // caching - val ehcache = "net.sf.ehcache" % "ehcache" % "2.10.9.2" + val ehcache = "org.ehcache" % "ehcache" % "3.10.8" // other val diff = "com.sksamuel.diff" % "diff" % "1.1.11" diff --git a/webapi/src/main/resources/application.conf b/webapi/src/main/resources/application.conf index fee7817fb1..90c9f97995 100644 --- a/webapi/src/main/resources/application.conf +++ b/webapi/src/main/resources/application.conf @@ -364,59 +364,6 @@ app { project-icons-basepath = "project-icons/" } - // http://www.ehcache.org/apidocs/2.10/index.html - caches = [ - { - cache-name = "ontologyCache" - max-elements-in-memory = 0 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 0 - time-to-idle-seconds = 0 - }, - { - cache-name = "userProfileCache" - max-elements-in-memory = 0 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 600 - time-to-idle-seconds = 0 - }, - { - cache-name = "userADMCache" - max-elements-in-memory = 0 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 600 - time-to-idle-seconds = 0 - } - { - cache-name = "authenticationInvalidationCache" - max-elements-in-memory = 0 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 2592000 - time-to-idle-seconds = 0 - }, - { - cache-name = "mappingCache" - max-elements-in-memory = 0 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 600 - time-to-idle-seconds = 0 - }, - { - cache-name = "xsltCache" - max-elements-in-memory = 100 - overflow-to-disk = false - eternal = true - time-to-live-seconds = 600 - time-to-idle-seconds = 0 - } - - ] - tmp-datadir = "/tmp/webapi_tmp/" // dir must exist on disk! datadir = "/tmp/webapi/" // dir must exist on disk! diff --git a/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala b/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala index ccc1dad06a..13fcce95d1 100644 --- a/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala +++ b/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala @@ -13,8 +13,6 @@ import zio.config.typesafe._ import java.time.Duration -import org.knora.webapi.util.cache.CacheUtil - /** * Represents the configuration as defined in application.conf. */ @@ -31,7 +29,6 @@ final case class AppConfig( sipi: Sipi, ark: Ark, salsah1: Salsah1, - caches: List[CacheConfig], tmpDatadir: String, datadir: String, maxResultsPerSearchResultPage: Int, @@ -39,23 +36,12 @@ final case class AppConfig( gui: Gui, routesToReject: List[String], triplestore: Triplestore, - cacheService: CacheService, clientTestDataService: ClientTestDataService, instrumentationServerConfig: InstrumentationServerConfig, jwt: JwtConfig, dspIngest: DspIngestConfig, ) { val tmpDataDirPath: zio.nio.file.Path = zio.nio.file.Path(this.tmpDatadir) - val cacheConfigs: Seq[org.knora.webapi.util.cache.CacheUtil.KnoraCacheConfig] = caches.map { c => - CacheUtil.KnoraCacheConfig( - c.cacheName, - c.maxElementsInMemory, - c.overflowToDisk, - c.eternal, - c.timeToLiveSeconds, - c.timeToIdleSeconds, - ) - } } final case class JwtConfig(secret: String, expiration: Duration, issuer: Option[String]) { def issuerAsString(): String = issuer.getOrElse( @@ -132,15 +118,6 @@ final case class Salsah1( projectIconsBasepath: String, ) -final case class CacheConfig( - cacheName: String, - maxElementsInMemory: Int, - overflowToDisk: Boolean, - eternal: Boolean, - timeToLiveSeconds: Int, - timeToIdleSeconds: Int, -) - final case class V2( resourcesSequence: ResourcesSequence, fulltextSearch: FulltextSearch, @@ -189,10 +166,6 @@ final case class Fuseki( queryLoggingThreshold: Duration = Duration.ofMillis(1000), ) -final case class CacheService( - enabled: Boolean, -) - final case class ClientTestDataService( collectClientTestData: Boolean, ) diff --git a/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala b/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala index 19f0f780b4..44bbef5be4 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala @@ -18,7 +18,6 @@ import org.knora.webapi.store.iiif.api.SipiService import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.domain.TriplestoreStatus import org.knora.webapi.store.triplestore.upgrade.RepositoryUpdater -import org.knora.webapi.util.cache.CacheUtil /** * The application bootstrapper @@ -61,19 +60,6 @@ final case class AppServer( _ <- state.set(AppState.RepositoryUpToDate) } yield () - /** - * Initiates building of all caches - */ - private val buildAllCaches: UIO[Unit] = - for { - _ <- state.set(AppState.CreatingCaches) - _ <- ZIO.attempt { - CacheUtil.removeAllCaches() - CacheUtil.createCaches(appConfig.cacheConfigs) - }.orDie - _ <- state.set(AppState.CachesReady) - } yield () - /** * Initiates population of the ontology caches if `requiresRepository` is `true` * @@ -124,7 +110,6 @@ final case class AppServer( _ <- ZIO.logInfo("=> Startup checks initiated") _ <- checkTriplestoreService _ <- upgradeRepository(requiresAdditionalRepositoryChecks) - _ <- buildAllCaches _ <- populateOntologyCaches(requiresAdditionalRepositoryChecks) _ <- checkIIIFService(requiresIIIFService) _ <- ZIO.logInfo("=> Startup checks finished") @@ -137,12 +122,12 @@ final case class AppServer( object AppServer { type AppServerEnvironment = - State & TriplestoreService & RepositoryUpdater & actor.ActorSystem & OntologyCache & SipiService & HttpServer & AppConfig + actor.ActorSystem & AppConfig & HttpServer & OntologyCache & RepositoryUpdater & SipiService & State & TriplestoreService /** * Initializes the AppServer instance with the required services */ - def init(): ZIO[AppServerEnvironment, Nothing, AppServer] = + def init(): URIO[AppServerEnvironment, AppServer] = for { state <- ZIO.service[State] ts <- ZIO.service[TriplestoreService] @@ -158,7 +143,7 @@ object AppServer { /** * The live AppServer */ - val make: ZIO[AppServerEnvironment, Nothing, Unit] = + val make: URIO[AppServerEnvironment, Unit] = for { appServer <- AppServer.init() _ <- appServer.start(requiresAdditionalRepositoryChecks = true, requiresIIIFService = true).orDie diff --git a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala index 647b0169d0..4345bc4aa7 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -40,6 +40,7 @@ import org.knora.webapi.slice.admin.api.service.UserRestService import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.common.api._ import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.slice.infrastructure.api.ManagementEndpoints @@ -55,9 +56,6 @@ import org.knora.webapi.slice.resourceinfo.ResourceInfoLayers import org.knora.webapi.slice.resourceinfo.domain.IriConverter import org.knora.webapi.slice.search.api.SearchApiRoutes import org.knora.webapi.slice.search.api.SearchEndpoints -import org.knora.webapi.store.cache.CacheService -import org.knora.webapi.store.cache.CacheServiceRequestMessageHandler -import org.knora.webapi.store.cache.CacheServiceRequestMessageHandlerLive import org.knora.webapi.store.iiif.IIIFRequestMessageHandler import org.knora.webapi.store.iiif.IIIFRequestMessageHandlerLive import org.knora.webapi.store.iiif.api.SipiService @@ -81,7 +79,7 @@ object LayersLive { AssetPermissionsResponder & Authenticator & AuthorizationRestService & - CacheServiceRequestMessageHandler & + CacheManager & CardinalityHandler & ConstructResponseUtilV2 & GravsearchTypeInspectionRunner & @@ -92,6 +90,7 @@ object LayersLive { IIIFRequestMessageHandler & InferenceOptimizationService & InstrumentationServerConfig & + InvalidTokenCache & IriConverter & JwtService & KnoraUserToUserConverter & @@ -144,8 +143,6 @@ object LayersLive { AuthenticatorLive.layer, AuthorizationRestService.layer, BaseEndpoints.layer, - CacheService.layer, - CacheServiceRequestMessageHandlerLive.layer, CardinalityHandlerLive.layer, CardinalityService.layer, ConstructResponseUtilV2Live.layer, @@ -157,6 +154,7 @@ object LayersLive { HttpServer.layer, IIIFRequestMessageHandlerLive.layer, InferenceOptimizationService.layer, + InvalidTokenCache.layer, IriConverter.layer, IriService.layer, JwtServiceLive.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala b/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala index d4aca1f261..3666ab7c49 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala @@ -10,22 +10,15 @@ package org.knora.webapi.core.domain */ sealed trait AppState object AppState { - case object Stopped extends AppState - case object StartingUp extends AppState - case object WaitingForTriplestore extends AppState - case object TriplestoreReady extends AppState - case object UpdatingRepository extends AppState - case object RepositoryUpToDate extends AppState - case object CreatingCaches extends AppState - case object CachesReady extends AppState - case object UpdatingSearchIndex extends AppState - case object SearchIndexReady extends AppState - case object LoadingOntologies extends AppState - case object OntologiesReady extends AppState - case object WaitingForIIIFService extends AppState - case object IIIFServiceReady extends AppState - case object WaitingForCacheService extends AppState - case object CacheServiceReady extends AppState - case object MaintenanceMode extends AppState - case object Running extends AppState + case object Stopped extends AppState + case object WaitingForTriplestore extends AppState + case object TriplestoreReady extends AppState + case object UpdatingRepository extends AppState + case object RepositoryUpToDate extends AppState + case object LoadingOntologies extends AppState + case object OntologiesReady extends AppState + case object WaitingForIIIFService extends AppState + case object IIIFServiceReady extends AppState + case object MaintenanceMode extends AppState + case object Running extends AppState } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/store/cacheservicemessages/CacheServiceMessages.scala b/webapi/src/main/scala/org/knora/webapi/messages/store/cacheservicemessages/CacheServiceMessages.scala deleted file mode 100644 index 3d420b24d0..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/messages/store/cacheservicemessages/CacheServiceMessages.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.messages.store.cacheservicemessages - -import org.knora.webapi.core.RelayedMessage -import org.knora.webapi.messages.admin.responder.projectsmessages.Project -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM -import org.knora.webapi.messages.store.StoreRequest - -sealed trait CacheServiceRequest extends StoreRequest with RelayedMessage - -/** - * Message equesting to write project to cache. - */ -case class CacheServicePutProjectADM(value: Project) extends CacheServiceRequest - -/** - * Message requesting to retrieve project from cache. - */ -case class CacheServiceGetProjectADM(identifier: ProjectIdentifierADM) extends CacheServiceRequest diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index a7b6cddece..a6fb5b476f 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -41,7 +41,6 @@ import org.knora.webapi.slice.ontology.domain.service.CardinalityService import org.knora.webapi.slice.ontology.domain.service.OntologyRepo import org.knora.webapi.slice.ontology.repo.service.OntologyCache import org.knora.webapi.slice.ontology.repo.service.OntologyCache.ONTOLOGY_CACHE_LOCK_IRI -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update @@ -76,7 +75,6 @@ final case class OntologyResponderV2Live( ontologyRepo: OntologyRepo, knoraProjectService: KnoraProjectService, triplestoreService: TriplestoreService, - cacheService: CacheService, )(implicit val stringFormatter: StringFormatter) extends OntologyResponderV2 with MessageHandler @@ -511,11 +509,6 @@ final case class OntologyResponderV2Live( ReadOntologyV2(ontologyMetadata = unescapedNewMetadata), ) - projectIri <- ZIO - .fromEither(ProjectIri.from(createOntologyRequest.projectIri.toString)) - .mapError(BadRequestException.apply) - _ <- cacheService.invalidateProjectADM(projectIri) - } yield ReadOntologyMetadataV2(ontologies = Set(unescapedNewMetadata)) for { @@ -1865,15 +1858,6 @@ final case class OntologyResponderV2Live( _ <- triplestoreService.query(Update(sparql.v2.txt.deleteOntology(internalOntologyIri))) // Remove the ontology from the cache. _ <- ontologyCache.deleteOntology(internalOntologyIri) - // invalidate the project cache - projectIri <- - ZIO - .fromOption(ontology.ontologyMetadata.projectIri) - .mapBoth( - _ => InconsistentRepositoryDataException(s"Project IRI not found for ontology $internalOntologyIri"), - iri => ProjectIri.unsafeFrom(iri.toString), - ) - _ <- cacheService.invalidateProjectADM(projectIri) // Check that the ontology has been deleted. maybeOntologyMetadata <- ontologyHelpers.loadOntologyMetadata(internalOntologyIri) @@ -2968,7 +2952,7 @@ final case class OntologyResponderV2Live( object OntologyResponderV2Live { val layer: URLayer[ AppConfig & CardinalityHandler & CardinalityService & IriService & KnoraProjectService & MessageRelay & OntologyCache & - OntologyHelpers & OntologyRepo & StringFormatter & TriplestoreService & CacheService, + OntologyHelpers & OntologyRepo & StringFormatter & TriplestoreService, OntologyResponderV2, ] = ZLayer.fromZIO { for { @@ -2982,8 +2966,7 @@ object OntologyResponderV2Live { or <- ZIO.service[OntologyRepo] sf <- ZIO.service[StringFormatter] ts <- ZIO.service[TriplestoreService] - cache <- ZIO.service[CacheService] - responder = OntologyResponderV2Live(ac, ch, cs, is, oc, oh, or, kr, ts, cache)(sf) + responder = OntologyResponderV2Live(ac, ch, cs, is, oc, oh, or, kr, ts)(sf) _ <- ZIO.serviceWithZIO[MessageRelay](_.subscribe(responder)) } yield responder } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala index 40090c8158..a93209e574 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala @@ -48,13 +48,14 @@ import org.knora.webapi.responders.Responder import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.ProjectService +import org.knora.webapi.slice.infrastructure.CacheManager +import org.knora.webapi.slice.infrastructure.EhCache import org.knora.webapi.slice.ontology.domain.model.Cardinality.AtLeastOne import org.knora.webapi.slice.ontology.domain.model.Cardinality.ExactlyOne import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Construct import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update import org.knora.webapi.util.FileUtil -import org.knora.webapi.util.cache.CacheUtil /** * Responds to requests relating to the creation of mappings from XML elements @@ -67,12 +68,13 @@ final case class StandoffResponderV2( constructResponseUtilV2: ConstructResponseUtilV2, standoffTagUtilV2: StandoffTagUtilV2, projectService: ProjectService, + xsltCache: EhCache[String, String], + mappingCache: EhCache[String, MappingXMLtoStandoff], )(implicit val stringFormatter: StringFormatter) extends MessageHandler with LazyLogging { - private val xmlMimeTypes = Set("text/xml", "application/xml") - private val xsltCacheName = "xsltCache" + private val xmlMimeTypes = Set("text/xml", "application/xml") override def isResponsibleFor(message: ResponderRequest): Boolean = message.isInstanceOf[StandoffResponderRequestV2] @@ -171,7 +173,7 @@ final case class StandoffResponderV2( // check if the XSL transformation is in the cache xsltFileUrl <- recoveredXsltUrlFuture - xsltMaybe: Option[String] = CacheUtil.get[String](cacheName = xsltCacheName, key = xsltFileUrl) + xsltMaybe: Option[String] = xsltCache.get(xsltFileUrl) xslt <- if (xsltMaybe.nonEmpty) { @@ -188,7 +190,7 @@ final case class StandoffResponderV2( senderName = this.getClass.getName, ), ) - _ = CacheUtil.put(cacheName = xsltCacheName, key = xsltFileUrl, value = response.content) + _ = xsltCache.put(xsltFileUrl, response.content) } yield response.content } @@ -260,7 +262,7 @@ final case class StandoffResponderV2( requestingUser = requestingUser, ) } yield Some(transIri) - case _ => ZIO.attempt(None) + case _ => ZIO.none } // create a collection of a all elements mappingElement @@ -490,7 +492,7 @@ final case class StandoffResponderV2( // check if the given project IRI represents an actual project projectId <- ZIO - .fromEither(KnoraProject.ProjectIri.from(projectIri.toString())) + .fromEither(KnoraProject.ProjectIri.from(projectIri.toString)) .mapError(BadRequestException.apply) project <- projectService .findById(projectId) @@ -664,11 +666,6 @@ final case class StandoffResponderV2( } - /** - * The name of the mapping cache. - */ - private val mappingCacheName = "mappingCache" - /** * Gets a mapping either from the cache or by making a request to the triplestore. * @@ -682,7 +679,7 @@ final case class StandoffResponderV2( ): Task[GetMappingResponseV2] = { val mappingFuture: Task[GetMappingResponseV2] = - CacheUtil.get[MappingXMLtoStandoff](cacheName = mappingCacheName, key = mappingIri) match { + mappingCache.get(mappingIri) match { case Some(mapping: MappingXMLtoStandoff) => for { @@ -821,7 +818,7 @@ final case class StandoffResponderV2( ) // add the mapping to the cache - _ = CacheUtil.put(cacheName = mappingCacheName, key = mappingIri, value = mappingXMLToStandoff) + _ = mappingCache.put(mappingIri, mappingXMLToStandoff) } yield mappingXMLToStandoff @@ -995,8 +992,10 @@ object StandoffResponderV2 { cru <- ZIO.service[ConstructResponseUtilV2] stu <- ZIO.service[StandoffTagUtilV2] ps <- ZIO.service[ProjectService] + xc <- ZIO.serviceWithZIO[CacheManager](_.createCache[String, String]("xsltCache")) + mc <- ZIO.serviceWithZIO[CacheManager](_.createCache[String, MappingXMLtoStandoff]("mappingCache")) sf <- ZIO.service[StringFormatter] - handler <- mr.subscribe(StandoffResponderV2(ac, mr, ts, cru, stu, ps)(sf)) + handler <- mr.subscribe(StandoffResponderV2(ac, mr, ts, cru, stu, ps, xc, mc)(sf)) } yield handler } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala index e4a0b18cc0..818dc96a7c 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala @@ -20,14 +20,12 @@ import java.util.Base64 import dsp.errors.AuthenticationException import dsp.errors.BadCredentialsException import org.knora.webapi.config.AppConfig -import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.usersmessages._ import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraJWTTokenCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraPasswordCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraSessionCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages._ -import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME import org.knora.webapi.routing.Authenticator.BAD_CRED_NONE_SUPPLIED import org.knora.webapi.routing.Authenticator.BAD_CRED_NOT_VALID import org.knora.webapi.slice.admin.domain.model.Email @@ -38,7 +36,6 @@ import org.knora.webapi.slice.admin.domain.service.KnoraUserRepo import org.knora.webapi.slice.admin.domain.service.PasswordService import org.knora.webapi.slice.admin.domain.service.UserService import org.knora.webapi.slice.infrastructure.JwtService -import org.knora.webapi.util.cache.CacheUtil /** * This trait is used in routes that need authentication support. It provides methods that use the [[RequestContext]] @@ -136,8 +133,6 @@ trait Authenticator { object Authenticator { val BAD_CRED_NONE_SUPPLIED = "bad credentials: none found" val BAD_CRED_NOT_VALID = "bad credentials: not valid" - - val AUTHENTICATION_INVALIDATION_CACHE_NAME = "authenticationInvalidationCache" } final case class AuthenticatorLive( @@ -145,8 +140,8 @@ final case class AuthenticatorLive( private val userService: UserService, private val jwtService: JwtService, private val passwordService: PasswordService, -)(private implicit val stringFormatter: StringFormatter) - extends Authenticator { + private val cache: InvalidTokenCache, +) extends Authenticator { /** * Checks if the provided credentials are valid, and if so returns a JWT token for the client to save. @@ -276,7 +271,7 @@ final case class AuthenticatorLive( credentials match { case Some(KnoraSessionCredentialsV2(sessionToken)) => - CacheUtil.put(AUTHENTICATION_INVALIDATION_CACHE_NAME, sessionToken, sessionToken) + cache.put(sessionToken) HttpResponse( headers = List( @@ -302,7 +297,7 @@ final case class AuthenticatorLive( ), ) case Some(KnoraJWTTokenCredentialsV2(jwtToken)) => - CacheUtil.put(AUTHENTICATION_INVALIDATION_CACHE_NAME, jwtToken, jwtToken) + cache.put(jwtToken) HttpResponse( headers = List( diff --git a/webapi/src/main/scala/org/knora/webapi/routing/InvalidTokenCache.scala b/webapi/src/main/scala/org/knora/webapi/routing/InvalidTokenCache.scala new file mode 100644 index 0000000000..7f016ffc06 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/routing/InvalidTokenCache.scala @@ -0,0 +1,23 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.routing + +import zio.ZIO +import zio.ZLayer + +import org.knora.webapi.slice.infrastructure.CacheManager +import org.knora.webapi.slice.infrastructure.EhCache + +final case class InvalidTokenCache(cache: EhCache[String, String]) { + def put(token: String): Unit = cache.put(token, "") + def contains(token: String): Boolean = cache.get(token).isDefined +} + +object InvalidTokenCache { + val layer = ZLayer.fromZIO( + ZIO.serviceWithZIO[CacheManager](_.createCache[String, String]("invalidTokenCache").map(InvalidTokenCache.apply)), + ) +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/AdminModule.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/AdminModule.scala index 77acf176b9..92e6e0a909 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/AdminModule.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/AdminModule.scala @@ -13,13 +13,12 @@ import org.knora.webapi.slice.admin.domain.AdminDomainModule import org.knora.webapi.slice.admin.repo.AdminRepoModule import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper import org.knora.webapi.slice.ontology.domain.service.OntologyRepo -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService object AdminModule { type Dependencies = - AppConfig & CacheService & IriService & OntologyRepo & PredicateObjectMapper & TriplestoreService + AppConfig & IriService & OntologyRepo & PredicateObjectMapper & TriplestoreService type Provided = AdminDomainModule.Provided diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiModule.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiModule.scala index 4d7278d05e..bf14337dbc 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiModule.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiModule.scala @@ -30,24 +30,47 @@ import org.knora.webapi.slice.admin.domain.service.ProjectExportService import org.knora.webapi.slice.admin.domain.service.ProjectImportService import org.knora.webapi.slice.admin.domain.service.ProjectService import org.knora.webapi.slice.admin.domain.service.UserService -import org.knora.webapi.slice.common.api.KnoraResponseRenderer import org.knora.webapi.slice.common.api._ +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.ontology.repo.service.OntologyCache -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService object AdminApiModule { type Dependencies = - AppConfig & AdministrativePermissionService & AssetPermissionsResponder & AuthorizationRestService & BaseEndpoints & CacheService & - GroupsResponderADM & GroupService & HandlerMapper & KnoraProjectService & KnoraResponseRenderer & - KnoraUserService & KnoraUserToUserConverter & ListsResponder & MaintenanceService & OntologyCache & - PasswordService & PermissionsResponder & ProjectExportService & ProjectImportService & ProjectService & - TapirToPekkoInterpreter & TriplestoreService & UserService & UsersResponder + AppConfig & + AdministrativePermissionService & + AssetPermissionsResponder & + AuthorizationRestService & + BaseEndpoints & + CacheManager & + GroupsResponderADM & + GroupService & + HandlerMapper & + KnoraProjectService & + KnoraResponseRenderer & + KnoraUserService & + KnoraUserToUserConverter & + ListsResponder & + MaintenanceService & + OntologyCache & + PasswordService & + PermissionsResponder & + ProjectExportService & + ProjectImportService & + ProjectService & + TapirToPekkoInterpreter & + TriplestoreService & + UserService & + UsersResponder - type Provided = AdminApiEndpoints & AdminApiRoutes & + type Provided = AdminApiEndpoints & + AdminApiRoutes & // the `*RestService`s are only exposed for the integration tests - GroupRestService & UserRestService & ProjectRestService & PermissionRestService + GroupRestService & + UserRestService & + ProjectRestService & + PermissionRestService val layer: ZLayer[Dependencies, Nothing, Provided] = ZLayer.makeSome[Dependencies, Provided]( diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/StoreRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/StoreRestService.scala index ea4a8e7432..86ef63b77a 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/StoreRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/StoreRestService.scala @@ -12,15 +12,15 @@ import org.knora.webapi.config.AppConfig import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.messages.util.KnoraSystemInstances.Users.SystemUser import org.knora.webapi.slice.admin.api.MessageResponse +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.ontology.repo.service.OntologyCache -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService final case class StoreRestService( appConfig: AppConfig, - cacheService: CacheService, triplestoreService: TriplestoreService, ontologyCache: OntologyCache, + cacheManager: CacheManager, ) { /** @@ -43,7 +43,7 @@ final case class StoreRestService( _ <- ZIO.logWarning(s"Resetting triplestore content with ${rdfDataObjects.map(_.name).mkString(", ")}") _ <- triplestoreService.resetTripleStoreContent(rdfDataObjects, prependDefaults).logError _ <- ontologyCache.loadOntologies(SystemUser).logError - _ <- cacheService.clearCache().logError + _ = cacheManager.clearAll() } yield MessageResponse("success") } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala index a41f695f44..6d215fe884 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala @@ -17,8 +17,8 @@ import org.knora.webapi.slice.admin.domain.service.ProjectService import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.admin.repo.AdminRepoModule import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.ontology.domain.service.OntologyRepo -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService object AdminDomainModule { @@ -26,7 +26,6 @@ object AdminDomainModule { type Dependencies = AppConfig & AdminRepoModule.Provided & - CacheService & IriService & OntologyRepo & PredicateObjectMapper & @@ -34,6 +33,7 @@ object AdminDomainModule { type Provided = AdministrativePermissionService & + CacheManager & GroupService & KnoraGroupService & KnoraProjectService & diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectService.scala index afacdee130..fa4ee7c804 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectService.scala @@ -21,9 +21,8 @@ import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortname import org.knora.webapi.slice.admin.domain.model.RestrictedView -import org.knora.webapi.store.cache.CacheService -final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo, cacheService: CacheService) { +final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo) { def findById(id: ProjectIri): Task[Option[KnoraProject]] = knoraProjectRepo.findById(id) def existsById(id: ProjectIri): Task[Boolean] = knoraProjectRepo.existsById(id) def findById(id: ProjectIdentifierADM): Task[Option[KnoraProject]] = knoraProjectRepo.findById(id) @@ -54,7 +53,6 @@ final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo, cacheSe RestrictedView.default, ) project <- knoraProjectRepo.save(project) - _ <- cacheService.clearCache() } yield project private def toNonEmptyChunk(descriptions: List[Description]) = @@ -98,7 +96,6 @@ final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo, cacheSe selfjoin = updateReq.selfjoin.getOrElse(project.selfjoin), ), ) - _ <- cacheService.clearCache() } yield updated } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserService.scala index 48cb15fb87..5895ff4933 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserService.scala @@ -34,18 +34,16 @@ import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.service.KnoraUserService.Errors.UserServiceError import org.knora.webapi.slice.admin.domain.service.KnoraUserService.UserChangeRequest -import org.knora.webapi.store.cache.CacheService case class KnoraUserService( private val userRepo: KnoraUserRepo, private val iriService: IriService, private val passwordService: PasswordService, - private val cacheService: CacheService, ) { def findById(userIri: UserIri): Task[Option[KnoraUser]] = userRepo.findById(userIri) def findByEmail(email: Email): Task[Option[KnoraUser]] = userRepo.findByEmail(email) def findByUsername(username: Username): Task[Option[KnoraUser]] = userRepo.findByUsername(username) - def findAll(): Task[Seq[KnoraUser]] = userRepo.findAll() + def findAllRegularUsers(): Task[Seq[KnoraUser]] = userRepo.findAll().map(_.filter(_.id.isRegularUser)) def findByProjectMembership(project: KnoraProject): Task[Chunk[KnoraUser]] = userRepo.findByProjectMembership(project.id) def findByProjectAdminMembership(project: KnoraProject): Task[Chunk[KnoraUser]] = diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserToUserConverter.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserToUserConverter.scala index 8ed2da06ca..16386eec31 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserToUserConverter.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraUserToUserConverter.scala @@ -26,6 +26,9 @@ final case class KnoraUserToUserConverter( private val administrativePermissionService: AdministrativePermissionService, ) { + def toUser(kUser: Option[KnoraUser]): Task[Option[User]] = ZIO.foreach(kUser)(toUser) + def toUser(kUser: Seq[KnoraUser]): Task[Seq[User]] = ZIO.foreach(kUser)(toUser) + def toUser(kUser: KnoraUser): Task[User] = for { projects <- projectsService.findByIds(kUser.isInProject) groups <- groupService.findByIds(kUser.isInGroup) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectService.scala index 10053f458d..f2c1d06621 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectService.scala @@ -20,12 +20,10 @@ import org.knora.webapi.slice.admin.domain.model.KnoraProject._ import org.knora.webapi.slice.admin.domain.model.RestrictedView import org.knora.webapi.slice.ontology.domain.service.OntologyRepo import org.knora.webapi.slice.resourceinfo.domain.InternalIri -import org.knora.webapi.store.cache.CacheService final case class ProjectService( private val ontologyRepo: OntologyRepo, private val knoraProjectService: KnoraProjectService, - private val cacheService: CacheService, ) { def findAllRegularProjects: Task[Chunk[Project]] = knoraProjectService @@ -45,14 +43,7 @@ final case class ProjectService( def findByIds(id: Seq[ProjectIri]): Task[Seq[Project]] = ZIO.foreach(id)(findById).map(_.flatten) private def findByProjectIdentifier(projectId: ProjectIdentifierADM): Task[Option[Project]] = - cacheService.getProjectADM(projectId).flatMap { - case Some(project) => ZIO.some(project) - case None => - knoraProjectService.findById(projectId).flatMap(ZIO.foreach(_)(toProject)).tap { - case Some(prj) => cacheService.putProjectADM(prj) - case None => ZIO.unit - } - } + knoraProjectService.findById(projectId).flatMap(ZIO.foreach(_)(toProject)) private def toProject(knoraProject: KnoraProject): Task[Project] = for { ontologies <- ontologyRepo.findByProject(knoraProject).map(_.map(_.ontologyMetadata.ontologyIri.toIri)) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala index fd32810eb6..3c0d009bde 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala @@ -6,57 +6,36 @@ package org.knora.webapi.slice.admin.domain.service import zio.Task -import zio.ZIO import zio.ZLayer import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.KnoraProject -import org.knora.webapi.slice.admin.domain.model.KnoraUser import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.admin.domain.model.Username -import org.knora.webapi.store.cache.CacheService final case class UserService( private val knoraUserService: KnoraUserService, - private val userToKnoraUserConverter: KnoraUserToUserConverter, - private val cacheService: CacheService, + private val userConverter: KnoraUserToUserConverter, ) { def findUserByIri(iri: UserIri): Task[Option[User]] = - fromCacheOrRepo(iri, cacheService.getUserByIri, knoraUserService.findById) + knoraUserService.findById(iri).flatMap(userConverter.toUser) def findUserByEmail(email: Email): Task[Option[User]] = - fromCacheOrRepo(email, cacheService.getUserByEmail, knoraUserService.findByEmail) + knoraUserService.findByEmail(email).flatMap(userConverter.toUser) def findUserByUsername(username: Username): Task[Option[User]] = - fromCacheOrRepo(username, cacheService.getUserByUsername, knoraUserService.findByUsername) + knoraUserService.findByUsername(username).flatMap(userConverter.toUser) def findByProjectMembership(project: KnoraProject): Task[Seq[User]] = - knoraUserService.findByProjectMembership(project).flatMap(ZIO.foreach(_)(userToKnoraUserConverter.toUser)) + knoraUserService.findByProjectMembership(project).flatMap(userConverter.toUser) def findByProjectAdminMembership(project: KnoraProject): Task[Seq[User]] = - knoraUserService.findByProjectAdminMembership(project).flatMap(ZIO.foreach(_)(userToKnoraUserConverter.toUser)) + knoraUserService.findByProjectAdminMembership(project).flatMap(userConverter.toUser) def findAllRegularUsers: Task[Seq[User]] = - knoraUserService - .findAll() - .map(_.filter(_.id.isRegularUser)) - .flatMap(ZIO.foreach(_)(userToKnoraUserConverter.toUser)) - - private def fromCacheOrRepo[A]( - id: A, - fromCache: A => Task[Option[User]], - fromRepo: A => Task[Option[KnoraUser]], - ): Task[Option[User]] = - fromCache(id).flatMap { - case Some(user) => ZIO.some(user) - case None => - fromRepo(id).flatMap(ZIO.foreach(_)(userToKnoraUserConverter.toUser)).tap { - case Some(user) => cacheService.putUser(user) - case None => ZIO.unit - } - } + knoraUserService.findAllRegularUsers().flatMap(userConverter.toUser) } object UserService { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/AdminRepoModule.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/AdminRepoModule.scala index 2d4243e426..a1748acb20 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/AdminRepoModule.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/AdminRepoModule.scala @@ -15,15 +15,16 @@ import org.knora.webapi.slice.admin.repo.service.AdministrativePermissionRepoLiv import org.knora.webapi.slice.admin.repo.service.KnoraGroupRepoLive import org.knora.webapi.slice.admin.repo.service.KnoraProjectRepoLive import org.knora.webapi.slice.admin.repo.service.KnoraUserRepoLive -import org.knora.webapi.store.cache.CacheService +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.store.triplestore.api.TriplestoreService object AdminRepoModule { - type Dependencies = TriplestoreService & CacheService - type Provided = AdministrativePermissionRepo & KnoraGroupRepo & KnoraProjectRepo & KnoraUserRepo + type Dependencies = TriplestoreService + type Provided = AdministrativePermissionRepo & CacheManager & KnoraGroupRepo & KnoraProjectRepo & KnoraUserRepo val layer = ZLayer.makeSome[Dependencies, Provided]( + CacheManager.layer, KnoraGroupRepoLive.layer, KnoraProjectRepoLive.layer, KnoraUserRepoLive.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/CachingEntityRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/CachingEntityRepo.scala new file mode 100644 index 0000000000..503f718a65 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/CachingEntityRepo.scala @@ -0,0 +1,24 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.repo.service + +import zio.Task +import zio.ZIO + +import org.knora.webapi.slice.common.Value.StringValue +import org.knora.webapi.store.triplestore.api.TriplestoreService + +abstract class CachingEntityRepo[E <: EntityWithId[Id], Id <: StringValue]( + private val triplestore: TriplestoreService, + private val mapper: RdfEntityMapper[E], + private val cache: EntityCache[Id, E], +) extends AbstractEntityRepo[E, Id](triplestore, mapper) { + + override def findById(id: Id): Task[Option[E]] = + cache.get(id).fold(super.findById(id).map(_.map(cache.put)))(ZIO.some(_)) + + override def save(entity: E): Task[E] = super.save(entity).map(cache.put) +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/EntityCache.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/EntityCache.scala new file mode 100644 index 0000000000..3baae83f27 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/EntityCache.scala @@ -0,0 +1,32 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.repo.service + +import izumi.reflect.Tag +import zio.URLayer +import zio.ZIO +import zio.ZLayer + +import scala.annotation.nowarn +import scala.reflect.ClassTag + +import org.knora.webapi.slice.common.Value.StringValue +import org.knora.webapi.slice.infrastructure.CacheManager +import org.knora.webapi.slice.infrastructure.EhCache + +final case class EntityCache[I <: StringValue, E <: EntityWithId[I]](cache: EhCache[I, E]) { + def put(value: E): E = { cache.put(value.id, value); value } + def get(id: I): Option[E] = cache.get(id) +} + +object EntityCache { + + @nowarn // suppresses warnings about unused type parameters Tag + def layer[I <: StringValue: ClassTag: Tag, E <: EntityWithId[I]: ClassTag: Tag]( + cacheName: String, + ): URLayer[CacheManager, EntityCache[I, E]] = + ZLayer.fromZIO(ZIO.serviceWithZIO[CacheManager](_.createCache[I, E](cacheName).map(EntityCache.apply))) +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala index be1f93d8ff..a98afe1e82 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala @@ -23,14 +23,13 @@ import org.knora.webapi.slice.admin.repo.rdf.RdfConversions._ import org.knora.webapi.slice.admin.repo.rdf.Vocabulary import org.knora.webapi.slice.common.repo.rdf.Errors.RdfError import org.knora.webapi.slice.common.repo.rdf.RdfResource -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService final case class KnoraProjectRepoLive( private val triplestore: TriplestoreService, - private val cacheService: CacheService, private val mapper: RdfEntityMapper[KnoraProject], -) extends AbstractEntityRepo[KnoraProject, ProjectIri](triplestore, mapper) + private val cache: EntityCache[ProjectIri, KnoraProject], +) extends CachingEntityRepo[KnoraProject, ProjectIri](triplestore, mapper, cache) with KnoraProjectRepo { override protected def resourceClass: ParsedIRI = ParsedIRI.create(KnoraAdmin.KnoraProject) @@ -76,8 +75,8 @@ final case class KnoraProjectRepoLive( override def save(project: KnoraProject): Task[KnoraProject] = ZIO .die(new IllegalArgumentException("Update not supported for built-in projects")) - .when(KnoraProjectRepo.builtIn.findOneBy(_.id == project.id).isDefined) *> - cacheService.clearCache() *> super.save(project) + .when(project.id.isBuiltInProjectIri) *> + super.save(project) } object KnoraProjectRepoLive { @@ -143,5 +142,6 @@ object KnoraProjectRepoLive { } } - val layer = ZLayer.succeed(mapper) >>> ZLayer.derive[KnoraProjectRepoLive] + val layer = (ZLayer.succeed(mapper) >+> EntityCache.layer[ProjectIri, KnoraProject]("knoraProject")) >>> ZLayer + .derive[KnoraProjectRepoLive] } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLive.scala index fb966d6a07..eede066a24 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLive.scala @@ -36,14 +36,13 @@ import org.knora.webapi.slice.admin.repo.rdf.Vocabulary.KnoraAdmin._ import org.knora.webapi.slice.common.repo.rdf.Errors.ConversionError import org.knora.webapi.slice.common.repo.rdf.Errors.RdfError import org.knora.webapi.slice.common.repo.rdf.RdfResource -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService final case class KnoraUserRepoLive( private val triplestore: TriplestoreService, - private val cacheService: CacheService, private val mapper: RdfEntityMapper[KnoraUser], -) extends AbstractEntityRepo[KnoraUser, UserIri](triplestore, mapper) + private val entityCache: EntityCache[UserIri, KnoraUser], +) extends CachingEntityRepo[KnoraUser, UserIri](triplestore, mapper, entityCache) with KnoraUserRepo { override protected val resourceClass: ParsedIRI = ParsedIRI.create(KnoraAdmin.User) @@ -82,8 +81,8 @@ final case class KnoraUserRepoLive( override def save(user: KnoraUser): Task[KnoraUser] = ZIO .die(new IllegalArgumentException("Update not supported for built-in users")) - .when(KnoraUserRepo.builtIn.findOneBy(_.id == user.id).isDefined) *> - cacheService.invalidateUser(user.id) *> super.save(user) + .when(user.id.isBuiltInUser) *> + super.save(user) } object KnoraUserRepoLive { @@ -135,5 +134,6 @@ object KnoraUserRepoLive { .andHas(isInProjectAdminGroup, u.isInProjectAdminGroup.map(p => Rdf.iri(p.value)).toList: _*) } - val layer = ZLayer.succeed(mapper) >>> ZLayer.derive[KnoraUserRepoLive] + val layer = + (ZLayer.succeed(mapper) >+> EntityCache.layer[UserIri, KnoraUser]("knoraUser")) >>> ZLayer.derive[KnoraUserRepoLive] } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/CacheManager.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/CacheManager.scala new file mode 100644 index 0000000000..2bb9587834 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/CacheManager.scala @@ -0,0 +1,60 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.infrastructure + +import org.ehcache.config.CacheConfiguration +import org.ehcache.config.builders.CacheConfigurationBuilder +import org.ehcache.config.builders.CacheManagerBuilder +import org.ehcache.config.builders.ResourcePoolsBuilder +import zio.Ref +import zio.UIO +import zio.ULayer +import zio.ZIO +import zio.ZLayer + +import scala.reflect.ClassTag + +import org.knora.webapi.slice.infrastructure.CacheManager.defaultCacheConfigBuilder + +final case class EhCache[K, V](cache: org.ehcache.Cache[K, V]) { + def put(key: K, value: V): Unit = cache.put(key, value) + def get(key: K): Option[V] = Option(cache.get(key)) + def clear(): Unit = cache.clear() +} + +final case class CacheManager(manager: org.ehcache.CacheManager, knownCaches: Ref[Set[EhCache[_, _]]]) { + + def createCache[K: ClassTag, V: ClassTag](alias: String): UIO[EhCache[K, V]] = + createCache(alias, defaultCacheConfigBuilder[K, V]().build()) + + def createCache[K, V](alias: String, config: CacheConfiguration[K, V]): UIO[EhCache[K, V]] = { + val cache = EhCache[K, V](manager.createCache(alias, config)) + knownCaches.update(_ + cache).as(cache) + } + + def clearAll(): UIO[Unit] = knownCaches.get.flatMap(ZIO.foreachDiscard(_)(c => ZIO.succeed(c.clear()))) +} + +object CacheManager { + + // a simple config for an in memory cache with 1000 elements + def defaultCacheConfigBuilder[K: ClassTag, V: ClassTag](entries: Long = 1_000): CacheConfigurationBuilder[K, V] = + CacheConfigurationBuilder.newCacheConfigurationBuilder[K, V]( + getClassOf[K], + getClassOf[V], + ResourcePoolsBuilder.heap(entries), + ) + + private def getClassOf[A: ClassTag]: Class[A] = implicitly[ClassTag[A]].runtimeClass.asInstanceOf[Class[A]] + + val layer: ULayer[CacheManager] = ZLayer.scoped { + val acquire = ZIO.succeed(CacheManagerBuilder.newCacheManagerBuilder().build(true)) + val release = (cm: org.ehcache.CacheManager) => ZIO.succeed(cm.close()) + ZIO + .acquireRelease(acquire)(release) + .flatMap(mgr => Ref.make(Set.empty[EhCache[_, _]]).map(CacheManager(mgr, _))) + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala index 72d65cb8ff..defcd3bde5 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala @@ -31,7 +31,7 @@ import org.knora.webapi.IRI import org.knora.webapi.config.DspIngestConfig import org.knora.webapi.config.JwtConfig import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionADM -import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME +import org.knora.webapi.routing.InvalidTokenCache import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.Permission.Administrative @@ -43,7 +43,6 @@ import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.infrastructure.Scope import org.knora.webapi.slice.infrastructure.ScopeValue import org.knora.webapi.slice.infrastructure.ScopeValue.Write -import org.knora.webapi.util.cache.CacheUtil case class Jwt(jwtString: String, expiration: Long) @@ -86,6 +85,7 @@ final case class JwtServiceLive( private val jwtConfig: JwtConfig, private val dspIngestConfig: DspIngestConfig, private val knoraProjectService: KnoraProjectService, + private val cache: InvalidTokenCache, ) extends JwtService { private val algorithm: JwtAlgorithm = JwtAlgorithm.HS256 private val header: String = """{"typ":"JWT","alg":"HS256"}""" @@ -160,13 +160,7 @@ final case class JwtServiceLive( * @return a [[Boolean]]. */ override def validateToken(token: String): Task[Boolean] = - ZIO.attempt(if (CacheUtil.get[User](AUTHENTICATION_INVALIDATION_CACHE_NAME, token).nonEmpty) { - // token invalidated so no need to decode - logger.debug("validateToken - token found in invalidation cache, so not valid") - false - } else { - decodeToken(token).isDefined - }) + ZIO.succeed(!cache.contains(token) && decodeToken(token).isDefined) /** * Extracts the encoded user IRI. This method also makes sure that the required headers and claims are present. diff --git a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/ManagementEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/ManagementEndpoints.scala index 2d06ec0cd3..097a9fc79f 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/ManagementEndpoints.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/ManagementEndpoints.scala @@ -60,24 +60,17 @@ object HealthResponse { HealthResponse("AppState", "non fatal", false, message) def from(appState: AppState): HealthResponse = appState match { - case AppState.Stopped => unhealthy("Stopped. Please retry later.") - case AppState.StartingUp => unhealthy("Starting up. Please retry later.") - case AppState.WaitingForTriplestore => unhealthy("Waiting for triplestore. Please retry later.") - case AppState.TriplestoreReady => unhealthy("Triplestore ready. Please retry later.") - case AppState.UpdatingRepository => unhealthy("Updating repository. Please retry later.") - case AppState.RepositoryUpToDate => unhealthy("Repository up to date. Please retry later.") - case AppState.CreatingCaches => unhealthy("Creating caches. Please retry later.") - case AppState.CachesReady => unhealthy("Caches ready. Please retry later.") - case AppState.UpdatingSearchIndex => unhealthy("Updating search index. Please retry later.") - case AppState.SearchIndexReady => unhealthy("Search index ready. Please retry later.") - case AppState.LoadingOntologies => unhealthy("Loading ontologies. Please retry later.") - case AppState.OntologiesReady => unhealthy("Ontologies ready. Please retry later.") - case AppState.WaitingForIIIFService => unhealthy("Waiting for IIIF service. Please retry later.") - case AppState.IIIFServiceReady => unhealthy("IIIF service ready. Please retry later.") - case AppState.WaitingForCacheService => unhealthy("Waiting for cache service. Please retry later.") - case AppState.CacheServiceReady => unhealthy("Cache service ready. Please retry later.") - case AppState.MaintenanceMode => unhealthy("Application is in maintenance mode. Please retry later.") - case AppState.Running => healthy + case AppState.Stopped => unhealthy("Stopped. Please retry later.") + case AppState.WaitingForTriplestore => unhealthy("Waiting for triplestore. Please retry later.") + case AppState.TriplestoreReady => unhealthy("Triplestore ready. Please retry later.") + case AppState.UpdatingRepository => unhealthy("Updating repository. Please retry later.") + case AppState.RepositoryUpToDate => unhealthy("Repository up to date. Please retry later.") + case AppState.LoadingOntologies => unhealthy("Loading ontologies. Please retry later.") + case AppState.OntologiesReady => unhealthy("Ontologies ready. Please retry later.") + case AppState.WaitingForIIIFService => unhealthy("Waiting for IIIF service. Please retry later.") + case AppState.IIIFServiceReady => unhealthy("IIIF service ready. Please retry later.") + case AppState.MaintenanceMode => unhealthy("Application is in maintenance mode. Please retry later.") + case AppState.Running => healthy } implicit val encoder: JsonCodec[HealthResponse] = DeriveJsonCodec.gen[HealthResponse] diff --git a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/model/OntologyCacheData.scala b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/model/OntologyCacheData.scala index 4c7b5a1a9f..54c24bab7d 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/model/OntologyCacheData.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/model/OntologyCacheData.scala @@ -38,3 +38,16 @@ case class OntologyCacheData( }) .toMap } +object OntologyCacheData { + val Empty = OntologyCacheData( + ontologies = Map.empty, + classToSuperClassLookup = Map.empty, + classToSubclassLookup = Map.empty, + subPropertyOfRelations = Map.empty, + superPropertyOfRelations = Map.empty, + classDefinedInOntology = Map.empty, + propertyDefinedInOntology = Map.empty, + entityDefinedInOntology = Map.empty, + standoffProperties = Set.empty, + ) +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyCache.scala b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyCache.scala index 34a65afb62..697b547932 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyCache.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyCache.scala @@ -30,12 +30,9 @@ import org.knora.webapi.responders.v2.ontology.OntologyHelpers.OntologyGraph import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.ontology.repo.model.OntologyCacheData -import org.knora.webapi.slice.ontology.repo.service.OntologyCache.OntologyCacheKey -import org.knora.webapi.slice.ontology.repo.service.OntologyCache.OntologyCacheName import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Construct import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select -import org.knora.webapi.util.cache.CacheUtil object OntologyCache { // The global ontology cache lock. This is needed because every ontology update replaces the whole ontology cache @@ -43,12 +40,6 @@ object OntologyCache { // concurrent updates (even to different ontologies) could overwrite each other. val ONTOLOGY_CACHE_LOCK_IRI = "http://rdfh.ch/ontologies" - // The name of the ontology cache. - val OntologyCacheName = "ontologyCache" - - // The cache key under which cached ontology data is stored. - val OntologyCacheKey = "ontologyCacheData" - /** * Checks a class definition to ensure that it doesn't refer to any non-shared ontologies in other projects. * @@ -485,15 +476,16 @@ trait OntologyCache { ): Task[OntologyCacheData] } -final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val stringFormatter: StringFormatter) - extends OntologyCache +final case class OntologyCacheLive(triplestore: TriplestoreService, cacheDataRef: Ref[OntologyCacheData])(implicit + val stringFormatter: StringFormatter, +) extends OntologyCache with LazyLogging { /** * Loads and caches all ontology information. * * @param requestingUser the user making the request. - * @return a [[SuccessResponseV2]]. + * @return [[Unit]] */ override def loadOntologies(requestingUser: User): Task[Unit] = for { @@ -505,12 +497,8 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val ) // Get all ontology metadata. - allOntologyMetadataSparql <- ZIO.succeed( - sparql.v2.txt - .getAllOntologyMetadata(), - ) _ <- ZIO.logInfo(s"Loading ontologies into cache") - allOntologyMetadataResponse <- triplestore.query(Select(allOntologyMetadataSparql)) + allOntologyMetadataResponse <- triplestore.query(Select(sparql.v2.txt.getAllOntologyMetadata())) allOntologyMetadata = OntologyHelpers.buildOntologyMetadata(allOntologyMetadataResponse) knoraBaseOntologyMetadata = @@ -887,33 +875,15 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val OntologyCache.checkReferencesBetweenOntologies(ontologyCacheData) // Update the cache. - storeCacheData(ontologyCacheData) ontologyCacheData - } - - /** - * Updates the ontology cache. - * - * @param cacheData the updated data to be cached. - */ - private def storeCacheData(cacheData: OntologyCacheData): Unit = - CacheUtil.put(cacheName = OntologyCacheName, key = OntologyCacheKey, value = cacheData) + }.tap(cacheDataRef.set) /** * Gets the ontology data from the cache. * * @return an [[OntologyCacheData]] */ - override def getCacheData: Task[OntologyCacheData] = - ZIO.attempt { - CacheUtil.get[OntologyCacheData](cacheName = OntologyCacheName, key = OntologyCacheKey) match { - case Some(data) => data - case None => - throw ApplicationCacheException( - s"The Knora API server has not loaded any ontologies, perhaps because of an invalid ontology", - ) - } - } + override def getCacheData: Task[OntologyCacheData] = cacheDataRef.get /** * Given the IRI of a base class, updates inherited cardinalities in subclasses. @@ -1004,14 +974,12 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val updatedOntologyData: ReadOntologyV2, updatedClassIri: SmartIri, ): Task[OntologyCacheData] = - for { - ontologyCache <- getCacheData - newOntologies = ontologyCache.ontologies + (updatedOntologyIri -> updatedOntologyData) - newOntologyCacheData = OntologyCache.make(newOntologies) - updatedCacheData = updateSubClasses(updatedClassIri, newOntologyCacheData) - _ = storeCacheData(updatedCacheData) - updatedOntologyCache <- getCacheData - } yield updatedOntologyCache + cacheDataRef.updateAndGet { data => + updateSubClasses( + updatedClassIri, + OntologyCache.make(data.ontologies + (updatedOntologyIri -> updatedOntologyData)), + ) + } /** * Updates an existing ontology in the cache. If a class has changed, use `cacheUpdatedOntologyWithClass()`. @@ -1024,13 +992,7 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val updatedOntologyIri: SmartIri, updatedOntologyData: ReadOntologyV2, ): Task[OntologyCacheData] = - for { - ontologyCache <- getCacheData - newOntologies = ontologyCache.ontologies + (updatedOntologyIri -> updatedOntologyData) - newOntologyCacheData = OntologyCache.make(newOntologies) - _ = storeCacheData(newOntologyCacheData) - updatedOntologyCache <- getCacheData - } yield updatedOntologyCache + cacheDataRef.updateAndGet(data => OntologyCache.make(data.ontologies + (updatedOntologyIri -> updatedOntologyData))) /** * Updates an existing ontology in the cache without updating the cache lookup maps. This should only be used if only the ontology metadata has changed. @@ -1043,13 +1005,7 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val updatedOntologyIri: SmartIri, updatedOntologyData: ReadOntologyV2, ): Task[OntologyCacheData] = - for { - ontologyCache <- getCacheData - newOntologies = ontologyCache.ontologies + (updatedOntologyIri -> updatedOntologyData) - updatedCacheData = ontologyCache.copy(ontologies = newOntologies) - _ = storeCacheData(updatedCacheData) - updatedOntologyCache <- getCacheData - } yield updatedOntologyCache + cacheDataRef.updateAndGet(data => data.copy(data.ontologies + (updatedOntologyIri -> updatedOntologyData))) /** * Deletes an ontology from the cache. @@ -1058,15 +1014,9 @@ final case class OntologyCacheLive(triplestore: TriplestoreService)(implicit val * @return the updated cache data */ override def deleteOntology(ontologyIri: SmartIri): Task[OntologyCacheData] = - for { - ontologyCache <- getCacheData - newOntologies = ontologyCache.ontologies - ontologyIri - newOntologyCacheData = OntologyCache.make(newOntologies) - _ = storeCacheData(newOntologyCacheData) - updatedOntologyCache <- getCacheData - } yield updatedOntologyCache + cacheDataRef.updateAndGet(data => OntologyCache.make(data.ontologies - ontologyIri)) } object OntologyCacheLive { - val layer = ZLayer.derive[OntologyCacheLive] + val layer = ZLayer.fromZIO(Ref.make(OntologyCacheData.Empty)) >>> ZLayer.derive[OntologyCacheLive] } diff --git a/webapi/src/main/scala/org/knora/webapi/store/cache/CacheService.scala b/webapi/src/main/scala/org/knora/webapi/store/cache/CacheService.scala deleted file mode 100644 index 02a9ade16e..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/store/cache/CacheService.scala +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.store.cache - -import zio._ -import zio.stm._ - -import org.knora.webapi.messages.admin.responder.projectsmessages.Project -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ -import org.knora.webapi.slice.admin.domain.model.Email -import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri -import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode -import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortname -import org.knora.webapi.slice.admin.domain.model.User -import org.knora.webapi.slice.admin.domain.model.UserIri -import org.knora.webapi.slice.admin.domain.model.Username - -/** - * In-Memory Cache implementation - * - * The state is divided into Refs used to store different types of objects. - * A ref in itself is fiber (thread) safe, but to keep the cumulative state - * consistent, all Refs need to be updated in a single transaction. This - * requires STM (Software Transactional Memory) to be used. - */ -case class CacheService( - users: TMap[UserIri, User], - projects: TMap[ProjectIri, Project], - mappingUsernameUserIri: TMap[Username, UserIri], - mappingEmailUserIri: TMap[Email, UserIri], - mappingShortcodeProjectIri: TMap[Shortcode, ProjectIri], - mappingShortnameProjectIri: TMap[Shortname, ProjectIri], -) { - - /** - * Stores the user under the IRI and additionally the IRI - * - * users: - * IRI -> byte array - * - * lookupTableUsers: - * username -> IRI - * email -> IRI - * - * @param value the value to be stored - */ - def putUser(value: User): Task[Unit] = - (for { - _ <- users.put(value.userIri, value) - _ <- mappingUsernameUserIri.put(value.getUsername, value.userIri) - _ <- mappingEmailUserIri.put(value.getEmail, value.userIri) - } yield ()).commit - - def getUserByIri(iri: UserIri): Task[Option[User]] = users.get(iri).commit - - def getUserByUsername(username: Username): Task[Option[User]] = - mappingUsernameUserIri.get(username).some.flatMap(users.get(_).some).commit.unsome - - def getUserByEmail(email: Email): Task[Option[User]] = - mappingEmailUserIri.get(email).some.flatMap(users.get(_).some).commit.unsome - - /** - * Invalidates the user stored under the IRI. - * @param iri the user's IRI. - */ - def invalidateUser(iri: UserIri): UIO[Unit] = - (for { - user <- users.get(iri).some - _ <- users.delete(iri) - _ <- mappingUsernameUserIri.delete(user.getUsername) - _ <- mappingEmailUserIri.delete(user.getEmail) - } yield ()).commit.ignore - - /** - * Stores the project under the IRI and additionally the IRI under the keys - * of Shortcode and Shortname: - * - * projects: - * IRI -> byte array - * - * lookupTableProjects: - * shortname -> IRI - * shortcode -> IRI - * - * @param value the stored value - * @return [[Unit]] - */ - def putProjectADM(value: Project): Task[Unit] = - (for { - _ <- projects.put(value.projectIri, value) - _ <- mappingShortcodeProjectIri.put(value.getShortcode, value.projectIri) - _ <- mappingShortnameProjectIri.put(value.getShortname, value.projectIri) - } yield ()).commit - - /** - * Retrieves the project stored under the identifier (either iri, shortname, or shortcode). - * - * The data is stored under the IRI key. - * Additionally, the Shortcode and Shortname keys point to the IRI key - * - * @param identifier the project identifier. - * @return an optional [[ProjectADM]] - */ - def getProjectADM(identifier: ProjectIdentifierADM): Task[Option[Project]] = - identifier match { - case IriIdentifier(projectIri) => projects.get(projectIri).commit - case ShortcodeIdentifier(code) => - mappingShortcodeProjectIri.get(code).some.flatMap(projects.get(_).some).commit.unsome - case ShortnameIdentifier(name) => - mappingShortnameProjectIri.get(name).some.flatMap(projects.get(_).some).commit.unsome - } - - /** - * Invalidates the project stored under the IRI. - * This includes removing the IRI, Shortcode and Shortname keys. - * @param iri the project's IRI. - */ - def invalidateProjectADM(iri: ProjectIri): UIO[Unit] = - (for { - project <- projects.get(iri).some - _ <- projects.delete(iri) - _ <- mappingShortcodeProjectIri.delete(project.getShortcode) - _ <- mappingShortnameProjectIri.delete(project.getShortname) - } yield ()).commit.ignore - - /** - * Flushes (removes) all stored content from the in-memory cache. - */ - def clearCache(): Task[Unit] = - (for { - _ <- users.removeIf((_, _) => true) - _ <- projects.removeIf((_, _) => true) - _ <- mappingUsernameUserIri.removeIf((_, _) => true) - _ <- mappingEmailUserIri.removeIf((_, _) => true) - _ <- mappingShortcodeProjectIri.removeIf((_, _) => true) - _ <- mappingShortnameProjectIri.removeIf((_, _) => true) - } yield ()).commit - -} - -object CacheService { - val layer: ZLayer[Any, Nothing, CacheService] = - ZLayer { - for { - users <- TMap.empty[UserIri, User].commit - projects <- TMap.empty[ProjectIri, Project].commit - usernameMapping <- TMap.empty[Username, UserIri].commit - emailMapping <- TMap.empty[Email, UserIri].commit - shortcodeMapping <- TMap.empty[Shortcode, ProjectIri].commit - shortnameMapping <- TMap.empty[Shortname, ProjectIri].commit - } yield CacheService(users, projects, usernameMapping, emailMapping, shortcodeMapping, shortnameMapping) - }.tap(_ => ZIO.logInfo(">>> In-Memory Cache Service Initialized <<<")) -} diff --git a/webapi/src/main/scala/org/knora/webapi/store/cache/CacheServiceRequestMessageHandler.scala b/webapi/src/main/scala/org/knora/webapi/store/cache/CacheServiceRequestMessageHandler.scala deleted file mode 100644 index 05beaec396..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/store/cache/CacheServiceRequestMessageHandler.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.store.cache - -import zio._ -import zio.metrics.Metric - -import java.time.temporal.ChronoUnit - -import org.knora.webapi.core.MessageHandler -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.ResponderRequest -import org.knora.webapi.messages.store.cacheservicemessages._ - -trait CacheServiceRequestMessageHandler extends MessageHandler - -final case class CacheServiceRequestMessageHandlerLive(cacheService: CacheService) - extends CacheServiceRequestMessageHandler { - - private val cacheServiceWriteProjectTimer = Metric - .timer( - name = "cache-service-write-project", - chronoUnit = ChronoUnit.NANOS, - ) - - private val cacheServiceReadProjectTimer = Metric - .timer( - name = "cache-service-read-project", - chronoUnit = ChronoUnit.NANOS, - ) - override def isResponsibleFor(message: ResponderRequest): Boolean = message.isInstanceOf[CacheServiceRequest] - - override def handle(message: ResponderRequest): Task[Any] = message match { - case CacheServicePutProjectADM(value) => - cacheService.putProjectADM(value) @@ cacheServiceWriteProjectTimer.trackDuration - case CacheServiceGetProjectADM(identifier) => - cacheService.getProjectADM(identifier) @@ cacheServiceReadProjectTimer.trackDuration - case other => ZIO.logError(s"CacheServiceManager received an unexpected message: $other") - } -} - -object CacheServiceRequestMessageHandlerLive { - val layer: URLayer[CacheService & MessageRelay, CacheServiceRequestMessageHandler] = ZLayer.fromZIO { - for { - mr <- ZIO.service[MessageRelay] - cs <- ZIO.service[CacheService] - handler <- mr.subscribe(CacheServiceRequestMessageHandlerLive(cs)) - } yield handler - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/util/cache/CacheUtil.scala b/webapi/src/main/scala/org/knora/webapi/util/cache/CacheUtil.scala deleted file mode 100644 index 48f8cc0cf5..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/util/cache/CacheUtil.scala +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.util.cache - -import com.typesafe.scalalogging.Logger -import net.sf.ehcache.Cache -import net.sf.ehcache.CacheManager -import net.sf.ehcache.Ehcache -import net.sf.ehcache.Element -import net.sf.ehcache.event.CacheEventListener -import org.slf4j.LoggerFactory - -import dsp.errors.ApplicationCacheException - -/** - * Maintains in-memory caches, and caches values, using Ehcache (http://ehcache.org/). Each cache is accessible - * by name from any function running in the same JVM. - */ -object CacheUtil { - - val log: Logger = Logger(LoggerFactory.getLogger("org.knora.webapi.util.cache")) - - /** - * Represents the configuration of a Knora application cache. - * - * @param cacheName the name of the cache. - * @param maxElementsInMemory the maximum number of elements in memory, before they are evicted (0 == no limit). - * @param overflowToDisk whether to use the disk store. - * @param eternal whether the elements in the cache are eternal, i.e. never expire. - * @param timeToLiveSeconds the default amount of time to live for an element from its creation date. - * @param timeToIdleSeconds the default amount of time to live for an element from its last accessed or modified date. - */ - case class KnoraCacheConfig( - cacheName: String, - maxElementsInMemory: Int, - overflowToDisk: Boolean, - eternal: Boolean, - timeToLiveSeconds: Int, - timeToIdleSeconds: Int, - ) - - /** - * Creates application caches. - * - * @param cacheConfigs Maps containing the keys `cacheName`, `maxElementsInMemory`, - * `overflowToDisk`, `eternal`, `timeToLiveSeconds`, and `timeToIdleSeconds`, - * representing configuration options for Ehcache caches. - */ - def createCaches(cacheConfigs: Seq[KnoraCacheConfig]): Unit = { - val cacheManager = CacheManager.getInstance() - cacheConfigs.foreach { cacheConfig => - val cache = new Cache( - cacheConfig.cacheName, - cacheConfig.maxElementsInMemory, - cacheConfig.overflowToDisk, - cacheConfig.eternal, - cacheConfig.timeToLiveSeconds, - cacheConfig.timeToIdleSeconds, - ) - cacheManager.addCache(cache) - cache.getCacheEventNotificationService.registerListener(new LoggingCacheEventListener(log)) - log.info(s"CacheUtil: Created application cache '${cacheConfig.cacheName}'") - } - } - - /** - * Removes all caches. - */ - def removeAllCaches(): Unit = { - val cacheManager = CacheManager.getInstance() - cacheManager.removeAllCaches() - } - - /** - * Clears a cache. - * - * @param cacheName the name of the cache to be cleared. - */ - def clearCache(cacheName: String): Unit = { - val cacheManager = CacheManager.getInstance() - Option(cacheManager.getCache(cacheName)) match { - case Some(cache) => - cache.removeAll() - case None => - throw ApplicationCacheException(s"Application cache '$cacheName' not found") - } - } - - /** - * Adds a value to a cache. - * - * @param cacheName the name of the cache. - * @param key the cache key as a [[String]]. - * @param value the value we want to cache. - * @tparam V the type of the value we want to cache. - */ - def put[V](cacheName: String, key: String, value: V): Unit = { - val cacheManager = CacheManager.getInstance() - val cacheOption = Option(cacheManager.getCache(cacheName)) - - cacheOption match { - case Some(cache) => - cache.put(new Element(key, value)) - case None => - throw ApplicationCacheException(s"Can't find application cache '$cacheName'") - } - } - - /** - * Tries to ge a value from a cache. - * - * @param cacheName the name of the cache. - * @param key the cache key as a [[String]]. - * @tparam V the type of the item we try to get from the cache. - * @return an [[Option[V]]]. - */ - def get[V](cacheName: String, key: String): Option[V] = { - val cacheManager = CacheManager.getInstance() - val cacheOption = Option(cacheManager.getCache(cacheName)) - - cacheOption match { - case Some(cache) => - Option(cache.get(key)) match { - case Some(element) => - log.debug(s"got value: ${element.toString} from cache: $cacheName") - Some(element.getObjectValue.asInstanceOf[V]) - case None => - log.debug(s"no value for key: $key found in cache: $cacheName") - None - } - case None => - throw ApplicationCacheException( - s"Can't find application cache '$cacheName'. Please check configuration of 'app.caches' in 'application.conf'", - ) - } - - } -} - -class LoggingCacheEventListener(log: Logger) extends CacheEventListener { - - def notifyElementRemoved(cache: Ehcache, element: Element): Unit = - log.debug("notifyElementRemoved " + cache.getName + element.toString) - - def notifyElementPut(cache: Ehcache, element: Element): Unit = - log.debug("notifyElementPut " + cache.getName + element.toString) - - def notifyElementUpdated(cache: Ehcache, element: Element): Unit = - log.debug("notifyElementUpdated " + cache.getName + element.toString) - - def notifyElementExpired(cache: Ehcache, element: Element): Unit = - log.debug("notifyElementExpired " + cache.getName + element.toString) - - def notifyElementEvicted(cache: Ehcache, element: Element): Unit = - log.debug("notifyElementEvicted " + cache.getName + element.toString) - - def notifyRemoveAll(cache: Ehcache): Unit = - log.debug("notifyRemoveAll " + cache.getName) - - override def clone(): Unit = log.debug("CloneNotSupportedException") - - def dispose(): Unit = {} - -} diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala index d6c91c79ca..0a8b9385a9 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala @@ -27,8 +27,8 @@ import org.knora.webapi.slice.admin.domain.service.PasswordService import org.knora.webapi.slice.admin.repo.service.KnoraGroupRepoInMemory import org.knora.webapi.slice.admin.repo.service.KnoraUserRepoLive import org.knora.webapi.slice.common.api.AuthorizationRestService +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.resourceinfo.domain.IriConverter -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.impl.TriplestoreServiceLive object AuthorizationRestServiceSpec extends ZIOSpecDefault { @@ -136,7 +136,7 @@ object AuthorizationRestServiceSpec extends ZIOSpecDefault { ).provide( AppConfig.layer, AuthorizationRestService.layer, - CacheService.layer, + CacheManager.layer, IriConverter.layer, IriService.layer, KnoraGroupRepoInMemory.layer, diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/MaintenanceServiceSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/MaintenanceServiceSpec.scala index 486e0ab642..675731fc79 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/MaintenanceServiceSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/MaintenanceServiceSpec.scala @@ -22,7 +22,6 @@ import org.knora.webapi.slice.admin.domain.service.MaintenanceService import org.knora.webapi.slice.admin.domain.service.ProjectService import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper import org.knora.webapi.slice.resourceinfo.domain.IriConverter -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TestTripleStore import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select @@ -117,7 +116,6 @@ object MaintenanceServiceSpec extends ZIOSpecDefault { }, ).provide( MaintenanceService.layer, - CacheService.layer, KnoraProjectService.layer, KnoraProjectRepoInMemory.layer, emptyDatasetRefLayer >>> TriplestoreServiceInMemory.layer, diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/GroupServiceSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/GroupServiceSpec.scala index 4898e1aedf..53ccbcfd97 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/GroupServiceSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/GroupServiceSpec.scala @@ -26,10 +26,10 @@ import org.knora.webapi.slice.admin.domain.model.KnoraGroup import org.knora.webapi.slice.admin.domain.repo.KnoraProjectRepoInMemory import org.knora.webapi.slice.admin.repo.service.KnoraGroupRepoInMemory import org.knora.webapi.slice.admin.repo.service.KnoraUserRepoLive +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.slice.ontology.repo.service.OntologyCacheLive import org.knora.webapi.slice.ontology.repo.service.OntologyRepoLive import org.knora.webapi.slice.resourceinfo.domain.IriConverter -import org.knora.webapi.store.cache.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory object GroupServiceSpec extends ZIOSpecDefault { @@ -66,7 +66,7 @@ object GroupServiceSpec extends ZIOSpecDefault { }, ).provide( AppConfig.layer, - CacheService.layer, + CacheManager.layer, GroupService.layer, IriConverter.layer, IriService.layer, diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLiveSpec.scala index 75484b1086..72a749c2e2 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLiveSpec.scala @@ -10,6 +10,7 @@ import zio.NonEmptyChunk import zio.ZIO import zio.test.Gen import zio.test.Spec +import zio.test.TestAspect import zio.test.ZIOSpecDefault import zio.test.assertTrue import zio.test.check @@ -29,7 +30,7 @@ import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortname import org.knora.webapi.slice.admin.domain.model.KnoraProject.Status import org.knora.webapi.slice.admin.domain.model.RestrictedView import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo -import org.knora.webapi.store.cache.CacheService +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory object KnoraProjectRepoLiveSpec extends ZIOSpecDefault { @@ -84,7 +85,7 @@ object KnoraProjectRepoLiveSpec extends ZIOSpecDefault { } yield assertTrue(exit.isFailure) } }, - ), + ) @@ TestAspect.sequential @@ TestAspect.before(ZIO.serviceWith[CacheManager](_.clearAll())), suite("findAll")( test("return all projects if some exist") { for { @@ -112,9 +113,10 @@ object KnoraProjectRepoLiveSpec extends ZIOSpecDefault { }, test("return None if project does not exist") { for { - project <- KnoraProjectRepo( - _.findById(ProjectIdentifierADM.IriIdentifier.unsafeFrom("http://rdfh.ch/projects/1234")), - ) + project <- + KnoraProjectRepo( + _.findById(ProjectIdentifierADM.IriIdentifier.unsafeFrom("http://rdfh.ch/projects/unknown-project")), + ) } yield assertTrue(project.isEmpty) }, test("should find all built in projects") { @@ -166,5 +168,5 @@ object KnoraProjectRepoLiveSpec extends ZIOSpecDefault { }, ), ), - ).provide(KnoraProjectRepoLive.layer, TriplestoreServiceInMemory.emptyLayer, CacheService.layer, StringFormatter.test) + ).provide(KnoraProjectRepoLive.layer, TriplestoreServiceInMemory.emptyLayer, CacheManager.layer, StringFormatter.test) } diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLiveSpec.scala index dff0177118..10e9eb2288 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/KnoraUserRepoLiveSpec.scala @@ -15,6 +15,7 @@ import zio.RIO import zio.ZIO import zio.test.Gen import zio.test.Spec +import zio.test.TestAspect import zio.test.ZIOSpecDefault import zio.test.assertTrue import zio.test.check @@ -37,7 +38,7 @@ import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.admin.repo.rdf.Vocabulary -import org.knora.webapi.store.cache.CacheService +import org.knora.webapi.slice.infrastructure.CacheManager import org.knora.webapi.store.triplestore.api.TestTripleStore import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update @@ -190,7 +191,7 @@ object KnoraUserRepoLiveSpec extends ZIOSpecDefault { test("should create and find user") { for { savedUser <- KnoraUserRepo(_.save(testUser)) - foundUser <- KnoraUserRepo(_.findById(savedUser.id)).someOrFail(new Exception("User not found")) + foundUser <- KnoraUserRepo(_.findById(savedUser.id)).someOrFailException } yield assertTrue(savedUser == testUser, foundUser == testUser) }, test("should update an existing user's username find user with new username") { @@ -198,38 +199,35 @@ object KnoraUserRepoLiveSpec extends ZIOSpecDefault { _ <- KnoraUserRepo(_.save(testUser)) // create the user updated <- KnoraUserRepo(_.save(testUser.copy(username = Username.unsafeFrom("newUsername")))) // update the username - updatedUser <- KnoraUserRepo(_.findByUsername(updated.username)).someOrFail(new Exception("User not found")) + updatedUser <- KnoraUserRepo(_.findByUsername(updated.username)).someOrFailException } yield assertTrue(updatedUser.username == Username.unsafeFrom("newUsername")) }, test("should update an existing user isInProject ") { for { - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProject = Chunk(TestDataFactory.someProject.id)))) - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) - } yield assertTrue(updatedUser.isInProject == Chunk(TestDataFactory.someProject.id)) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user + userUpdate = testUserWithoutAnyGroups.copy(isInProject = Chunk(TestDataFactory.someProject.id)) + _ <- KnoraUserRepo(_.save(userUpdate)) + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException + } yield assertTrue(updatedUser == userUpdate) }, test("should update an existing user isInProject and remove them") { for { - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user - _ <- TestTripleStore.printDataset("After creating user: ") - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProject = Chunk(TestDataFactory.someProject.id)))) - _ <- TestTripleStore.printDataset("After adding isInProject: ") - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProject = Chunk.empty))) - _ <- TestTripleStore.printDataset("After removing isInProject: ") - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user + _ <- TestTripleStore.printDataset("After creating user: ") + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProject = Chunk(TestDataFactory.someProject.id)))) + _ <- TestTripleStore.printDataset("After adding isInProject: ") + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProject = Chunk.empty))) + _ <- TestTripleStore.printDataset("After removing isInProject: ") + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException } yield assertTrue(updatedUser.isInProject.isEmpty) }, test("should update an existing user isInProjectAdminGroup") { for { - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user - _ <- KnoraUserRepo( - _.save(testUserWithoutAnyGroups.copy(isInProjectAdminGroup = Chunk(TestDataFactory.someProject.id))), - ) - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) - } yield assertTrue(updatedUser.isInProjectAdminGroup == Chunk(TestDataFactory.someProject.id)) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user + userUpdate = testUserWithoutAnyGroups.copy(isInProjectAdminGroup = Chunk(TestDataFactory.someProject.id)) + _ <- KnoraUserRepo(_.save(userUpdate)) + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException + } yield assertTrue(updatedUser == userUpdate) }, test("should update an existing user isInProjectAdminGroup and remove them") { for { @@ -237,28 +235,25 @@ object KnoraUserRepoLiveSpec extends ZIOSpecDefault { _ <- KnoraUserRepo( _.save(testUserWithoutAnyGroups.copy(isInProjectAdminGroup = Chunk(TestDataFactory.someProject.id))), ) - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProjectAdminGroup = Chunk.empty))) - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInProjectAdminGroup = Chunk.empty))) + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException } yield assertTrue(updatedUser.isInProjectAdminGroup.isEmpty) }, test("should update an existing user isInGroup ") { val groupIri = GroupIri.unsafeFrom("http://rdfh.ch/groups/0001/1234") for { - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk(groupIri)))) - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk(groupIri)))) + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException } yield assertTrue(updatedUser.isInGroup == Chunk(groupIri)) }, test("should update an existing user isInGroup and remove them") { val groupIri = GroupIri.unsafeFrom("http://rdfh.ch/groups/0001/1234") for { - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk(groupIri)))) - _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk.empty))) - updatedUser <- - KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFail(new Exception("User not found")) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups)) // create the user + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk(groupIri)))) + _ <- KnoraUserRepo(_.save(testUserWithoutAnyGroups.copy(isInGroup = Chunk.empty))) + updatedUser <- KnoraUserRepo(_.findById(testUserWithoutAnyGroups.id)).someOrFailException } yield assertTrue(updatedUser.isInGroup.isEmpty) }, test("die for built in users") { @@ -268,7 +263,14 @@ object KnoraUserRepoLiveSpec extends ZIOSpecDefault { } yield assertTrue(exit.isFailure) } }, - ), + test("after save should retrieve user from cache") { + for { + _ <- KnoraUserRepo(_.save(testUser)) + _ <- TestTripleStore.setEmptyDataset() + userById <- KnoraUserRepo(_.findById(testUser.id)).someOrFailException + } yield assertTrue(userById == testUser) + }, + ) @@ TestAspect.before(ZIO.serviceWith[CacheManager](_.clearAll())) @@ TestAspect.sequential, suite("find members")( test("find project members given a project should return all members") { for { @@ -297,8 +299,8 @@ object KnoraUserRepoLiveSpec extends ZIOSpecDefault { ), ).provide( KnoraUserRepoLive.layer, + CacheManager.layer, TriplestoreServiceInMemory.emptyLayer, StringFormatter.test, - CacheService.layer, ) } diff --git a/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala index 4d07a333e4..ffd21e3ab0 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala @@ -5,7 +5,6 @@ package org.knora.webapi.slice.infrastructure -import net.sf.ehcache.CacheManager import pdi.jwt.JwtAlgorithm import pdi.jwt.JwtClaim import pdi.jwt.JwtHeader @@ -37,7 +36,7 @@ import org.knora.webapi.config.JwtConfig import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionADM import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 -import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME +import org.knora.webapi.routing.InvalidTokenCache import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.Description import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri @@ -53,9 +52,6 @@ import org.knora.webapi.slice.admin.domain.repo.KnoraProjectRepoInMemory import org.knora.webapi.slice.admin.domain.service.KnoraGroupRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectService -import org.knora.webapi.slice.infrastructure.JwtService -import org.knora.webapi.slice.infrastructure.JwtServiceLive -import org.knora.webapi.store.cache.CacheService final case class ScopeJs(scope: String) object ScopeJs { @@ -133,13 +129,7 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { private def getScopeClaimValue(token: String) = getClaimZIO(token, c => ZIO.fromEither(c.content.fromJson[ScopeJs](ScopeJs.decoder))).map(_.scope) - private def initCache = ZIO.succeed { - val cacheManager = CacheManager.getInstance() - cacheManager.addCacheIfAbsent(AUTHENTICATION_INVALIDATION_CACHE_NAME) - cacheManager.clearAll() - } - - val spec: Spec[TestEnvironment with Scope, Any] = suite("JwtService")( + val spec: Spec[TestEnvironment with Scope, Any] = (suite("JwtService")( test("create a token") { for { token <- JwtService(_.createJwt(user, Map("foo" -> JsString("bar")))) @@ -233,12 +223,14 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { } yield assertTrue(!isValid) } }, - ).provide( - CacheService.layer, - JwtServiceLive.layer, - KnoraProjectRepoInMemory.layer, - KnoraProjectService.layer, - dspIngestConfigLayer, - jwtConfigLayer, - ) @@ TestAspect.withLiveEnvironment @@ TestAspect.beforeAll(initCache) + ) @@ TestAspect.withLiveEnvironment @@ TestAspect.beforeAll(ZIO.serviceWith[CacheManager](_.clearAll()))) + .provide( + CacheManager.layer, + InvalidTokenCache.layer, + JwtServiceLive.layer, + KnoraProjectRepoInMemory.layer, + KnoraProjectService.layer, + dspIngestConfigLayer, + jwtConfigLayer, + ) } diff --git a/webapi/src/test/scala/org/knora/webapi/store/triplestore/api/TriplestoreServiceInMemory.scala b/webapi/src/test/scala/org/knora/webapi/store/triplestore/api/TriplestoreServiceInMemory.scala index de2202ae08..1c2f071155 100644 --- a/webapi/src/test/scala/org/knora/webapi/store/triplestore/api/TriplestoreServiceInMemory.scala +++ b/webapi/src/test/scala/org/knora/webapi/store/triplestore/api/TriplestoreServiceInMemory.scala @@ -69,6 +69,9 @@ object TestTripleStore { def setDataset(dataset: Dataset): ZIO[TestTripleStore, Throwable, Unit] = ZIO.serviceWithZIO[TestTripleStore](_.setDataset(dataset)) + def setEmptyDataset() = + ZIO.serviceWithZIO[TestTripleStore](_.setDataset(TDB2Factory.createDataset())) + def getDataset: RIO[TestTripleStore, Dataset] = ZIO.serviceWithZIO[TestTripleStore](_.getDataset)