All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eventstore.akka.SubscriptionActor.scala Maven / Gradle / Ivy

package eventstore
package akka

import scala.annotation.tailrec
import scala.collection.immutable.Queue
import _root_.akka.actor.{ActorRef, Props}
import ReadDirection.Forward

object SubscriptionActor {

  def props(
    connection:            ActorRef,
    client:                ActorRef,
    fromPositionExclusive: Option[Position],
    credentials:           Option[UserCredentials],
    settings:              Settings
  ): Props = {

    Props(new SubscriptionActor(
      connection = connection,
      client = client,
      fromPositionExclusive = fromPositionExclusive,
      credentials = credentials,
      settings = settings
    ))
  }

  /**
   * Java API
   */
  def getProps(
    connection:            ActorRef,
    client:                ActorRef,
    fromPositionExclusive: Position.Exact,
    credentials:           UserCredentials,
    settings:              Settings
  ): Props = props(
    connection,
    client,
    Option(fromPositionExclusive),
    Option(credentials),
    Option(settings).getOrElse(Settings.Default)
  )

}

private[eventstore] class SubscriptionActor(
    val connection:        ActorRef,
    val client:            ActorRef,
    fromPositionExclusive: Option[Position],
    val credentials:       Option[UserCredentials],
    val settings:          Settings
) extends AbstractSubscriptionActor[IndexedEvent] {

  type Next = Position.Exact
  type Last = Option[Position.Exact]

  val streamId = EventStream.All

  def receive = fromPositionExclusive match {
    case Some(Position.Last)     => subscribingFromLast()
    case Some(x: Position.Exact) => reading(Some(x), x, ready = true)
    case None                    => reading(None, Position.First, ready = true)
  }

  def reading(last: Last, next: Next, ready: Boolean): Receive = {
    def rcv(ready: Boolean) = {
      def read(events: List[IndexedEvent], next: Position.Exact) = {
        if (events.isEmpty) subscribing(last, next)
        else {
          val l = process(last, events)
          whenReady(reading(l, next, ready = false), ready)
        }
      }

      rcvReadCompleted(read) or rcvFailure
    }

    readEventsFrom(next)
    rcv(ready) or rcvReady(rcv(ready = true))
  }

  def subscribing(last: Last, next: Next): Receive = {
    def subscribed(position: Long) = {
      if (last.exists(_.commitPosition >= position)) liveProcessing(last, Queue())
      else catchingUp(last, next, position, Queue())
    }

    subscribeToStream()
    rcvSubscribeCompleted(subscribed) or
      rcvFailureOrUnsubscribe
  }

  def subscribingFromLast(): Receive = {
    def subscribed: Receive =
      liveProcessing(None, Queue())

    subscribeToStream()
    rcvSubscribeCompleted(_ => subscribed) or
      rcvFailureOrUnsubscribe
  }

  def catchingUp(last: Last, next: Next, subscriptionCommit: Long, stash: Queue[IndexedEvent]): Receive = {
    def catchUp(subscriptionCommit: Long, stash: Queue[IndexedEvent]): Receive = {
      def read(events: List[IndexedEvent], next: Position.Exact) = {
        if (events.isEmpty) liveProcessing(last, stash)
        else {
          @tailrec def loop(events: List[IndexedEvent], last: Last): Receive = events match {
            case Nil => catchingUp(last, next, subscriptionCommit, stash)
            case event :: tail =>
              val position = event.position
              if (last.exists(_ >= position)) loop(tail, last)
              else if (position.commitPosition > subscriptionCommit) liveProcessing(last, stash)
              else {
                toClient(event)
                loop(tail, Some(position))
              }
          }
          loop(events, last)
        }
      }

      def eventAppeared(event: IndexedEvent) = {
        catchUp(subscriptionCommit, stash enqueue event)
      }

      def subscribed(position: Long) = {
        catchUp(position, Queue())
      }

      rcvReadCompleted(read) or
        rcvEventAppeared(eventAppeared) or
        rcvSubscribeCompleted(subscribed) or
        rcvFailureOrUnsubscribe
    }

    readEventsFrom(next)
    catchUp(subscriptionCommit, stash)
  }

  def liveProcessing(last: Last, stash: Queue[IndexedEvent]): Receive = {
    def liveProcessing(last: Last, n: Long, ready: Boolean): Receive = {
      def eventAppeared(event: IndexedEvent) = {
        val l = process(last, event)
        if (n < settings.readBatchSize) liveProcessing(l, n + 1, ready)
        else {
          checkReadiness()
          if (ready) liveProcessing(l, 0, ready = false)
          else {
            unsubscribe()
            rcvReady(reading(l, l getOrElse Position.First, ready = false)) or
              ignoreUnsubscribed or
              rcvFailure
          }
        }
      }

      def subscribed(position: Long) = {
        last match {
          case Some(l) if position > l.commitPosition => catchingUp(Some(l), l, position, Queue())
          case _                                      => liveProcessing(last, n, ready)
        }
      }

      rcvEventAppeared(eventAppeared) or
        rcvReady(liveProcessing(last, n, ready = true)) or
        rcvSubscribeCompleted(subscribed) or
        rcvFailureOrUnsubscribe
    }

    client ! LiveProcessingStarted
    liveProcessing(process(last, stash), 0, ready = true)
  }

  def process(lastPosition: Option[Position.Exact], event: IndexedEvent): Last = {
    val position = event.position
    if (lastPosition.exists(_ >= position)) lastPosition
    else {
      toClient(event)
      Some(position)
    }
  }

  def readEventsFrom(position: Next) = {
    val msg = ReadAllEvents(
      position, settings.readBatchSize,
      Forward,
      resolveLinkTos = settings.resolveLinkTos,
      requireMaster = settings.requireMaster
    )
    toConnection(msg)
  }

  def rcvReadCompleted(receive: (List[IndexedEvent], Position.Exact) => Receive): Receive = {
    case ReadAllEventsCompleted(events, _, next, Forward) => context become receive(events, next)
  }

  def rcvSubscribeCompleted(receive: Long => Receive): Receive = {
    case SubscribeToAllCompleted(x) => context become receive(x)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy