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

eventstore.operations.SubscriptionOperation.scala Maven / Gradle / Ivy

The newest version!
package eventstore
package operations

import akka.actor.ActorRef
import eventstore.NotHandled.{ TooBusy, NotReady }
import eventstore.tcp.PackOut
import eventstore.SubscriptionDropped.AccessDenied
import eventstore.operations.OnIncoming._
import scala.util.{ Success, Failure, Try }

private[eventstore] sealed trait SubscriptionOperation extends Operation {
  def stream: EventStream

  def pack: PackOut

  def id = pack.correlationId

  protected def unexpected(actual: Any, expectedClass: Class[_]): OnIncoming = {
    val expected = expectedClass.getSimpleName
    val msg = s"Expected: $expected, actual: $actual"
    Stop(new CommandNotExpectedException(msg))
  }

  protected def retry: OnIncoming = Retry(this, pack)

  protected def accessDenied(msg: String): OnIncoming = Stop(new AccessDeniedException(msg))
}

private[eventstore] object SubscriptionOperation {

  def apply(
    subscribeTo: SubscribeTo,
    pack: PackOut,
    client: ActorRef,
    ongoing: Boolean): Operation = {
    Subscribing(subscribeTo, pack, client, ongoing, 0)
  }

  case class Subscribing(
      subscribeTo: SubscribeTo,
      pack: PackOut,
      client: ActorRef,
      ongoing: Boolean,
      version: Int) extends SubscriptionOperation {

    def stream = subscribeTo.stream

    def clientTerminated = Some(pack.copy(message = Unsubscribe))

    def inspectIn(in: Try[In]) = {
      def subscribed =
        if (!ongoing) Ignore
        else Continue(Subscribed(subscribeTo, pack, client, ongoing, version + 1), in)

      def unexpected(x: Any) = this.unexpected(x, Completed.expected)

      in match {
        case Success(Completed())            => subscribed
        case Success(_: StreamEventAppeared) => Continue(this, in)
        case Success(x)                      => unexpected(x)
        case Failure(AccessDenied)           => accessDenied(s"Subscription to $stream failed due to access denied")
        case Failure(OperationTimedOut)      => Stop(OperationTimeoutException(pack))
        case Failure(NotHandled(NotReady))   => retry
        case Failure(NotHandled(TooBusy))    => retry
        case Failure(BadRequest)             => Stop(new ServerErrorException(s"Bad request: $pack"))
        case Failure(NotAuthenticated)       => Stop(NotAuthenticatedException(pack))
        case Failure(x)                      => unexpected(x)
      }
    }

    def inspectOut = {
      case Unsubscribe => OnOutgoing.Stop(pack.copy(message = Unsubscribe), Try(Unsubscribed))
    }

    def disconnected = {
      val operation = if (ongoing) copy(ongoing = false) else this
      OnDisconnected.Continue(operation)
    }

    def connected = {
      val operation = if (ongoing) this else copy(ongoing = true)
      OnConnected.Retry(operation, pack)
    }

    object Completed {
      def unapply(x: SubscribeCompleted): Boolean = (stream, x) match {
        case (EventStream.All, _: SubscribeToAllCompleted) => true
        case (_: EventStream.Id, _: SubscribeToStreamCompleted) => true
        case _ => false
      }

      def expected: Class[_] = stream match {
        case EventStream.All   => classOf[SubscribeToAllCompleted]
        case _: EventStream.Id => classOf[SubscribeToStreamCompleted]
      }
    }
  }

  case class Subscribed(
      subscribeTo: SubscribeTo,
      pack: PackOut,
      client: ActorRef,
      ongoing: Boolean,
      version: Int) extends SubscriptionOperation {

    def stream = subscribeTo.stream

    def inspectIn(in: Try[In]): OnIncoming = {
      def unexpected(x: Any) = this.unexpected(x, classOf[StreamEventAppeared])

      in match {
        case Success(_: StreamEventAppeared) => Continue(this, in)
        case Success(Unsubscribed)           => Stop(in)
        case Success(x)                      => unexpected(x)
        case Failure(AccessDenied)           => accessDenied(s"Subscription on $stream dropped due to access denied")
        case Failure(x)                      => unexpected(x)
      }
    }

    def clientTerminated = Some(pack.copy(message = Unsubscribe))

    def inspectOut = {
      case Unsubscribe =>
        val pack = this.pack.copy(message = Unsubscribe)
        val operation = Unsubscribing(stream, pack, client, ongoing, version + 1)
        OnOutgoing.Continue(operation, pack)
    }

    def disconnected = {
      val operation = Subscribing(subscribeTo, pack, client, ongoing = false, version + 1)
      OnDisconnected.Continue(operation)
    }

    def connected = {
      val operation = Subscribing(subscribeTo, pack, client, ongoing = true, version + 1)
      OnConnected.Retry(operation, pack)
    }
  }

  case class Unsubscribing(
      stream: EventStream,
      pack: PackOut,
      client: ActorRef,
      ongoing: Boolean,
      version: Int) extends SubscriptionOperation {

    def inspectIn(in: Try[In]) = {
      def unexpected(x: Any) = this.unexpected(x, Unsubscribed.getClass)

      in match {
        case Success(Unsubscribed)           => Stop(Unsubscribed)
        case Success(_: StreamEventAppeared) => Ignore
        case Success(x)                      => unexpected(x)
        case Failure(AccessDenied)           => accessDenied(s"Unsubscribed from $stream due to access denied")
        case Failure(OperationTimedOut)      => Stop(OperationTimeoutException(pack))
        case Failure(NotHandled(NotReady))   => retry
        case Failure(NotHandled(TooBusy))    => retry
        case Failure(BadRequest)             => Stop(new ServerErrorException(s"Bad request: $pack"))
        case Failure(x)                      => unexpected(x)
      }
    }

    def clientTerminated = None

    def inspectOut = PartialFunction.empty

    def disconnected = OnDisconnected.Stop(Try(Unsubscribed))

    def connected = OnConnected.Stop(Try(Unsubscribed))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy