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 2980f7b740..9c86afbfd8 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 @@ -20,6 +20,11 @@ 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.ProjectADM +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetRequestADM +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetResponseADM +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 @@ -156,6 +161,28 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender { ) } + "invalidate cached project information when adding an ontology to a project" in { + // ernsure that the project is cached + appActor ! ProjectGetRequestADM(ProjectIdentifierADM.IriIdentifier.unsafeFrom(imagesProjectIri.toString)) + val projectResponse = expectMsgType[ProjectGetResponseADM](timeout) + appActor ! CacheServiceGetProjectADM(ProjectIdentifierADM.IriIdentifier.unsafeFrom(imagesProjectIri.toString)) + val cachedProjectBefore = expectMsgType[Option[ProjectADM]](timeout) + assert(cachedProjectBefore.isDefined) + // create an ontology + appActor ! CreateOntologyRequestV2( + ontologyName = "foo-two", + projectIri = imagesProjectIri, + label = "The foo-two ontology", + apiRequestID = UUID.randomUUID, + requestingUser = imagesUser + ) + val response = expectMsgType[ReadOntologyMetadataV2](timeout) + // ensure that the project is no longer cached + appActor ! CacheServiceGetProjectADM(ProjectIdentifierADM.IriIdentifier.unsafeFrom(imagesProjectIri.toString)) + val cachedProject = expectMsgType[Option[ProjectADM]](timeout) + assert(cachedProject.isEmpty) + } + "change the label in the metadata of 'foo'" in { val newLabel = "The modified foo ontology" 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 44382f5c46..19ba7f0873 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 @@ -36,11 +36,13 @@ import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder import org.knora.webapi.responders.v2.ontology.CardinalityHandler import org.knora.webapi.responders.v2.ontology.OntologyHelpers +import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo 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.api.CacheService import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update @@ -75,6 +77,7 @@ final case class OntologyResponderV2Live( ontologyRepo: OntologyRepo, projectRepo: KnoraProjectRepo, triplestoreService: TriplestoreService, + cacheService: CacheService, implicit val stringFormatter: StringFormatter ) extends OntologyResponderV2 with MessageHandler @@ -509,6 +512,9 @@ final case class OntologyResponderV2Live( ReadOntologyV2(ontologyMetadata = unescapedNewMetadata) ) + projectIri <- KnoraProject.ProjectIri.from(createOntologyRequest.projectIri.toString).toZIO + _ <- cacheService.invalidateProjectADM(projectIri) + } yield ReadOntologyMetadataV2(ontologies = Set(unescapedNewMetadata)) for { @@ -1857,6 +1863,13 @@ 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) + .flatMap(iri => KnoraProject.ProjectIri.from(iri.toString).toZIO) + .orElseFail(InconsistentRepositoryDataException(s"Project IRI not found for ontology $internalOntologyIri")) + _ <- cacheService.invalidateProjectADM(projectIri) // Check that the ontology has been deleted. maybeOntologyMetadata <- ontologyHelpers.loadOntologyMetadata(internalOntologyIri) @@ -2951,7 +2964,7 @@ final case class OntologyResponderV2Live( object OntologyResponderV2Live { val layer: URLayer[ AppConfig & CardinalityHandler & CardinalityService & IriService & KnoraProjectRepo & MessageRelay & OntologyCache & - OntologyHelpers & OntologyRepo & StringFormatter & TriplestoreService, + OntologyHelpers & OntologyRepo & StringFormatter & TriplestoreService & CacheService, OntologyResponderV2 ] = ZLayer.fromZIO { for { @@ -2965,7 +2978,8 @@ object OntologyResponderV2Live { or <- ZIO.service[OntologyRepo] sf <- ZIO.service[StringFormatter] ts <- ZIO.service[TriplestoreService] - responder = OntologyResponderV2Live(ac, ch, cs, is, oc, oh, or, kr, ts, sf) + cache <- ZIO.service[CacheService] + responder = OntologyResponderV2Live(ac, ch, cs, is, oc, oh, or, kr, ts, cache, sf) _ <- MessageRelay.subscribe(responder) } yield responder } diff --git a/webapi/src/main/scala/org/knora/webapi/store/cache/api/CacheService.scala b/webapi/src/main/scala/org/knora/webapi/store/cache/api/CacheService.scala index ff0c7ed5af..e93e8795f9 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/cache/api/CacheService.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/cache/api/CacheService.scala @@ -13,6 +13,7 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentif import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.admin.responder.usersmessages.UserIdentifierADM import org.knora.webapi.messages.store.cacheservicemessages.CacheServiceStatusResponse +import org.knora.webapi.slice.admin.domain.model.KnoraProject /** * Cache Service Interface @@ -23,29 +24,10 @@ trait CacheService { def getUserADM(identifier: UserIdentifierADM): Task[Option[UserADM]] def putProjectADM(value: ProjectADM): Task[Unit] def getProjectADM(identifier: ProjectIdentifierADM): Task[Option[ProjectADM]] + def invalidateProjectADM(identifier: KnoraProject.ProjectIri): UIO[Unit] def putStringValue(key: String, value: String): Task[Unit] def getStringValue(key: String): Task[Option[String]] def removeValues(keys: Set[String]): Task[Unit] def flushDB(requestingUser: UserADM): Task[Unit] val getStatus: UIO[CacheServiceStatusResponse] } - -/** - * Cache Service companion object using [[Accessible]]. - * To use, simply call `Companion(_.someMethod)`, to return a ZIO - * effect that requires the Service in its environment. - * - * Example: - * {{{ - * trait CacheService { - * def ping(): Task[CacheServiceStatusResponse] - * } - * - * object CacheService extends Accessible[CacheService] - * - * val example: ZIO[CacheService, Nothing, Unit] = - * for { - * _ <- CacheService(_.ping()) - * } yield () - * }}} - */ diff --git a/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala b/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala index edc8cb5256..750a3865ec 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala @@ -127,6 +127,24 @@ case class CacheServiceInMemImpl( case ShortnameIdentifier(value) => getProjectByShortname(value) }).tap(_ => ZIO.logDebug(s"Retrieved ProjectADM from Cache: $identifier")) + /** + * 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.value).some + shortcode = project.shortcode + shortname = project.shortname + _ <- projects.delete(iri.value) + _ <- projects.delete(shortcode) + _ <- projects.delete(shortname) + _ <- lut.delete(iri.value) + _ <- lut.delete(shortcode) + _ <- lut.delete(shortname) + } yield ()).commit.ignore + /** * Retrieves the project by the IRI. *