The philosophy is that the FlowMaterializer has complete control over how it interprets the AST, no restrictions. Therefore it only involves one specified method: materialize() which returns a MaterializedFlow. Within the ActorBasedFlowMaterializer we materialize Sources and Sinks that implement the specified SimpleSource/SourceWithKey interfaces (same for Sinks), others are not supported. These traits are extensible and they require that an ActorBasedFlowMaterializer is passed into the factory methods. Other materializers can of course interpret these AST nodes differently, or they can use the actor-based facilities by creating a suitable materializer for them to use. This means that everything is fully extensible, but the infrastructure we provide concretely for ourselves is built exactly for that and nothing more. Overgeneralization would just lead nowhere. Also made FutureSink isActive and implement it using a light-weight Subscriber instead of a Flow/Transformer.
192 lines
7 KiB
Scala
192 lines
7 KiB
Scala
/**
|
|
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
|
*/
|
|
package akka.stream.impl2
|
|
|
|
import java.util.concurrent.atomic.AtomicLong
|
|
|
|
import scala.annotation.tailrec
|
|
import scala.collection.immutable
|
|
import scala.concurrent.Await
|
|
|
|
import org.reactivestreams.{ Processor, Publisher, Subscriber }
|
|
|
|
import akka.actor._
|
|
import akka.pattern.ask
|
|
import akka.stream.{ MaterializerSettings, Transformer }
|
|
import akka.stream.impl.{ ActorProcessor, ActorPublisher, ExposedPublisher, TransformProcessorImpl }
|
|
import akka.stream.scaladsl2._
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] object Ast {
|
|
sealed trait AstNode {
|
|
def name: String
|
|
}
|
|
|
|
case class Transform(name: String, mkTransformer: () ⇒ Transformer[Any, Any]) extends AstNode
|
|
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
case class ActorBasedFlowMaterializer(override val settings: MaterializerSettings,
|
|
supervisor: ActorRef,
|
|
flowNameCounter: AtomicLong,
|
|
namePrefix: String)
|
|
extends FlowMaterializer(settings) {
|
|
|
|
import akka.stream.impl2.Ast._
|
|
|
|
def withNamePrefix(name: String): FlowMaterializer = this.copy(namePrefix = name)
|
|
|
|
private def nextFlowNameCount(): Long = flowNameCounter.incrementAndGet()
|
|
|
|
private def createFlowName(): String = s"$namePrefix-${nextFlowNameCount()}"
|
|
|
|
@tailrec private def processorChain(topSubscriber: Subscriber[_], ops: immutable.Seq[AstNode],
|
|
flowName: String, n: Int): Subscriber[_] = {
|
|
ops match {
|
|
case op :: tail ⇒
|
|
val opProcessor: Processor[Any, Any] = processorForNode(op, flowName, n)
|
|
opProcessor.subscribe(topSubscriber.asInstanceOf[Subscriber[Any]])
|
|
processorChain(opProcessor, tail, flowName, n - 1)
|
|
case _ ⇒ topSubscriber
|
|
}
|
|
}
|
|
|
|
// Ops come in reverse order
|
|
override def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow = {
|
|
val flowName = createFlowName()
|
|
|
|
def attachSink(pub: Publisher[Out]) = sink match {
|
|
case s: SimpleSink[Out] ⇒ s.attach(pub, this, flowName)
|
|
case s: SinkWithKey[Out, _] ⇒ s.attach(pub, this, flowName)
|
|
case _ ⇒ throw new MaterializationException("unknown Sink type " + sink.getClass)
|
|
}
|
|
def attachSource(sub: Subscriber[In]) = source match {
|
|
case s: SimpleSource[In] ⇒ s.attach(sub, this, flowName)
|
|
case s: SourceWithKey[In, _] ⇒ s.attach(sub, this, flowName)
|
|
case _ ⇒ throw new MaterializationException("unknown Source type " + sink.getClass)
|
|
}
|
|
def createSink() = sink.asInstanceOf[Sink[In]] match {
|
|
case s: SimpleSink[In] ⇒ s.create(this, flowName) -> (())
|
|
case s: SinkWithKey[In, _] ⇒ s.create(this, flowName)
|
|
case _ ⇒ throw new MaterializationException("unknown Sink type " + sink.getClass)
|
|
}
|
|
def createSource() = source.asInstanceOf[Source[Out]] match {
|
|
case s: SimpleSource[Out] ⇒ s.create(this, flowName) -> (())
|
|
case s: SourceWithKey[Out, _] ⇒ s.create(this, flowName)
|
|
case _ ⇒ throw new MaterializationException("unknown Source type " + sink.getClass)
|
|
}
|
|
def isActive(s: AnyRef) = s match {
|
|
case source: SimpleSource[_] ⇒ source.isActive
|
|
case source: SourceWithKey[_, _] ⇒ source.isActive
|
|
case sink: SimpleSink[_] ⇒ sink.isActive
|
|
case sink: SinkWithKey[_, _] ⇒ sink.isActive
|
|
case _: Source[_] ⇒ throw new MaterializationException("unknown Source type " + sink.getClass)
|
|
case _: Sink[_] ⇒ throw new MaterializationException("unknown Sink type " + sink.getClass)
|
|
}
|
|
|
|
val (sourceValue, sinkValue) =
|
|
if (ops.isEmpty) {
|
|
if (isActive(sink)) {
|
|
val (sub, value) = createSink()
|
|
(attachSource(sub), value)
|
|
} else if (isActive(source)) {
|
|
val (pub, value) = createSource()
|
|
(value, attachSink(pub))
|
|
} else {
|
|
val id: Processor[In, Out] = processorForNode(identityTransform, flowName, 1).asInstanceOf[Processor[In, Out]]
|
|
(attachSource(id), attachSink(id))
|
|
}
|
|
} else {
|
|
val opsSize = ops.size
|
|
val last = processorForNode(ops.head, flowName, opsSize).asInstanceOf[Processor[Any, Out]]
|
|
val first = processorChain(last, ops.tail, flowName, opsSize - 1).asInstanceOf[Processor[In, Any]]
|
|
(attachSource(first), attachSink(last))
|
|
}
|
|
new MaterializedFlow(source, sourceValue, sink, sinkValue)
|
|
}
|
|
|
|
private val identityTransform = Transform("identity", () ⇒
|
|
new Transformer[Any, Any] {
|
|
override def onNext(element: Any) = List(element)
|
|
})
|
|
|
|
private def processorForNode(op: AstNode, flowName: String, n: Int): Processor[Any, Any] = {
|
|
val impl = actorOf(ActorProcessorFactory.props(settings, op), s"$flowName-$n-${op.name}")
|
|
ActorProcessorFactory(impl)
|
|
}
|
|
|
|
def actorOf(props: Props, name: String): ActorRef = supervisor match {
|
|
case ref: LocalActorRef ⇒
|
|
ref.underlying.attachChild(props, name, systemService = false)
|
|
case ref: RepointableActorRef ⇒
|
|
if (ref.isStarted)
|
|
ref.underlying.asInstanceOf[ActorCell].attachChild(props, name, systemService = false)
|
|
else {
|
|
implicit val timeout = ref.system.settings.CreationTimeout
|
|
val f = (supervisor ? StreamSupervisor.Materialize(props, name)).mapTo[ActorRef]
|
|
Await.result(f, timeout.duration)
|
|
}
|
|
case _ ⇒
|
|
throw new IllegalStateException(s"Stream supervisor must be a local actor, was [${supervisor.getClass.getName}]")
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] object FlowNameCounter extends ExtensionId[FlowNameCounter] with ExtensionIdProvider {
|
|
override def get(system: ActorSystem): FlowNameCounter = super.get(system)
|
|
override def lookup = FlowNameCounter
|
|
override def createExtension(system: ExtendedActorSystem): FlowNameCounter = new FlowNameCounter
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] class FlowNameCounter extends Extension {
|
|
val counter = new AtomicLong(0)
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] object StreamSupervisor {
|
|
def props(settings: MaterializerSettings): Props = Props(new StreamSupervisor(settings))
|
|
|
|
case class Materialize(props: Props, name: String)
|
|
}
|
|
|
|
private[akka] class StreamSupervisor(settings: MaterializerSettings) extends Actor {
|
|
import StreamSupervisor._
|
|
|
|
def receive = {
|
|
case Materialize(props, name) ⇒
|
|
val impl = context.actorOf(props, name)
|
|
sender() ! impl
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] object ActorProcessorFactory {
|
|
|
|
import Ast._
|
|
def props(settings: MaterializerSettings, op: AstNode): Props =
|
|
(op match {
|
|
case t: Transform ⇒ Props(new TransformProcessorImpl(settings, t.mkTransformer()))
|
|
}).withDispatcher(settings.dispatcher)
|
|
|
|
def apply[I, O](impl: ActorRef): ActorProcessor[I, O] = {
|
|
val p = new ActorProcessor[I, O](impl)
|
|
impl ! ExposedPublisher(p.asInstanceOf[ActorPublisher[Any]])
|
|
p
|
|
}
|
|
}
|