Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Extract common code from responders into EntityAndClassIriS… #2348

Merged
merged 1 commit into from Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion project/Dependencies.scala
Expand Up @@ -44,6 +44,7 @@ object Dependencies {
// zio-test and friends
val zioTest = "dev.zio" %% "zio-test" % ZioVersion
val zioTestSbt = "dev.zio" %% "zio-test-sbt" % ZioVersion
val zioMock = "dev.zio" %% "zio-mock" % "1.0.0-RC9"

// akka
val akkaActor = "com.typesafe.akka" %% "akka-actor" % AkkaActorVersion // Scala 3 compatible
Expand Down Expand Up @@ -127,7 +128,7 @@ object Dependencies {
zioTestSbt
).map(_ % IntegrationTest)

val webapiTestDependencies = Seq(zioTest, zioTestSbt).map(_ % Test)
val webapiTestDependencies = Seq(zioTest, zioTestSbt, zioMock).map(_ % Test)

val webapiDependencies = Seq(
akkaActor,
Expand Down
10 changes: 5 additions & 5 deletions webapi/src/it/scala/org/knora/webapi/CoreSpec.scala
Expand Up @@ -9,22 +9,23 @@ import akka.actor
import akka.testkit.ImplicitSender
import akka.testkit.TestKitBase
import com.typesafe.scalalogging.Logger

import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.AppRouter
import org.knora.webapi.core.AppServer
import org.knora.webapi.core.TestStartupUtils
import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.store.cache.settings.CacheServiceSettings
import org.knora.webapi.util.LogAspect
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import zio._
import zio.logging.backend.SLF4J

import scala.concurrent.ExecutionContext

import org.knora.webapi.responders.ActorDeps

abstract class CoreSpec
extends AnyWordSpec
with TestKitBase
Expand Down Expand Up @@ -87,9 +88,8 @@ abstract class CoreSpec
val appActor = router.ref

// needed by some tests
val appConfig = config
val cacheServiceSettings = new CacheServiceSettings(appConfig)
val responderData = ResponderData(system, appActor, appConfig, cacheServiceSettings)
val appConfig = config
seakayone marked this conversation as resolved.
Show resolved Hide resolved
val responderData = ResponderData(ActorDeps(system, appActor, appConfig.defaultTimeoutAsDuration), appConfig)

final override def beforeAll(): Unit =
/* Here we start our app and initialize the repository before each suit runs */
Expand Down
Expand Up @@ -11,6 +11,9 @@ import zio.ZLayer
import org.knora.webapi.auth.JWTService
import org.knora.webapi.config.AppConfig
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.responders.ActorToZioBridge
import org.knora.webapi.responders.admin.ProjectsService
import org.knora.webapi.routing.ApiRoutes
import org.knora.webapi.routing.admin.ProjectsRouteZ
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoute
Expand Down Expand Up @@ -54,7 +57,9 @@ object LayersLive {
*/
val dspLayersLive: ULayer[DspEnvironmentLive] =
ZLayer.make[DspEnvironmentLive](
ActorDeps.layer,
ActorSystem.layer,
ActorToZioBridge.live,
ApiRoutes.layer,
AppConfig.live,
AppRouter.layer,
Expand All @@ -67,6 +72,7 @@ object LayersLive {
IriConverter.layer,
JWTService.layer,
ProjectsRouteZ.layer,
ProjectsService.layer,
RepositoryUpdater.layer,
ResourceInfoRepo.layer,
ResourceInfoRoute.layer,
Expand Down
Expand Up @@ -6,7 +6,6 @@
package org.knora.webapi.core.actors

import akka.actor.Actor
import akka.actor.ActorSystem
import com.typesafe.scalalogging.Logger

import scala.concurrent.ExecutionContext
Expand Down Expand Up @@ -39,6 +38,7 @@ import org.knora.webapi.messages.v2.responder.resourcemessages.ResourcesResponde
import org.knora.webapi.messages.v2.responder.searchmessages.SearchResponderRequestV2
import org.knora.webapi.messages.v2.responder.standoffmessages.StandoffResponderRequestV2
import org.knora.webapi.messages.v2.responder.valuemessages.ValuesResponderRequestV2
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.responders.admin.GroupsResponderADM
import org.knora.webapi.responders.admin.ListsResponderADM
import org.knora.webapi.responders.admin.PermissionsResponderADM
Expand Down Expand Up @@ -67,59 +67,47 @@ import org.knora.webapi.store.iiif.IIIFServiceManager
import org.knora.webapi.store.triplestore.TriplestoreServiceManager
import org.knora.webapi.util.ActorUtil

class RoutingActor(
final case class RoutingActor(
cacheServiceManager: CacheServiceManager,
iiifServiceManager: IIIFServiceManager,
triplestoreManager: TriplestoreServiceManager,
appConfig: AppConfig,
runtime: zio.Runtime[Any]
) extends Actor {

implicit val system: ActorSystem = context.system
val log: Logger = Logger(this.getClass)

/**
* The Cache Service's configuration.
*/
implicit val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)

/**
* Provides the default global execution context
*/
implicit val executionContext: ExecutionContext = context.dispatcher

/**
* Data used in responders.
*/
val responderData: ResponderData = ResponderData(system, self, appConfig, cacheServiceSettings)
private val log: Logger = Logger(this.getClass)
private val actorDeps: ActorDeps = ActorDeps(context.system, self, appConfig.defaultTimeoutAsDuration)
private val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)
private val responderData: ResponderData = ResponderData(actorDeps, appConfig)
private implicit val executionContext: ExecutionContext = actorDeps.executionContext

// V1 responders
val ckanResponderV1: CkanResponderV1 = new CkanResponderV1(responderData)
val resourcesResponderV1: ResourcesResponderV1 = new ResourcesResponderV1(responderData)
val valuesResponderV1: ValuesResponderV1 = new ValuesResponderV1(responderData)
val standoffResponderV1: StandoffResponderV1 = new StandoffResponderV1(responderData)
val usersResponderV1: UsersResponderV1 = new UsersResponderV1(responderData)
val listsResponderV1: ListsResponderV1 = new ListsResponderV1(responderData)
val searchResponderV1: SearchResponderV1 = new SearchResponderV1(responderData)
val ontologyResponderV1: OntologyResponderV1 = new OntologyResponderV1(responderData)
val projectsResponderV1: ProjectsResponderV1 = new ProjectsResponderV1(responderData)
private val ckanResponderV1: CkanResponderV1 = new CkanResponderV1(responderData)
private val resourcesResponderV1: ResourcesResponderV1 = new ResourcesResponderV1(responderData)
private val valuesResponderV1: ValuesResponderV1 = new ValuesResponderV1(responderData)
private val standoffResponderV1: StandoffResponderV1 = new StandoffResponderV1(responderData)
private val usersResponderV1: UsersResponderV1 = new UsersResponderV1(responderData)
private val listsResponderV1: ListsResponderV1 = new ListsResponderV1(responderData)
private val searchResponderV1: SearchResponderV1 = new SearchResponderV1(responderData)
private val ontologyResponderV1: OntologyResponderV1 = new OntologyResponderV1(responderData)
private val projectsResponderV1: ProjectsResponderV1 = ProjectsResponderV1(actorDeps)

// V2 responders
val ontologiesResponderV2: OntologyResponderV2 = new OntologyResponderV2(responderData)
val searchResponderV2: SearchResponderV2 = new SearchResponderV2(responderData)
val resourcesResponderV2: ResourcesResponderV2 = new ResourcesResponderV2(responderData)
val valuesResponderV2: ValuesResponderV2 = new ValuesResponderV2(responderData)
val standoffResponderV2: StandoffResponderV2 = new StandoffResponderV2(responderData)
val listsResponderV2: ListsResponderV2 = new ListsResponderV2(responderData)
private val ontologiesResponderV2: OntologyResponderV2 = new OntologyResponderV2(responderData)
private val searchResponderV2: SearchResponderV2 = new SearchResponderV2(responderData)
private val resourcesResponderV2: ResourcesResponderV2 = new ResourcesResponderV2(responderData)
private val valuesResponderV2: ValuesResponderV2 = new ValuesResponderV2(responderData)
private val standoffResponderV2: StandoffResponderV2 = new StandoffResponderV2(responderData)
private val listsResponderV2: ListsResponderV2 = new ListsResponderV2(responderData)

// Admin responders
val groupsResponderADM: GroupsResponderADM = new GroupsResponderADM(responderData)
val listsResponderADM: ListsResponderADM = new ListsResponderADM(responderData)
val permissionsResponderADM: PermissionsResponderADM = new PermissionsResponderADM(responderData)
val projectsResponderADM: ProjectsResponderADM = new ProjectsResponderADM(responderData)
val storeResponderADM: StoresResponderADM = new StoresResponderADM(responderData)
val usersResponderADM: UsersResponderADM = new UsersResponderADM(responderData)
val sipiRouterADM: SipiResponderADM = new SipiResponderADM(responderData)
private val groupsResponderADM: GroupsResponderADM = new GroupsResponderADM(responderData)
private val listsResponderADM: ListsResponderADM = new ListsResponderADM(responderData)
private val permissionsResponderADM: PermissionsResponderADM = new PermissionsResponderADM(responderData)
private val projectsResponderADM: ProjectsResponderADM = ProjectsResponderADM(actorDeps, cacheServiceSettings)
private val storeResponderADM: StoresResponderADM = new StoresResponderADM(responderData)
private val usersResponderADM: UsersResponderADM = new UsersResponderADM(responderData)
private val sipiRouterADM: SipiResponderADM = new SipiResponderADM(responderData)

def receive: Receive = {

Expand Down Expand Up @@ -183,5 +171,4 @@ class RoutingActor(
s"RoutingActor received an unexpected message $other of type ${other.getClass.getCanonicalName}"
)
}

}
Expand Up @@ -54,14 +54,6 @@ trait InstrumentationSupport {
}
}

// def counter(name: String) = Kamon.metrics.counter(name)
// def minMaxCounter(name: String) = Kamon.metrics.minMaxCounter(name)
// def time[A](name: String)(thunk: => A) = Latency.measure(Kamon.metrics.histogram(name))(thunk)
// def traceFuture[A](name:String)(future: => Future[A]):Future[A] =
// Tracer.withContext(Kamon.tracer.newContext(name)) {
// future.andThen { case completed ⇒ Tracer.currentContext.finish() }(SameThreadExecutionContext)
// }

/**
* Based on the current class name, create a logger with the name in the
* form 'M-ClassName', e.g., 'M-RedisManager'.
Expand Down
Expand Up @@ -7,20 +7,25 @@ package org.knora.webapi.messages.util

import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.util.Timeout

import scala.concurrent.ExecutionContext

import org.knora.webapi.config.AppConfig
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.store.cache.settings.CacheServiceSettings

/**
* Data needed to be passed to each responder.
*
* @param system the actor system.
* @param appActor the main application actor.
* @param cacheServiceSettings the cache service part of the settings.
* @param actorDeps all dependencies necessary for interacting with the [[org.knora.webapi.core.actors.RoutingActor]]
* @param appConfig the application configuration for creating the [[CacheServiceSettings]]
*/
case class ResponderData(
system: ActorSystem,
appActor: ActorRef,
appConfig: AppConfig,
cacheServiceSettings: CacheServiceSettings
)
case class ResponderData(actorDeps: ActorDeps, appConfig: AppConfig) {
val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)

val appActor: ActorRef = actorDeps.appActor
val executionContext: ExecutionContext = actorDeps.executionContext
val system: ActorSystem = actorDeps.system
val timeout: Timeout = actorDeps.timeout
}
Expand Up @@ -7,7 +7,6 @@ package org.knora.webapi.messages.util.search.gravsearch.types

import akka.actor.ActorRef

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import dsp.errors.GravsearchException
Expand All @@ -16,7 +15,6 @@ import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.messages.util.search._
import org.knora.webapi.settings.KnoraDispatchers

/**
* Runs Gravsearch type inspection using one or more type inspector implementations.
Expand All @@ -30,8 +28,7 @@ class GravsearchTypeInspectionRunner(
responderData: ResponderData,
inferTypes: Boolean = true
) {
private implicit val executionContext: ExecutionContext =
responderData.system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
private implicit val executionContext = responderData.actorDeps.executionContext

// If inference was requested, construct an inferring type inspector.
private val maybeInferringTypeInspector: Option[GravsearchTypeInspector] = if (inferTypes) {
Expand Down
Expand Up @@ -14,7 +14,6 @@ import scala.concurrent.Future
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.messages.util.search.WhereClause
import org.knora.webapi.settings.KnoraDispatchers

/**
* An trait whose implementations can get type information from a parsed Gravsearch query in different ways.
Expand All @@ -29,10 +28,9 @@ abstract class GravsearchTypeInspector(
responderData: ResponderData
) {

protected val system: ActorSystem = responderData.system
protected implicit val executionContext: ExecutionContext =
system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
protected implicit val timeout: Timeout = responderData.appConfig.defaultTimeoutAsDuration
protected val system: ActorSystem = responderData.system
protected implicit val executionContext: ExecutionContext = responderData.executionContext
protected implicit val timeout: Timeout = responderData.timeout

/**
* Given the WHERE clause from a parsed Gravsearch query, returns information about the types found
Expand Down
@@ -0,0 +1,54 @@
/*
* Copyright © 2021 - 2022 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.responders
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.util.Timeout
import zio.ZIO
import zio.ZLayer

import scala.concurrent.ExecutionContext

import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.AppRouter
import org.knora.webapi.settings.KnoraDispatchers

/**
* Class encapsulating all Akka dependencies necessary to interact with the [[org.knora.webapi.core.actors.RoutingActor]] aka. "appActor"
*
* When using this class in a service depending on the routing actor this will provide the necessary implicit dependencies for using the ask pattern
* whilst making the dependency explicit for ZIO layers.
*
* @example Usage in client code:
* {{{
* final case class YourService(actorDeps: ActorDeps){
* private implicit val ec: ExecutionContext = actorDeps.executionContext
* private implicit val timeout: Timeout = actorDeps.timeout
*
* private val appActor: ActorRef = actorDeps.appActor
*
* def someMethod = appActor.ask(SomeMessage())...
* }
* }}}
*
* @param system the akka.core.ActorSystem - used to extract the [[ExecutionContext]] from
* @param appActor a reference to the [[org.knora.webapi.core.actors.RoutingActor]]
* @param timeout the timeout needed for the ask pattern
*/
final case class ActorDeps(system: ActorSystem, appActor: ActorRef, timeout: Timeout) {
val executionContext: ExecutionContext = system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
}

object ActorDeps {
val layer: ZLayer[AppConfig with AppRouter, Nothing, ActorDeps] = ZLayer.fromZIO {
for {
router <- ZIO.service[AppRouter]
system = router.system
appActor = router.ref
timeout <- ZIO.service[AppConfig].map(_.defaultTimeoutAsDuration)
} yield ActorDeps(system, appActor, timeout)
}
}
@@ -0,0 +1,47 @@
package org.knora.webapi.responders
import akka.actor.Actor
import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
import zio.Tag
import zio.Task
import zio.URLayer
import zio.ZIO
import zio.ZLayer

import scala.reflect.ClassTag

import org.knora.webapi.messages.ResponderRequest

/**
* This trait encapsulates the [[akka.pattern.ask]] into the zio world
*/
trait ActorToZioBridge {

/**
* Sends a message to the "appActor" [[org.knora.webapi.core.actors.RoutingActor]] using the [[akka.pattern.ask]],
* casts and returns the response to the expected return type `R` as [[Task]].
*
* @param message The message sent to the actor
* @param tag implicit proof that the result type `R` has a [[ClassTag]]
*
* @tparam R The type of the expected success value
* @return A Task containing either the success `R` or the failure [[Throwable]],
* will fail during runtime with a [[ClassCastException]] if the `R` does not correspond
* to the response of the message being sent due to the untyped nature of the ask pattern
*/
def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R]

}

final case class ActorToZioBridgeLive(actorDeps: ActorDeps) extends ActorToZioBridge {
private implicit val timeout: Timeout = actorDeps.timeout
private val appActor: ActorRef = actorDeps.appActor

override def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R] =
ZIO.fromFuture(_ => appActor.ask(message, Actor.noSender).mapTo[R])
}

object ActorToZioBridge {
val live: URLayer[ActorDeps, ActorToZioBridgeLive] = ZLayer.fromFunction(ActorToZioBridgeLive.apply _)
}