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

akka.io.Pipelines.scala Maven / Gradle / Ivy

/**
 * Borrowed from Akka 2.2.4 due to the removal of pipeline in Akka 2.3
 * TODO Remove after migrating to Akka 2.4 reactive stream
 * https://github.com/debasishg/scala-redis-nb/issues/65
 * https://github.com/debasishg/scala-redis-nb/issues/66
 *
 * Copyright (C) 2009-2013 Typesafe Inc. 
 */

package akka.io

import java.lang.{ Iterable ⇒ JIterable }
import scala.annotation.tailrec
import scala.util.{ Try, Success, Failure }
import java.nio.ByteOrder
import akka.util.ByteString
import scala.collection.mutable
import akka.actor.{ NoSerializationVerificationNeeded, ActorContext }
import scala.concurrent.duration.FiniteDuration
import scala.collection.mutable.WrappedArray
import scala.concurrent.duration.Deadline
import scala.beans.BeanProperty
import akka.event.LoggingAdapter

/**
 * Scala API: A pair of pipes, one for commands and one for events, plus a
 * management port. Commands travel from top to bottom, events from bottom to
 * top. All messages which need to be handled “in-order” (e.g. top-down or
 * bottom-up) need to be either events or commands; management messages are
 * processed in no particular order.
 *
 * Java base classes are provided in the form of [[AbstractPipePair]]
 * and [[AbstractSymmetricPipePair]] since the Scala function types can be
 * awkward to handle in Java.
 *
 * @see [[PipelineStage]]
 * @see [[AbstractPipePair]]
 * @see [[AbstractSymmetricPipePair]]
 * @see [[PipePairFactory]]
 */
trait PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] {

  type Result = Either[EvtAbove, CmdBelow]
  type Mgmt = PartialFunction[AnyRef, Iterable[Result]]

  /**
   * The command pipeline transforms injected commands from the upper stage
   * into commands for the stage below, but it can also emit events for the
   * upper stage. Any number of each can be generated.
   */
  def commandPipeline: CmdAbove ⇒ Iterable[Result]

  /**
   * The event pipeline transforms injected event from the lower stage
   * into event for the stage above, but it can also emit commands for the
   * stage below. Any number of each can be generated.
   */
  def eventPipeline: EvtBelow ⇒ Iterable[Result]

  /**
   * The management port allows sending broadcast messages to all stages
   * within this pipeline. This can be used to communicate with stages in the
   * middle without having to thread those messages through the surrounding
   * stages. Each stage can generate events and commands in response to a
   * command, and the aggregation of all those is returned.
   *
   * The default implementation ignores all management commands.
   */
  def managementPort: Mgmt = PartialFunction.empty
}

/**
 * A convenience type for expressing a [[PipePair]] which has the same types
 * for commands and events.
 */
trait SymmetricPipePair[Above, Below] extends PipePair[Above, Below, Above, Below]

/**
 * Java API: A pair of pipes, one for commands and one for events. Commands travel from
 * top to bottom, events from bottom to top.
 *
 * @see [[PipelineStage]]
 * @see [[AbstractSymmetricPipePair]]
 * @see [[PipePairFactory]]
 */
abstract class AbstractPipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] {

  /**
   * Commands reaching this pipe pair are transformed into a sequence of
   * commands for the next or events for the previous stage.
   *
   * Throwing exceptions within this method will abort processing of the whole
   * pipeline which this pipe pair is part of.
   *
   * @param cmd the incoming command
   * @return an Iterable of elements which are either events or commands
   *
   * @see [[#makeCommand]]
   * @see [[#makeEvent]]
   */
  def onCommand(cmd: CmdAbove): JIterable[Either[EvtAbove, CmdBelow]]

  /**
   * Events reaching this pipe pair are transformed into a sequence of
   * commands for the next or events for the previous stage.
   *
   * Throwing exceptions within this method will abort processing of the whole
   * pipeline which this pipe pair is part of.
   *
   * @param cmd the incoming command
   * @return an Iterable of elements which are either events or commands
   *
   * @see [[#makeCommand]]
   * @see [[#makeEvent]]
   */
  def onEvent(event: EvtBelow): JIterable[Either[EvtAbove, CmdBelow]]

  /**
   * Management commands are sent to all stages in a broadcast fashion,
   * conceptually in parallel (but not actually executing a stage
   * reentrantly in case of events or commands being generated in response
   * to a management command).
   */
  def onManagementCommand(cmd: AnyRef): JIterable[Either[EvtAbove, CmdBelow]] =
    java.util.Collections.emptyList()

  /**
   * Helper method for wrapping a command which shall be emitted.
   */
  def makeCommand(cmd: CmdBelow): Either[EvtAbove, CmdBelow] = Right(cmd)

  /**
   * Helper method for wrapping an event which shall be emitted.
   */
  def makeEvent(event: EvtAbove): Either[EvtAbove, CmdBelow] = Left(event)

  /**
   * INTERNAL API: do not touch!
   */
  private[io] val _internal$cmd = {
    val l = new java.util.ArrayList[AnyRef](1)
    l add null
    l
  }
  /**
   * INTERNAL API: do not touch!
   */
  private[io] val _internal$evt = {
    val l = new java.util.ArrayList[AnyRef](1)
    l add null
    l
  }

  /**
   * Wrap a single command for efficient return to the pipeline’s machinery.
   * This method avoids allocating a [[scala.util.Right]] and an [[java.lang.Iterable]] by reusing
   * one such instance within the AbstractPipePair, hence it can be used ONLY ONCE by
   * each pipeline stage. Prototypic and safe usage looks like this:
   *
   * {{{
   * final MyResult result = ... ;
   * return singleCommand(result);
   * }}}
   *
   * @see PipelineContext#singleCommand
   */
  def singleCommand(cmd: CmdBelow): JIterable[Either[EvtAbove, CmdBelow]] = {
    _internal$cmd.set(0, cmd.asInstanceOf[AnyRef])
    _internal$cmd.asInstanceOf[JIterable[Either[EvtAbove, CmdBelow]]]
  }

  /**
   * Wrap a single event for efficient return to the pipeline’s machinery.
   * This method avoids allocating a [[scala.util.Left]] and an [[java.lang.Iterable]] by reusing
   * one such instance within the AbstractPipePair, hence it can be used ONLY ONCE by
   * each pipeline stage. Prototypic and safe usage looks like this:
   *
   * {{{
   * final MyResult result = ... ;
   * return singleEvent(result);
   * }}}
   *
   * @see PipelineContext#singleEvent
   */
  def singleEvent(evt: EvtAbove): JIterable[Either[EvtAbove, CmdBelow]] = {
    _internal$evt.set(0, evt.asInstanceOf[AnyRef])
    _internal$evt.asInstanceOf[JIterable[Either[EvtAbove, CmdBelow]]]
  }

  /**
   * INTERNAL API: Dealias a possibly optimized return value such that it can
   * be safely used; this is never needed when only using public API.
   */
  def dealias[Cmd, Evt](msg: JIterable[Either[Evt, Cmd]]): JIterable[Either[Evt, Cmd]] = {
    import java.util.Collections.singletonList
    if (msg eq _internal$cmd) singletonList(Right(_internal$cmd.get(0).asInstanceOf[Cmd]))
    else if (msg eq _internal$evt) singletonList(Left(_internal$evt.get(0).asInstanceOf[Evt]))
    else msg
  }
}

/**
 * A convenience type for expressing a [[AbstractPipePair]] which has the same types
 * for commands and events.
 */
abstract class AbstractSymmetricPipePair[Above, Below] extends AbstractPipePair[Above, Below, Above, Below]

/**
 * This class contains static factory methods which produce [[PipePair]]
 * instances; those are needed within the implementation of [[PipelineStage#apply]].
 */
object PipePairFactory {

  /**
   * Scala API: construct a [[PipePair]] from the two given functions; useful for not capturing `$outer` references.
   */
  def apply[CmdAbove, CmdBelow, EvtAbove, EvtBelow] //
  (commandPL: CmdAbove ⇒ Iterable[Either[EvtAbove, CmdBelow]],
   eventPL: EvtBelow ⇒ Iterable[Either[EvtAbove, CmdBelow]],
   management: PartialFunction[AnyRef, Iterable[Either[EvtAbove, CmdBelow]]] = PartialFunction.empty) =
    new PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] {
      override def commandPipeline = commandPL
      override def eventPipeline = eventPL
      override def managementPort = management
    }

  private abstract class Converter[CmdAbove <: AnyRef, CmdBelow <: AnyRef, EvtAbove <: AnyRef, EvtBelow <: AnyRef] //
  (val ap: AbstractPipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow], ctx: PipelineContext) {
    import scala.collection.JavaConverters._
    protected def normalize(output: JIterable[Either[EvtAbove, CmdBelow]]): Iterable[Either[EvtAbove, CmdBelow]] =
      if (output == java.util.Collections.EMPTY_LIST) Nil
      else if (output eq ap._internal$cmd) ctx.singleCommand(ap._internal$cmd.get(0).asInstanceOf[CmdBelow])
      else if (output eq ap._internal$evt) ctx.singleEvent(ap._internal$evt.get(0).asInstanceOf[EvtAbove])
      else output.asScala
  }

  /**
   * Java API: construct a [[PipePair]] from the given [[AbstractPipePair]].
   */
  def create[CmdAbove <: AnyRef, CmdBelow <: AnyRef, EvtAbove <: AnyRef, EvtBelow <: AnyRef] //
  (ctx: PipelineContext, ap: AbstractPipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow]) //
  : PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] =
    new Converter(ap, ctx) with PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] {
      override val commandPipeline = { cmd: CmdAbove ⇒ normalize(ap.onCommand(cmd)) }
      override val eventPipeline = { evt: EvtBelow ⇒ normalize(ap.onEvent(evt)) }
      override val managementPort: Mgmt = { case x ⇒ normalize(ap.onManagementCommand(x)) }
    }

  /**
   * Java API: construct a [[PipePair]] from the given [[AbstractSymmetricPipePair]].
   */
  def create[Above <: AnyRef, Below <: AnyRef] //
  (ctx: PipelineContext, ap: AbstractSymmetricPipePair[Above, Below]): SymmetricPipePair[Above, Below] =
    new Converter(ap, ctx) with SymmetricPipePair[Above, Below] {
      override val commandPipeline = { cmd: Above ⇒ normalize(ap.onCommand(cmd)) }
      override val eventPipeline = { evt: Below ⇒ normalize(ap.onEvent(evt)) }
      override val managementPort: Mgmt = { case x ⇒ normalize(ap.onManagementCommand(x)) }
    }
}

case class PipelinePorts[CmdAbove, CmdBelow, EvtAbove, EvtBelow](
  commands: CmdAbove ⇒ (Iterable[EvtAbove], Iterable[CmdBelow]),
  events: EvtBelow ⇒ (Iterable[EvtAbove], Iterable[CmdBelow]),
  management: PartialFunction[AnyRef, (Iterable[EvtAbove], Iterable[CmdBelow])])

/**
 * This class contains static factory methods which turn a pipeline context
 * and a [[PipelineStage]] into readily usable pipelines.
 */
object PipelineFactory {

  /**
   * Scala API: build the pipeline and return a pair of functions representing
   * the command and event pipelines. Each function returns the commands and
   * events resulting from running the pipeline on the given input, where the
   * the sequence of events is the first element of the returned pair and the
   * sequence of commands the second element.
   *
   * Exceptions thrown by the pipeline stages will not be caught.
   *
   * @param ctx The context object for this pipeline
   * @param stage The (composite) pipeline stage from whcih to build the pipeline
   * @return a pair of command and event pipeline functions
   */
  def buildFunctionTriple[Ctx <: PipelineContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] //
  (ctx: Ctx, stage: PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow]) //
  : PipelinePorts[CmdAbove, CmdBelow, EvtAbove, EvtBelow] = {
    val pp = stage apply ctx
    val split: (Iterable[Either[EvtAbove, CmdBelow]]) ⇒ (Iterable[EvtAbove], Iterable[CmdBelow]) = { in ⇒
      if (in.isEmpty) (Nil, Nil)
      else if (in eq ctx.cmd) (Nil, Seq[CmdBelow](ctx.cmd(0)))
      else if (in eq ctx.evt) (Seq[EvtAbove](ctx.evt(0)), Nil)
      else {
        val cmds = Vector.newBuilder[CmdBelow]
        val evts = Vector.newBuilder[EvtAbove]
        in foreach {
          case Right(cmd) ⇒ cmds += cmd
          case Left(evt)  ⇒ evts += evt
        }
        (evts.result, cmds.result)
      }
    }
    PipelinePorts(pp.commandPipeline andThen split, pp.eventPipeline andThen split, pp.managementPort andThen split)
  }

  /**
   * Scala API: build the pipeline attaching the given command and event sinks
   * to its outputs. Exceptions thrown within the pipeline stages will abort
   * processing (i.e. will not be processed in following stages) but will be
   * caught and passed as [[scala.util.Failure]] into the respective sink.
   *
   * Exceptions thrown while processing management commands are not caught.
   *
   * @param ctx The context object for this pipeline
   * @param stage The (composite) pipeline stage from whcih to build the pipeline
   * @param commandSink The function to invoke for commands or command failures
   * @param eventSink The function to invoke for events or event failures
   * @return a handle for injecting events or commands into the pipeline
   */
  def buildWithSinkFunctions[Ctx <: PipelineContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] //
  (ctx: Ctx,
   stage: PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow])(
     commandSink: Try[CmdBelow] ⇒ Unit,
     eventSink: Try[EvtAbove] ⇒ Unit): PipelineInjector[CmdAbove, EvtBelow] =
    new PipelineInjector[CmdAbove, EvtBelow] {
      val pl = stage(ctx)
      override def injectCommand(cmd: CmdAbove): Unit = {
        Try(pl.commandPipeline(cmd)) match {
          case f: Failure[_] ⇒ commandSink(f.asInstanceOf[Try[CmdBelow]])
          case Success(out) ⇒
            if (out.isEmpty) () // nothing
            else if (out eq ctx.cmd) commandSink(Success(ctx.cmd(0)))
            else if (out eq ctx.evt) eventSink(Success(ctx.evt(0)))
            else out foreach {
              case Right(cmd) ⇒ commandSink(Success(cmd))
              case Left(evt)  ⇒ eventSink(Success(evt))
            }
        }
      }
      override def injectEvent(evt: EvtBelow): Unit = {
        Try(pl.eventPipeline(evt)) match {
          case f: Failure[_] ⇒ eventSink(f.asInstanceOf[Try[EvtAbove]])
          case Success(out) ⇒
            if (out.isEmpty) () // nothing
            else if (out eq ctx.cmd) commandSink(Success(ctx.cmd(0)))
            else if (out eq ctx.evt) eventSink(Success(ctx.evt(0)))
            else out foreach {
              case Right(cmd) ⇒ commandSink(Success(cmd))
              case Left(evt)  ⇒ eventSink(Success(evt))
            }
        }
      }
      override def managementCommand(cmd: AnyRef): Unit = {
        val out = pl.managementPort(cmd)
        if (out.isEmpty) () // nothing
        else if (out eq ctx.cmd) commandSink(Success(ctx.cmd(0)))
        else if (out eq ctx.evt) eventSink(Success(ctx.evt(0)))
        else out foreach {
          case Right(cmd) ⇒ commandSink(Success(cmd))
          case Left(evt)  ⇒ eventSink(Success(evt))
        }
      }
    }

  /**
   * Java API: build the pipeline attaching the given callback object to its
   * outputs. Exceptions thrown within the pipeline stages will abort
   * processing (i.e. will not be processed in following stages) but will be
   * caught and passed as [[scala.util.Failure]] into the respective sink.
   *
   * Exceptions thrown while processing management commands are not caught.
   *
   * @param ctx The context object for this pipeline
   * @param stage The (composite) pipeline stage from whcih to build the pipeline
   * @param callback The [[PipelineSink]] to attach to the built pipeline
   * @return a handle for injecting events or commands into the pipeline
   */
  def buildWithSink[Ctx <: PipelineContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] //
  (ctx: Ctx,
   stage: PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow],
   callback: PipelineSink[CmdBelow, EvtAbove]): PipelineInjector[CmdAbove, EvtBelow] =
    buildWithSinkFunctions[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow](ctx, stage)({
      case Failure(thr) ⇒ callback.onCommandFailure(thr)
      case Success(cmd) ⇒ callback.onCommand(cmd)
    }, {
      case Failure(thr) ⇒ callback.onEventFailure(thr)
      case Success(evt) ⇒ callback.onEvent(evt)
    })
}

/**
 * A handle for injecting commands and events into a pipeline. Commands travel
 * down (or to the right) through the stages, events travel in the opposite
 * direction.
 *
 * @see [[PipelineFactory#buildWithSinkFunctions]]
 * @see [[PipelineFactory#buildWithSink]]
 */
trait PipelineInjector[Cmd, Evt] {

  /**
   * Inject the given command into the connected pipeline.
   */
  @throws(classOf[Exception])
  def injectCommand(cmd: Cmd): Unit

  /**
   * Inject the given event into the connected pipeline.
   */
  @throws(classOf[Exception])
  def injectEvent(event: Evt): Unit

  /**
   * Send a management command to all stages (in an unspecified order).
   */
  @throws(classOf[Exception])
  def managementCommand(cmd: AnyRef): Unit
}

/**
 * A sink which can be attached by [[PipelineFactory#buildWithSink]] to a
 * pipeline when it is being built. The methods are called when commands,
 * events or their failures occur during evaluation of the pipeline (i.e.
 * when injection is triggered using the associated [[PipelineInjector]]).
 */
abstract class PipelineSink[Cmd, Evt] {

  /**
   * This callback is invoked for every command generated by the pipeline.
   *
   * By default this does nothing.
   */
  @throws(classOf[Throwable])
  def onCommand(cmd: Cmd): Unit = ()

  /**
   * This callback is invoked if an exception occurred while processing an
   * injected command. If this callback is invoked that no other callbacks will
   * be invoked for the same injection.
   *
   * By default this will just throw the exception.
   */
  @throws(classOf[Throwable])
  def onCommandFailure(thr: Throwable): Unit = throw thr

  /**
   * This callback is invoked for every event generated by the pipeline.
   *
   * By default this does nothing.
   */
  @throws(classOf[Throwable])
  def onEvent(event: Evt): Unit = ()

  /**
   * This callback is invoked if an exception occurred while processing an
   * injected event. If this callback is invoked that no other callbacks will
   * be invoked for the same injection.
   *
   * By default this will just throw the exception.
   */
  @throws(classOf[Throwable])
  def onEventFailure(thr: Throwable): Unit = throw thr
}

/**
 * This base trait of each pipeline’s context provides optimized facilities
 * for generating single commands or events (i.e. the fast common case of 1:1
 * message transformations).
 *
 * IMPORTANT NOTICE:
 *
 * A PipelineContext MUST NOT be shared between multiple pipelines, it contains mutable
 * state without synchronization. You have been warned!
 *
 * @see AbstractPipelineContext see AbstractPipelineContext for a default implementation (Java)
 */
trait PipelineContext {

  /**
   * INTERNAL API: do not touch!
   */
  private val cmdHolder = new Array[AnyRef](1)
  /**
   * INTERNAL API: do not touch!
   */
  private val evtHolder = new Array[AnyRef](1)
  /**
   * INTERNAL API: do not touch!
   */
  private[io] val cmd = WrappedArray.make(cmdHolder)
  /**
   * INTERNAL API: do not touch!
   */
  private[io] val evt = WrappedArray.make(evtHolder)

  /**
   * Scala API: Wrap a single command for efficient return to the pipeline’s machinery.
   * This method avoids allocating a [[scala.util.Right]] and an [[scala.collection.Iterable]] by reusing
   * one such instance within the PipelineContext, hence it can be used ONLY ONCE by
   * each pipeline stage. Prototypic and safe usage looks like this:
   *
   * {{{
   * override val commandPipeline = { cmd =>
   *   val myResult = ...
   *   ctx.singleCommand(myResult)
   * }
   * }}}
   *
   * @see AbstractPipePair#singleCommand see AbstractPipePair for the Java API
   */
  def singleCommand[Cmd <: AnyRef, Evt <: AnyRef](cmd: Cmd): Iterable[Either[Evt, Cmd]] = {
    cmdHolder(0) = cmd
    this.cmd
  }

  /**
   * Scala API: Wrap a single event for efficient return to the pipeline’s machinery.
   * This method avoids allocating a [[scala.util.Left]] and an [[scala.collection.Iterable]] by reusing
   * one such instance within the context, hence it can be used ONLY ONCE by
   * each pipeline stage. Prototypic and safe usage looks like this:
   *
   * {{{
   * override val eventPipeline = { cmd =>
   *   val myResult = ...
   *   ctx.singleEvent(myResult)
   * }
   * }}}
   *
   * @see AbstractPipePair#singleEvent see AbstractPipePair for the Java API
   */
  def singleEvent[Cmd <: AnyRef, Evt <: AnyRef](evt: Evt): Iterable[Either[Evt, Cmd]] = {
    evtHolder(0) = evt
    this.evt
  }

  /**
   * A shared (and shareable) instance of an empty `Iterable[Either[EvtAbove, CmdBelow]]`.
   * Use this when processing does not yield any commands or events as result.
   */
  def nothing[Cmd, Evt]: Iterable[Either[Evt, Cmd]] = Nil

  /**
   * INTERNAL API: Dealias a possibly optimized return value such that it can
   * be safely used; this is never needed when only using public API.
   */
  def dealias[Cmd, Evt](msg: Iterable[Either[Evt, Cmd]]): Iterable[Either[Evt, Cmd]] = {
    if (msg.isEmpty) Nil
    else if (msg eq cmd) Seq(Right(cmd(0)))
    else if (msg eq evt) Seq(Left(evt(0)))
    else msg
  }
}

/**
 * This base trait of each pipeline’s context provides optimized facilities
 * for generating single commands or events (i.e. the fast common case of 1:1
 * message transformations).
 *
 * IMPORTANT NOTICE:
 *
 * A PipelineContext MUST NOT be shared between multiple pipelines, it contains mutable
 * state without synchronization. You have been warned!
 */
abstract class AbstractPipelineContext extends PipelineContext

object PipelineStage {

  /**
   * Java API: attach the two given stages such that the command output of the
   * first is fed into the command input of the second, and the event output of
   * the second is fed into the event input of the first. In other words:
   * sequence the stages such that the left one is on top of the right one.
   *
   * @param left the left or upper pipeline stage
   * @param right the right or lower pipeline stage
   * @return a pipeline stage representing the sequence of the two stages
   */
  def sequence[Ctx <: PipelineContext, CmdAbove, CmdBelow, CmdBelowBelow, EvtAbove, EvtBelow, EvtBelowBelow] //
  (left: PipelineStage[_ >: Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow],
   right: PipelineStage[_ >: Ctx, CmdBelow, CmdBelowBelow, EvtBelow, EvtBelowBelow]) //
   : PipelineStage[Ctx, CmdAbove, CmdBelowBelow, EvtAbove, EvtBelowBelow] =
    left >> right

  /**
   * Java API: combine the two stages such that the command pipeline of the
   * left stage is used and the event pipeline of the right, discarding the
   * other two sub-pipelines.
   *
   * @param left the command pipeline
   * @param right the event pipeline
   * @return a pipeline stage using the left command pipeline and the right event pipeline
   */
  def combine[Ctx <: PipelineContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] //
  (left: PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow],
   right: PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow]) //
   : PipelineStage[Ctx, CmdAbove, CmdBelow, EvtAbove, EvtBelow] =
    left | right
}

/**
 * A [[PipelineStage]] which is symmetric in command and event types, i.e. it only
 * has one command and event type above and one below.
 */
abstract class SymmetricPipelineStage[Context <: PipelineContext, Above, Below] extends PipelineStage[Context, Above, Below, Above, Below]

/**
 * A pipeline stage which can be combined with other stages to build a
 * protocol stack. The main function of this class is to serve as a factory
 * for the actual [[PipePair]] generated by the [[#apply]] method so that a
 * context object can be passed in.
 *
 * @see [[PipelineFactory]]
 */
abstract class PipelineStage[Context <: PipelineContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] { left ⇒

  /**
   * Implement this method to generate this stage’s pair of command and event
   * functions.
   *
   * INTERNAL API: do not use this method to instantiate a pipeline!
   *
   * @see [[PipelineFactory]]
   * @see [[AbstractPipePair]]
   * @see [[AbstractSymmetricPipePair]]
   */
  protected[io] def apply(ctx: Context): PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow]

  /**
   * Scala API: attach the two given stages such that the command output of the
   * first is fed into the command input of the second, and the event output of
   * the second is fed into the event input of the first. In other words:
   * sequence the stages such that the left one is on top of the right one.
   *
   * @param right the right or lower pipeline stage
   * @return a pipeline stage representing the sequence of the two stages
   */
  def >>[CmdBelowBelow, EvtBelowBelow, BelowContext <: Context] //
  (right: PipelineStage[_ >: BelowContext, CmdBelow, CmdBelowBelow, EvtBelow, EvtBelowBelow]) //
  : PipelineStage[BelowContext, CmdAbove, CmdBelowBelow, EvtAbove, EvtBelowBelow] =
    new PipelineStage[BelowContext, CmdAbove, CmdBelowBelow, EvtAbove, EvtBelowBelow] {

      protected[io] override def apply(ctx: BelowContext): PipePair[CmdAbove, CmdBelowBelow, EvtAbove, EvtBelowBelow] = {

        val leftPL = left(ctx)
        val rightPL = right(ctx)

        new PipePair[CmdAbove, CmdBelowBelow, EvtAbove, EvtBelowBelow] {

          type Output = Either[EvtAbove, CmdBelowBelow]

          import language.implicitConversions
          @inline implicit def narrowRight[A, B, C](in: Right[A, B]): Right[C, B] = in.asInstanceOf[Right[C, B]]
          @inline implicit def narrowLeft[A, B, C](in: Left[A, B]): Left[A, C] = in.asInstanceOf[Left[A, C]]

          def loopLeft(input: Iterable[Either[EvtAbove, CmdBelow]]): Iterable[Output] = {
            if (input.isEmpty) Nil
            else if (input eq ctx.cmd) loopRight(rightPL.commandPipeline(ctx.cmd(0)))
            else if (input eq ctx.evt) ctx.evt
            else {
              val output = Vector.newBuilder[Output]
              input foreach {
                case Right(cmd)  ⇒ output ++= ctx.dealias(loopRight(rightPL.commandPipeline(cmd)))
                case l @ Left(_) ⇒ output += l
              }
              output.result
            }
          }

          def loopRight(input: Iterable[Either[EvtBelow, CmdBelowBelow]]): Iterable[Output] = {
            if (input.isEmpty) Nil
            else if (input eq ctx.cmd) ctx.cmd
            else if (input eq ctx.evt) loopLeft(leftPL.eventPipeline(ctx.evt(0)))
            else {
              val output = Vector.newBuilder[Output]
              input foreach {
                case r @ Right(_) ⇒ output += r
                case Left(evt)    ⇒ output ++= ctx.dealias(loopLeft(leftPL.eventPipeline(evt)))
              }
              output.result
            }
          }

          override val commandPipeline = { a: CmdAbove ⇒ loopLeft(leftPL.commandPipeline(a)) }

          override val eventPipeline = { b: EvtBelowBelow ⇒ loopRight(rightPL.eventPipeline(b)) }

          override val managementPort: PartialFunction[AnyRef, Iterable[Either[EvtAbove, CmdBelowBelow]]] = {
            case x ⇒
              val output = Vector.newBuilder[Output]
              output ++= ctx.dealias(loopLeft(leftPL.managementPort.applyOrElse(x, (_: AnyRef) ⇒ Nil)))
              output ++= ctx.dealias(loopRight(rightPL.managementPort.applyOrElse(x, (_: AnyRef) ⇒ Nil)))
              output.result
          }
        }
      }
    }

  /**
   * Scala API: combine the two stages such that the command pipeline of the
   * left stage is used and the event pipeline of the right, discarding the
   * other two sub-pipelines.
   *
   * @param right the event pipeline
   * @return a pipeline stage using the left command pipeline and the right event pipeline
   */
  def |[RightContext <: Context] //
  (right: PipelineStage[_ >: RightContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow]) //
  : PipelineStage[RightContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] =
    new PipelineStage[RightContext, CmdAbove, CmdBelow, EvtAbove, EvtBelow] {
      override def apply(ctx: RightContext): PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] =
        new PipePair[CmdAbove, CmdBelow, EvtAbove, EvtBelow] {

          val leftPL = left(ctx)
          val rightPL = right(ctx)

          override val commandPipeline = leftPL.commandPipeline
          override val eventPipeline = rightPL.eventPipeline
          override val managementPort: Mgmt = {
            case x ⇒
              val output = Vector.newBuilder[Either[EvtAbove, CmdBelow]]
              output ++= ctx.dealias(leftPL.managementPort(x))
              output ++= ctx.dealias(rightPL.managementPort(x))
              output.result
          }
        }
    }
}

object BackpressureBuffer {
  /**
   * Message type which is sent when the buffer’s high watermark has been
   * reached, which means that further write requests should not be sent
   * until the low watermark has been reached again.
   */
  trait HighWatermarkReached extends Tcp.Event
  case object HighWatermarkReached extends HighWatermarkReached

  /**
   * Message type which is sent when the buffer’s fill level falls below
   * the low watermark, which means that writing can commence again.
   */
  trait LowWatermarkReached extends Tcp.Event
  case object LowWatermarkReached extends LowWatermarkReached

}

/**
 * This pipeline stage implements a configurable buffer for transforming the
 * per-write ACK/NACK-based backpressure model of a TCP connection actor into
 * an edge-triggered back-pressure model: the upper stages will receive
 * notification when the buffer runs full ([[BackpressureBuffer.HighWatermarkReached]]) and when
 * it subsequently empties ([[BackpressureBuffer.LowWatermarkReached]]). The upper layers should
 * respond by not generating more writes when the buffer is full. There is also
 * a hard limit upon which this buffer will abort the connection.
 *
 * All limits are configurable and are given in number of bytes.
 * The `highWatermark` should be set such that the
 * amount of data generated before reception of the asynchronous
 * [[BackpressureBuffer.HighWatermarkReached]] notification does not lead to exceeding the
 * `maxCapacity` hard limit; if the writes may arrive in bursts then the
 * difference between these two should allow for at least one burst to be sent
 * after the high watermark has been reached. The `lowWatermark` must be less
 * than or equal to the `highWatermark`, where the difference between these two
 * defines the hysteresis, i.e. how often these notifications are sent out (i.e.
 * if the difference is rather large then it will take some time for the buffer
 * to empty below the low watermark, and that room is then available for data
 * sent in response to the [[BackpressureBuffer.LowWatermarkReached]] notification; if the
 * difference was small then the buffer would more quickly oscillate between
 * these two limits).
 */
class BackpressureBuffer(lowBytes: Long, highBytes: Long, maxBytes: Long)
  extends PipelineStage[HasLogging, Tcp.Command, Tcp.Command, Tcp.Event, Tcp.Event] {

  require(lowBytes >= 0, "lowWatermark needs to be non-negative")
  require(highBytes >= lowBytes, "highWatermark needs to be at least as large as lowWatermark")
  require(maxBytes >= highBytes, "maxCapacity needs to be at least as large as highWatermark")

  // WARNING: Closes over enclosing class -- cannot moved outside because of backwards binary compatibility
  // Fixed in 2.3
  case class Ack(num: Int, ack: Tcp.Event) extends Tcp.Event with NoSerializationVerificationNeeded

  override def apply(ctx: HasLogging) = new PipePair[Tcp.Command, Tcp.Command, Tcp.Event, Tcp.Event] {

    import Tcp._
    import BackpressureBuffer._

    private val log = ctx.getLogger

    private var storageOffset = 0
    private var storage = Vector.empty[Write]
    private def currentOffset = storageOffset + storage.size

    private var stored = 0L
    private var suspended = false

    private var behavior = writing
    override def commandPipeline = behavior
    override def eventPipeline = behavior

    private def become(f: Message ⇒ Iterable[Result]) { behavior = f }

    private lazy val writing: Message ⇒ Iterable[Result] = {
      case Write(data, ack) ⇒
        buffer(Write(data, Ack(currentOffset, ack)), doWrite = true)

      case CommandFailed(Write(_, Ack(offset, _))) ⇒
        become(buffering(offset))
        ctx.singleCommand(ResumeWriting)

      case cmd: CloseCommand ⇒ cmd match {
        case _ if storage.isEmpty ⇒
          become(finished)
          ctx.singleCommand(cmd)
        case Abort ⇒
          storage = Vector.empty
          become(finished)
          ctx.singleCommand(Abort)
        case _ ⇒
          become(closing(cmd))
          ctx.nothing
      }

      case Ack(seq, ack) ⇒ acknowledge(seq, ack)

      case cmd: Command  ⇒ ctx.singleCommand(cmd)
      case evt: Event    ⇒ ctx.singleEvent(evt)
    }

    private def buffering(nack: Int): Message ⇒ Iterable[Result] = {
      var toAck = 10
      var closed: CloseCommand = null

      {
        case Write(data, ack) ⇒
          buffer(Write(data, Ack(currentOffset, ack)), doWrite = false)

        case WritingResumed ⇒
          ctx.singleCommand(storage(0))

        case cmd: CloseCommand ⇒ cmd match {
          case Abort ⇒
            storage = Vector.empty
            become(finished)
            ctx.singleCommand(Abort)
          case _ ⇒
            closed = cmd
            ctx.nothing
        }

        case Ack(seq, ack) if seq < nack ⇒ acknowledge(seq, ack)

        case Ack(seq, ack) ⇒
          val ackMsg = acknowledge(seq, ack)
          if (storage.nonEmpty) {
            if (toAck > 0) {
              toAck -= 1
              ctx.dealias(ackMsg) ++ Seq(Right(storage(0)))
            } else {
              become(if (closed != null) closing(closed) else writing)
              ctx.dealias(ackMsg) ++ storage.map(Right(_))
            }
          } else if (closed != null) {
            become(finished)
            ctx.dealias(ackMsg) ++ Seq(Right(closed))
          } else {
            become(writing)
            ackMsg
          }

        case CommandFailed(_: Write) ⇒ ctx.nothing
        case cmd: Command            ⇒ ctx.singleCommand(cmd)
        case evt: Event              ⇒ ctx.singleEvent(evt)
      }
    }

    private def closing(cmd: CloseCommand): Message ⇒ Iterable[Result] = {
      case Ack(seq, ack) ⇒
        val result = acknowledge(seq, ack)
        if (storage.isEmpty) {
          become(finished)
          ctx.dealias(result) ++ Seq(Right(cmd))
        } else result

      case CommandFailed(_: Write) ⇒
        become({
          case WritingResumed ⇒
            become(closing(cmd))
            storage.map(Right(_))
          case CommandFailed(_: Write) ⇒ ctx.nothing
          case cmd: Command            ⇒ ctx.singleCommand(cmd)
          case evt: Event              ⇒ ctx.singleEvent(evt)
        })
        ctx.singleCommand(ResumeWriting)

      case cmd: Command ⇒ ctx.singleCommand(cmd)
      case evt: Event   ⇒ ctx.singleEvent(evt)
    }

    private val finished: Message ⇒ Iterable[Result] = {
      case _: Write                ⇒ ctx.nothing
      case CommandFailed(_: Write) ⇒ ctx.nothing
      case cmd: Command            ⇒ ctx.singleCommand(cmd)
      case evt: Event              ⇒ ctx.singleEvent(evt)
    }

    private def buffer(w: Write, doWrite: Boolean): Iterable[Result] = {
      storage :+= w
      stored += w.data.size

      if (stored > maxBytes) {
        log.warning("aborting connection (buffer overrun)")
        become(finished)
        ctx.singleCommand(Abort)
      } else if (stored > highBytes && !suspended) {
        log.debug("suspending writes")
        suspended = true
        if (doWrite) {
          Seq(Right(w), Left(HighWatermarkReached))
        } else {
          ctx.singleEvent(HighWatermarkReached)
        }
      } else if (doWrite) {
        ctx.singleCommand(w)
      } else Nil
    }

    private def acknowledge(seq: Int, ack: Event): Iterable[Result] = {
      require(seq == storageOffset, s"received ack $seq at $storageOffset")
      require(storage.nonEmpty, s"storage was empty at ack $seq")

      val size = storage(0).data.size
      stored -= size

      storageOffset += 1
      storage = storage drop 1

      if (suspended && stored < lowBytes) {
        log.debug("resuming writes")
        suspended = false
        if (ack == NoAck) ctx.singleEvent(LowWatermarkReached)
        else Vector(Left(ack), Left(LowWatermarkReached))
      } else if (ack == NoAck) ctx.nothing
      else ctx.singleEvent(ack)
    }
  }

}

//#length-field-frame
/**
 * Pipeline stage for length-field encoded framing. It will prepend a
 * four-byte length header to the message; the header contains the length of
 * the resulting frame including header in big-endian representation.
 *
 * The `maxSize` argument is used to protect the communication channel sanity:
 * larger frames will not be sent (silently dropped) or received (in which case
 * stream decoding would be broken, hence throwing an IllegalArgumentException).
 */
class LengthFieldFrame(maxSize: Int,
                       byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN,
                       headerSize: Int = 4,
                       lengthIncludesHeader: Boolean = true)
  extends SymmetricPipelineStage[PipelineContext, ByteString, ByteString] {

  //#range-checks-omitted
  require(byteOrder ne null, "byteOrder must not be null")
  require(headerSize > 0 && headerSize <= 4, "headerSize must be in (0, 4]")
  require(maxSize > 0, "maxSize must be positive")
  require(maxSize <= (Int.MaxValue >> (4 - headerSize) * 8) * (if (headerSize == 4) 1 else 2),
    "maxSize cannot exceed 256**headerSize")
  //#range-checks-omitted

  override def apply(ctx: PipelineContext) =
    new SymmetricPipePair[ByteString, ByteString] {
      var buffer = None: Option[ByteString]
      implicit val byteOrder = LengthFieldFrame.this.byteOrder

      /**
       * Extract as many complete frames as possible from the given ByteString
       * and return the remainder together with the extracted frames in reverse
       * order.
       */
      @tailrec
      def extractFrames(bs: ByteString, acc: List[ByteString]) //
      : (Option[ByteString], Seq[ByteString]) = {
        if (bs.isEmpty) {
          (None, acc)
        } else if (bs.length < headerSize) {
          (Some(bs.compact), acc)
        } else {
          val length = bs.iterator.getLongPart(headerSize).toInt
          if (length < 0 || length > maxSize)
            throw new IllegalArgumentException(
              s"received too large frame of size $length (max = $maxSize)")
          val total = if (lengthIncludesHeader) length else length + headerSize
          if (bs.length >= total) {
            extractFrames(bs drop total, bs.slice(headerSize, total) :: acc)
          } else {
            (Some(bs.compact), acc)
          }
        }
      }

      /*
       * This is how commands (writes) are transformed: calculate length
       * including header, write that to a ByteStringBuilder and append the
       * payload data. The result is a single command (i.e. `Right(...)`).
       */
      override def commandPipeline =
        { bs: ByteString ⇒
          val length =
            if (lengthIncludesHeader) bs.length + headerSize else bs.length
          if (length > maxSize) Seq()
          else {
            val bb = ByteString.newBuilder
            bb.putLongPart(length, headerSize)
            bb ++= bs
            ctx.singleCommand(bb.result)
          }
        }

      /*
       * This is how events (reads) are transformed: append the received
       * ByteString to the buffer (if any) and extract the frames from the
       * result. In the end store the new buffer contents and return the
       * list of events (i.e. `Left(...)`).
       */
      override def eventPipeline =
        { bs: ByteString ⇒
          val data = if (buffer.isEmpty) bs else buffer.get ++ bs
          val (nb, frames) = extractFrames(data, Nil)
          buffer = nb
          /*
           * please note the specialized (optimized) facility for emitting
           * just a single event
           */
          frames match {
            case Nil        ⇒ Nil
            case one :: Nil ⇒ ctx.singleEvent(one)
            case many       ⇒ many reverseMap (Left(_))
          }
        }
    }
}
//#length-field-frame

/**
 * Pipeline stage for delimiter byte based framing and de-framing. Useful for string oriented protocol using '\n'
 * or 0 as delimiter values.
 *
 * @param maxSize The maximum size of the frame the pipeline is willing to decode. Not checked for encoding, as the
 *                sender might decide to pass through multiple chunks in one go (multiple lines in case of a line-based
 *                protocol)
 * @param delimiter The sequence of bytes that will be used as the delimiter for decoding.
 * @param includeDelimiter If enabled, the delmiter bytes will be part of the decoded messages. In the case of sends
 *                          the delimiter has to be appended to the end of frames by the user. It is also possible
 *                          to send multiple frames by embedding multiple delimiters in the passed ByteString
 */
class DelimiterFraming(maxSize: Int, delimiter: ByteString = ByteString('\n'), includeDelimiter: Boolean = false)
  extends SymmetricPipelineStage[PipelineContext, ByteString, ByteString] {

  require(maxSize > 0, "maxSize must be positive")
  require(delimiter.nonEmpty, "delimiter must not be empty")

  override def apply(ctx: PipelineContext) = new SymmetricPipePair[ByteString, ByteString] {
    val singleByteDelimiter: Boolean = delimiter.size == 1
    var buffer: ByteString = ByteString.empty
    var delimiterFragment: Option[ByteString] = None
    val firstByteOfDelimiter = delimiter.head

    @tailrec
    private def extractParts(nextChunk: ByteString, acc: List[ByteString]): List[ByteString] = delimiterFragment match {
      case Some(fragment) if nextChunk.size < fragment.size && fragment.startsWith(nextChunk) ⇒
        buffer ++= nextChunk
        delimiterFragment = Some(fragment.drop(nextChunk.size))
        acc
      // We got the missing parts of the delimiter
      case Some(fragment) if nextChunk.startsWith(fragment) ⇒
        val decoded = if (includeDelimiter) buffer ++ fragment else buffer.take(buffer.size - delimiter.size + fragment.size)
        buffer = ByteString.empty
        delimiterFragment = None
        extractParts(nextChunk.drop(fragment.size), decoded :: acc)
      case _ ⇒
        val matchPosition = nextChunk.indexOf(firstByteOfDelimiter)
        if (matchPosition == -1) {
          delimiterFragment = None
          val minSize = buffer.size + nextChunk.size
          if (minSize > maxSize) throw new IllegalArgumentException(
            s"Received too large frame of size $minSize (max = $maxSize)")
          buffer ++= nextChunk
          acc
        } else if (matchPosition + delimiter.size > nextChunk.size) {
          val delimiterMatchLength = nextChunk.size - matchPosition
          if (nextChunk.drop(matchPosition) == delimiter.take(delimiterMatchLength)) {
            buffer ++= nextChunk
            // we are expecting the other parts of the delimiter
            delimiterFragment = Some(delimiter.drop(nextChunk.size - matchPosition))
            acc
          } else {
            // false positive
            delimiterFragment = None
            buffer ++= nextChunk.take(matchPosition + 1)
            extractParts(nextChunk.drop(matchPosition + 1), acc)
          }
        } else {
          delimiterFragment = None
          val missingBytes: Int = if (includeDelimiter) matchPosition + delimiter.size else matchPosition
          val expectedSize = buffer.size + missingBytes
          if (expectedSize > maxSize) throw new IllegalArgumentException(
            s"Received frame already of size $expectedSize (max = $maxSize)")

          if (singleByteDelimiter || nextChunk.slice(matchPosition, matchPosition + delimiter.size) == delimiter) {
            val decoded = buffer ++ nextChunk.take(missingBytes)
            buffer = ByteString.empty
            extractParts(nextChunk.drop(matchPosition + delimiter.size), decoded :: acc)
          } else {
            buffer ++= nextChunk.take(matchPosition + 1)
            extractParts(nextChunk.drop(matchPosition + 1), acc)
          }
        }

    }

    override val eventPipeline = {
      bs: ByteString ⇒
        val parts = extractParts(bs, Nil)
        buffer = buffer.compact // TODO: This should be properly benchmarked and memory profiled
        parts match {
          case Nil        ⇒ Nil
          case one :: Nil ⇒ ctx.singleEvent(one.compact)
          case many       ⇒ many reverseMap { frame ⇒ Left(frame.compact) }
        }
    }

    override val commandPipeline = {
      bs: ByteString ⇒ ctx.singleCommand(bs)
    }
  }
}

/**
 * Simple convenience pipeline stage for turning Strings into ByteStrings and vice versa.
 *
 * @param charset The character set to be used for encoding and decoding the raw byte representation of the strings.
 */
class StringByteStringAdapter(charset: String = "utf-8")
  extends PipelineStage[PipelineContext, String, ByteString, String, ByteString] {

  override def apply(ctx: PipelineContext) = new PipePair[String, ByteString, String, ByteString] {

    val commandPipeline = (str: String) ⇒ ctx.singleCommand(ByteString(str, charset))

    val eventPipeline = (bs: ByteString) ⇒ ctx.singleEvent(bs.decodeString(charset))
  }
}

/**
 * This trait expresses that the pipeline’s context needs to provide a logging
 * facility.
 */
trait HasLogging extends PipelineContext {
  /**
   * Retrieve the [[akka.event.LoggingAdapter]] for this pipeline’s context.
   */
  def getLogger: LoggingAdapter
}

//#tick-generator
/**
 * This trait expresses that the pipeline’s context needs to live within an
 * actor and provide its ActorContext.
 */
trait HasActorContext extends PipelineContext {
  /**
   * Retrieve the [[akka.actor.ActorContext]] for this pipeline’s context.
   */
  def getContext: ActorContext
}

object TickGenerator {
  /**
   * This message type is used by the TickGenerator to trigger
   * the rescheduling of the next Tick. The actor hosting the pipeline
   * which includes a TickGenerator must arrange for messages of this
   * type to be injected into the management port of the pipeline.
   */
  trait Trigger

  /**
   * This message type is emitted by the TickGenerator to the whole
   * pipeline, informing all stages about the time at which this Tick
   * was emitted (relative to some arbitrary epoch).
   */
  case class Tick(@BeanProperty timestamp: FiniteDuration) extends Trigger
}

/**
 * This pipeline stage does not alter the events or commands
 */
class TickGenerator[Cmd <: AnyRef, Evt <: AnyRef](interval: FiniteDuration)
  extends PipelineStage[HasActorContext, Cmd, Cmd, Evt, Evt] {
  import TickGenerator._

  override def apply(ctx: HasActorContext) =
    new PipePair[Cmd, Cmd, Evt, Evt] {

      // use unique object to avoid double-activation on actor restart
      private val trigger: Trigger = {
        val path = ctx.getContext.self.path

        new Trigger {
          override def toString = s"Tick[$path]"
        }
      }

      private def schedule() =
        ctx.getContext.system.scheduler.scheduleOnce(
          interval, ctx.getContext.self, trigger)(ctx.getContext.dispatcher)

      // automatically activate this generator
      schedule()

      override val commandPipeline = (cmd: Cmd) ⇒ ctx.singleCommand(cmd)

      override val eventPipeline = (evt: Evt) ⇒ ctx.singleEvent(evt)

      override val managementPort: Mgmt = {
        case `trigger` ⇒
          ctx.getContext.self ! Tick(Deadline.now.time)
          schedule()
          Nil
      }
    }
}
//#tick-generator





© 2015 - 2025 Weber Informatics LLC | Privacy Policy