+doc #19429 initial merge of docs-dev and docs

This commit is contained in:
Konrad Malawski 2016-01-13 16:25:24 +01:00
parent be0c8af4c0
commit 5a18d43435
501 changed files with 9876 additions and 3681 deletions

View file

@ -0,0 +1,150 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.japi.pf.ReceiveBuilder;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.actor.AbstractActorPublisher;
import akka.stream.actor.ActorPublisherMessage;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class ActorPublisherDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("ActorPublisherDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#job-manager
public static class JobManagerProtocol {
final public static class Job {
public final String payload;
public Job(String payload) {
this.payload = payload;
}
}
public static class JobAcceptedMessage {
@Override
public String toString() {
return "JobAccepted";
}
}
public static final JobAcceptedMessage JobAccepted = new JobAcceptedMessage();
public static class JobDeniedMessage {
@Override
public String toString() {
return "JobDenied";
}
}
public static final JobDeniedMessage JobDenied = new JobDeniedMessage();
}
public static class JobManager extends AbstractActorPublisher<JobManagerProtocol.Job> {
public static Props props() { return Props.create(JobManager.class); }
private final int MAX_BUFFER_SIZE = 100;
private final List<JobManagerProtocol.Job> buf = new ArrayList<>();
public JobManager() {
receive(ReceiveBuilder.
match(JobManagerProtocol.Job.class, job -> buf.size() == MAX_BUFFER_SIZE, job -> {
sender().tell(JobManagerProtocol.JobDenied, self());
}).
match(JobManagerProtocol.Job.class, job -> {
sender().tell(JobManagerProtocol.JobAccepted, self());
if (buf.isEmpty() && totalDemand() > 0)
onNext(job);
else {
buf.add(job);
deliverBuf();
}
}).
match(ActorPublisherMessage.Request.class, request -> deliverBuf()).
match(ActorPublisherMessage.Cancel.class, cancel -> context().stop(self())).
build());
}
void deliverBuf() {
while (totalDemand() > 0) {
/*
* totalDemand is a Long and could be larger than
* what buf.splitAt can accept
*/
if (totalDemand() <= Integer.MAX_VALUE) {
final List<JobManagerProtocol.Job> took =
buf.subList(0, Math.min(buf.size(), (int) totalDemand()));
took.forEach(this::onNext);
buf.removeAll(took);
break;
} else {
final List<JobManagerProtocol.Job> took =
buf.subList(0, Math.min(buf.size(), Integer.MAX_VALUE));
took.forEach(this::onNext);
buf.removeAll(took);
}
}
}
}
//#job-manager
@Test
public void demonstrateActorPublisherUsage() {
new JavaTestKit(system) {
private final SilenceSystemOut.System System = SilenceSystemOut.get(getTestActor());
{
//#actor-publisher-usage
final Source<JobManagerProtocol.Job, ActorRef> jobManagerSource =
Source.actorPublisher(JobManager.props());
final ActorRef ref = jobManagerSource
.map(job -> job.payload.toUpperCase())
.map(elem -> {
System.out.println(elem);
return elem;
})
.to(Sink.ignore())
.run(mat);
ref.tell(new JobManagerProtocol.Job("a"), ActorRef.noSender());
ref.tell(new JobManagerProtocol.Job("b"), ActorRef.noSender());
ref.tell(new JobManagerProtocol.Job("c"), ActorRef.noSender());
//#actor-publisher-usage
expectMsgEquals("A");
expectMsgEquals("B");
expectMsgEquals("C");
}
};
}
}

View file

@ -0,0 +1,233 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.japi.pf.ReceiveBuilder;
import akka.routing.ActorRefRoutee;
import akka.routing.RoundRobinRoutingLogic;
import akka.routing.Routee;
import akka.routing.Router;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.actor.AbstractActorSubscriber;
import akka.stream.actor.ActorSubscriberMessage;
import akka.stream.actor.MaxInFlightRequestStrategy;
import akka.stream.actor.RequestStrategy;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.assertEquals;
public class ActorSubscriberDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("ActorSubscriberDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#worker-pool
public static class WorkerPoolProtocol {
public static class Msg {
public final int id;
public final ActorRef replyTo;
public Msg(int id, ActorRef replyTo) {
this.id = id;
this.replyTo = replyTo;
}
@Override
public String toString() {
return String.format("Msg(%s, %s)", id, replyTo);
}
}
public static Msg msg(int id, ActorRef replyTo) {
return new Msg(id, replyTo);
}
public static class Work {
public final int id;
public Work(int id) { this.id = id; }
@Override
public String toString() {
return String.format("Work(%s)", id);
}
}
public static Work work(int id) {
return new Work(id);
}
public static class Reply {
public final int id;
public Reply(int id) { this.id = id; }
@Override
public String toString() {
return String.format("Reply(%s)", id);
}
}
public static Reply reply(int id) {
return new Reply(id);
}
public static class Done {
public final int id;
public Done(int id) { this.id = id; }
@Override
public String toString() {
return String.format("Done(%s)", id);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Done done = (Done) o;
if (id != done.id) {
return false;
}
return true;
}
@Override
public int hashCode() {
return id;
}
}
public static Done done(int id) {
return new Done(id);
}
}
public static class WorkerPool extends AbstractActorSubscriber {
public static Props props() { return Props.create(WorkerPool.class); }
final int MAX_QUEUE_SIZE = 10;
final Map<Integer, ActorRef> queue = new HashMap<>();
final Router router;
@Override
public RequestStrategy requestStrategy() {
return new MaxInFlightRequestStrategy(MAX_QUEUE_SIZE) {
@Override
public int inFlightInternally() {
return queue.size();
}
};
}
public WorkerPool() {
final List<Routee> routees = new ArrayList<>();
for (int i = 0; i < 3; i++)
routees.add(new ActorRefRoutee(context().actorOf(Props.create(Worker.class))));
router = new Router(new RoundRobinRoutingLogic(), routees);
receive(ReceiveBuilder.
match(ActorSubscriberMessage.OnNext.class, on -> on.element() instanceof WorkerPoolProtocol.Msg,
onNext -> {
WorkerPoolProtocol.Msg msg = (WorkerPoolProtocol.Msg) onNext.element();
queue.put(msg.id, msg.replyTo);
if (queue.size() > MAX_QUEUE_SIZE)
throw new RuntimeException("queued too many: " + queue.size());
router.route(WorkerPoolProtocol.work(msg.id), self());
}).
match(WorkerPoolProtocol.Reply.class, reply -> {
int id = reply.id;
queue.get(id).tell(WorkerPoolProtocol.done(id), self());
queue.remove(id);
}).
build());
}
}
static class Worker extends AbstractActor {
public Worker() {
receive(ReceiveBuilder.
match(WorkerPoolProtocol.Work.class, work -> {
// ...
sender().tell(WorkerPoolProtocol.reply(work.id), self());
}).build());
}
}
//#worker-pool
@Test
public void demonstrateActorPublisherUsage() {
new JavaTestKit(system) {
{
final ActorRef replyTo = getTestActor();
//#actor-subscriber-usage
final int N = 117;
final List<Integer> data = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
data.add(i);
}
Source.from(data)
.map(i -> WorkerPoolProtocol.msg(i, replyTo))
.runWith(Sink.<WorkerPoolProtocol.Msg>actorSubscriber(WorkerPool.props()), mat);
//#actor-subscriber-usage
List<Object> got = Arrays.asList(receiveN(N));
Collections.sort(got, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof WorkerPoolProtocol.Done && o2 instanceof WorkerPoolProtocol.Done) {
return ((WorkerPoolProtocol.Done) o1).id - ((WorkerPoolProtocol.Done) o2).id;
} else return 0;
}
});
int i = 0;
for (; i < N; i++) {
assertEquals(String.format("Expected %d, but got %s", i, got.get(i)), WorkerPoolProtocol.done(i), got.get(i));
}
assertEquals(String.format("Expected 117 messages but got %d", i), i, 117);
}
};
}
}

View file

@ -0,0 +1,236 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import akka.stream.javadsl.GraphDSL;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.actor.ActorSystem;
import akka.japi.pf.PFBuilder;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.stage.*;
import akka.testkit.JavaTestKit;
import akka.util.ByteIterator;
import akka.util.ByteString;
import akka.util.ByteStringBuilder;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import static org.junit.Assert.assertArrayEquals;
public class BidiFlowDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#codec
static interface Message {}
static class Ping implements Message {
final int id;
public Ping(int id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (o instanceof Ping) {
return ((Ping) o).id == id;
} else return false;
}
@Override
public int hashCode() {
return id;
}
}
static class Pong implements Message {
final int id;
public Pong(int id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (o instanceof Pong) {
return ((Pong) o).id == id;
} else return false;
}
@Override
public int hashCode() {
return id;
}
}
//#codec-impl
public static ByteString toBytes(Message msg) {
//#implementation-details-elided
if (msg instanceof Ping) {
final int id = ((Ping) msg).id;
return new ByteStringBuilder().putByte((byte) 1)
.putInt(id, ByteOrder.LITTLE_ENDIAN).result();
} else {
final int id = ((Pong) msg).id;
return new ByteStringBuilder().putByte((byte) 2)
.putInt(id, ByteOrder.LITTLE_ENDIAN).result();
}
//#implementation-details-elided
}
public static Message fromBytes(ByteString bytes) {
//#implementation-details-elided
final ByteIterator it = bytes.iterator();
switch(it.getByte()) {
case 1:
return new Ping(it.getInt(ByteOrder.LITTLE_ENDIAN));
case 2:
return new Pong(it.getInt(ByteOrder.LITTLE_ENDIAN));
default:
throw new RuntimeException("message format error");
}
//#implementation-details-elided
}
//#codec-impl
//#codec
@SuppressWarnings("unused")
//#codec
public final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> codecVerbose =
BidiFlow.fromGraph(GraphDSL.create(b -> {
final FlowShape<Message, ByteString> top =
b.add(Flow.of(Message.class).map(BidiFlowDocTest::toBytes));
final FlowShape<ByteString, Message> bottom =
b.add(Flow.of(ByteString.class).map(BidiFlowDocTest::fromBytes));
return BidiShape.fromFlows(top, bottom);
}));
public final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> codec =
BidiFlow.fromFunctions(BidiFlowDocTest::toBytes, BidiFlowDocTest::fromBytes);
//#codec
//#framing
public static ByteString addLengthHeader(ByteString bytes) {
final int len = bytes.size();
return new ByteStringBuilder()
.putInt(len, ByteOrder.LITTLE_ENDIAN)
.append(bytes)
.result();
}
public static class FrameParser extends PushPullStage<ByteString, ByteString> {
// this holds the received but not yet parsed bytes
private ByteString stash = ByteString.empty();
// this holds the current message length or -1 if at a boundary
private int needed = -1;
@Override
public SyncDirective onPull(Context<ByteString> ctx) {
return run(ctx);
}
@Override
public SyncDirective onPush(ByteString bytes, Context<ByteString> ctx) {
stash = stash.concat(bytes);
return run(ctx);
}
@Override
public TerminationDirective onUpstreamFinish(Context<ByteString> ctx) {
if (stash.isEmpty()) return ctx.finish();
else return ctx.absorbTermination(); // we still have bytes to emit
}
private SyncDirective run(Context<ByteString> ctx) {
if (needed == -1) {
// are we at a boundary? then figure out next length
if (stash.size() < 4) return pullOrFinish(ctx);
else {
needed = stash.iterator().getInt(ByteOrder.LITTLE_ENDIAN);
stash = stash.drop(4);
return run(ctx); // cycle back to possibly already emit the next chunk
}
} else if (stash.size() < needed) {
// we are in the middle of a message, need more bytes
return pullOrFinish(ctx);
} else {
// we have enough to emit at least one message, so do it
final ByteString emit = stash.take(needed);
stash = stash.drop(needed);
needed = -1;
return ctx.push(emit);
}
}
/*
* After having called absorbTermination() we cannot pull any more, so if we need
* more data we will just have to give up.
*/
private SyncDirective pullOrFinish(Context<ByteString> ctx) {
if (ctx.isFinishing()) return ctx.finish();
else return ctx.pull();
}
}
public final BidiFlow<ByteString, ByteString, ByteString, ByteString, BoxedUnit> framing =
BidiFlow.fromGraph(GraphDSL.create(b -> {
final FlowShape<ByteString, ByteString> top =
b.add(Flow.of(ByteString.class).map(BidiFlowDocTest::addLengthHeader));
final FlowShape<ByteString, ByteString> bottom =
b.add(Flow.of(ByteString.class).transform(() -> new FrameParser()));
return BidiShape.fromFlows(top, bottom);
}));
//#framing
@Test
public void mustCompose() throws Exception {
//#compose
/* construct protocol stack
* +------------------------------------+
* | stack |
* | |
* | +-------+ +---------+ |
* ~> O~~o | ~> | o~~O ~>
* Message | | codec | ByteString | framing | | ByteString
* <~ O~~o | <~ | o~~O <~
* | +-------+ +---------+ |
* +------------------------------------+
*/
final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> stack =
codec.atop(framing);
// test it by plugging it into its own inverse and closing the right end
final Flow<Message, Message, BoxedUnit> pingpong =
Flow.of(Message.class).collect(new PFBuilder<Message, Message>()
.match(Ping.class, p -> new Pong(p.id))
.build()
);
final Flow<Message, Message, BoxedUnit> flow =
stack.atop(stack.reversed()).join(pingpong);
final Future<List<Message>> result = Source
.from(Arrays.asList(0, 1, 2))
.<Message> map(id -> new Ping(id))
.via(flow)
.grouped(10)
.runWith(Sink.<List<Message>> head(), mat);
final FiniteDuration oneSec = Duration.create(1, TimeUnit.SECONDS);
assertArrayEquals(
new Message[] { new Pong(0), new Pong(1), new Pong(2) },
Await.result(result, oneSec).toArray(new Message[0]));
//#compose
}
}

View file

@ -0,0 +1,308 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import java.util.Arrays;
import akka.stream.ClosedShape;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.actor.ActorSystem;
import akka.dispatch.Mapper;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.javadsl.Tcp.OutgoingConnection;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
import scala.concurrent.*;
import scala.runtime.BoxedUnit;
import scala.Option;
public class CompositionDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void nonNestedFlow() throws Exception {
//#non-nested-flow
Source.single(0)
.map(i -> i + 1)
.filter(i -> i != 0)
.map(i -> i - 2)
.to(Sink.fold(0, (acc, i) -> acc + i));
// ... where is the nesting?
//#non-nested-flow
}
@Test
public void nestedFlow() throws Exception {
//#nested-flow
final Source<Integer, BoxedUnit> nestedSource =
Source.single(0) // An atomic source
.map(i -> i + 1) // an atomic processing stage
.named("nestedSource"); // wraps up the current Source and gives it a name
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
Flow.of(Integer.class).filter(i -> i != 0) // an atomic processing stage
.map(i -> i - 2) // another atomic processing stage
.named("nestedFlow"); // wraps up the Flow, and gives it a name
final Sink<Integer, BoxedUnit> nestedSink =
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
.named("nestedSink"); // wrap it up
// Create a RunnableGraph
final RunnableGraph<BoxedUnit> runnableGraph = nestedSource.to(nestedSink);
//#nested-flow
}
@Test
public void reusingComponents() throws Exception {
final Source<Integer, BoxedUnit> nestedSource =
Source.single(0) // An atomic source
.map(i -> i + 1) // an atomic processing stage
.named("nestedSource"); // wraps up the current Source and gives it a name
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
Flow.of(Integer.class).filter(i -> i != 0) // an atomic processing stage
.map(i -> i - 2) // another atomic processing stage
.named("nestedFlow"); // wraps up the Flow, and gives it a name
final Sink<Integer, BoxedUnit> nestedSink =
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
.named("nestedSink"); // wrap it up
//#reuse
// Create a RunnableGraph from our components
final RunnableGraph<BoxedUnit> runnableGraph = nestedSource.to(nestedSink);
// Usage is uniform, no matter if modules are composite or atomic
final RunnableGraph<BoxedUnit> runnableGraph2 =
Source.single(0).to(Sink.fold(0, (acc, i) -> acc + i));
//#reuse
}
@Test
public void complexGraph() throws Exception {
//#complex-graph
RunnableGraph.fromGraph(
GraphDSL.create(builder -> {
final Outlet<Integer> A = builder.add(Source.single(0)).out();
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
final FlowShape<Integer, Integer> D =
builder.add(Flow.of(Integer.class).map(i -> i + 1));
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
final Inlet<Integer> G = builder.add(Sink.<Integer> foreach(System.out::println)).in();
builder.from(F).toFanIn(C);
builder.from(A).viaFanOut(B).viaFanIn(C).toFanIn(F);
builder.from(B).via(D).viaFanOut(E).toFanIn(F);
builder.from(E).toInlet(G);
return ClosedShape.getInstance();
}));
//#complex-graph
//#complex-graph-alt
RunnableGraph.fromGraph(
GraphDSL.create(builder -> {
final SourceShape<Integer> A = builder.add(Source.single(0));
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
final FlowShape<Integer, Integer> D =
builder.add(Flow.of(Integer.class).map(i -> i + 1));
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
final SinkShape<Integer> G = builder.add(Sink.foreach(System.out::println));
builder.from(F.out()).toInlet(C.in(0));
builder.from(A).toInlet(B.in());
builder.from(B.out(0)).toInlet(C.in(1));
builder.from(C.out()).toInlet(F.in(0));
builder.from(B.out(1)).via(D).toInlet(E.in());
builder.from(E.out(0)).toInlet(F.in(1));
builder.from(E.out(1)).to(G);
return ClosedShape.getInstance();
}));
//#complex-graph-alt
}
@Test
public void partialGraph() throws Exception {
//#partial-graph
final Graph<FlowShape<Integer, Integer>, BoxedUnit> partial =
GraphDSL.create(builder -> {
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
builder.from(F.out()).toInlet(C.in(0));
builder.from(B).viaFanIn(C).toFanIn(F);
builder.from(B).via(builder.add(Flow.of(Integer.class).map(i -> i + 1))).viaFanOut(E).toFanIn(F);
return new FlowShape<Integer, Integer>(B.in(), E.out(1));
});
//#partial-graph
//#partial-use
Source.single(0).via(partial).to(Sink.ignore());
//#partial-use
//#partial-flow-dsl
// Convert the partial graph of FlowShape to a Flow to get
// access to the fluid DSL (for example to be able to call .filter())
final Flow<Integer, Integer, BoxedUnit> flow = Flow.fromGraph(partial);
// Simple way to create a graph backed Source
final Source<Integer, BoxedUnit> source = Source.fromGraph(
GraphDSL.create(builder -> {
final UniformFanInShape<Integer, Integer> merge = builder.add(Merge.create(2));
builder.from(builder.add(Source.single(0))).toFanIn(merge);
builder.from(builder.add(Source.from(Arrays.asList(2, 3, 4)))).toFanIn(merge);
// Exposing exactly one output port
return new SourceShape<Integer>(merge.out());
})
);
// Building a Sink with a nested Flow, using the fluid DSL
final Sink<Integer, BoxedUnit> sink = Flow.of(Integer.class)
.map(i -> i * 2)
.drop(10)
.named("nestedFlow")
.to(Sink.head());
// Putting all together
final RunnableGraph<BoxedUnit> closed = source.via(flow.filter(i -> i > 1)).to(sink);
//#partial-flow-dsl
}
@Test
public void closedGraph() throws Exception {
//#embed-closed
final RunnableGraph<BoxedUnit> closed1 =
Source.single(0).to(Sink.foreach(System.out::println));
final RunnableGraph<BoxedUnit> closed2 =
RunnableGraph.fromGraph(
GraphDSL.create(builder -> {
final ClosedShape embeddedClosed = builder.add(closed1);
return embeddedClosed; // Could return ClosedShape.getInstance()
}));
//#embed-closed
}
//#mat-combine-4a
static class MyClass {
private Promise<Option<Integer>> p;
private OutgoingConnection conn;
public MyClass(Promise<Option<Integer>> p, OutgoingConnection conn) {
this.p = p;
this.conn = conn;
}
public void close() {
p.success(Option.empty());
}
}
static class Combiner {
static Future<MyClass> f(Promise<Option<Integer>> p,
Pair<Future<OutgoingConnection>, Future<String>> rest) {
return rest.first().map(new Mapper<OutgoingConnection, MyClass>() {
public MyClass apply(OutgoingConnection c) {
return new MyClass(p, c);
}
}, system.dispatcher());
}
}
//#mat-combine-4a
@Test
public void materializedValues() throws Exception {
//#mat-combine-1
// Materializes to Promise<BoxedUnit> (red)
final Source<Integer, Promise<Option<Integer>>> source = Source.<Integer>maybe();
// Materializes to BoxedUnit (black)
final Flow<Integer, Integer, BoxedUnit> flow1 = Flow.of(Integer.class).take(100);
// Materializes to Promise<Option<>> (red)
final Source<Integer, Promise<Option<Integer>>> nestedSource =
source.viaMat(flow1, Keep.left()).named("nestedSource");
//#mat-combine-1
//#mat-combine-2
// Materializes to BoxedUnit (orange)
final Flow<Integer, ByteString, BoxedUnit> flow2 = Flow.of(Integer.class)
.map(i -> ByteString.fromString(i.toString()));
// Materializes to Future<OutgoingConnection> (yellow)
final Flow<ByteString, ByteString, Future<OutgoingConnection>> flow3 =
Tcp.get(system).outgoingConnection("localhost", 8080);
// Materializes to Future<OutgoingConnection> (yellow)
final Flow<Integer, ByteString, Future<OutgoingConnection>> nestedFlow =
flow2.viaMat(flow3, Keep.right()).named("nestedFlow");
//#mat-combine-2
//#mat-combine-3
// Materializes to Future<String> (green)
final Sink<ByteString, Future<String>> sink = Sink
.fold("", (acc, i) -> acc + i.utf8String());
// Materializes to Pair<Future<OutgoingConnection>, Future<String>> (blue)
final Sink<Integer, Pair<Future<OutgoingConnection>, Future<String>>> nestedSink =
nestedFlow.toMat(sink, Keep.both());
//#mat-combine-3
//#mat-combine-4b
// Materializes to Future<MyClass> (purple)
final RunnableGraph<Future<MyClass>> runnableGraph =
nestedSource.toMat(nestedSink, Combiner::f);
//#mat-combine-4b
}
@Test
public void attributes() throws Exception {
//#attributes-inheritance
final Source<Integer, BoxedUnit> nestedSource =
Source.single(0)
.map(i -> i + 1)
.named("nestedSource"); // Wrap, no inputBuffer set
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
Flow.of(Integer.class).filter(i -> i != 0)
.via(Flow.of(Integer.class)
.map(i -> i - 2)
.withAttributes(Attributes.inputBuffer(4, 4))) // override
.named("nestedFlow"); // Wrap, no inputBuffer set
final Sink<Integer, BoxedUnit> nestedSink =
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
.withAttributes(Attributes.name("nestedSink")
.and(Attributes.inputBuffer(3, 3))); // override
//#attributes-inheritance
}
}

View file

@ -0,0 +1,301 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import akka.japi.Pair;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.Promise;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import scala.Option;
import akka.actor.ActorSystem;
import akka.actor.Cancellable;
import akka.dispatch.Futures;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
public class FlowDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void sourceIsImmutable() throws Exception {
//#source-immutable
final Source<Integer, BoxedUnit> source =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
source.map(x -> 0); // has no effect on source, since it's immutable
source.runWith(Sink.fold(0, (agg, next) -> agg + next), mat); // 55
// returns new Source<Integer>, with `map()` appended
final Source<Integer, BoxedUnit> zeroes = source.map(x -> 0);
final Sink<Integer, Future<Integer>> fold =
Sink.fold(0, (agg, next) -> agg + next);
zeroes.runWith(fold, mat); // 0
//#source-immutable
int result = Await.result(
zeroes.runWith(fold, mat),
Duration.create(3, TimeUnit.SECONDS)
);
assertEquals(0, result);
}
@Test
public void materializationInSteps() throws Exception {
//#materialization-in-steps
final Source<Integer, BoxedUnit> source =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// note that the Future is scala.concurrent.Future
final Sink<Integer, Future<Integer>> sink =
Sink.fold(0, (aggr, next) -> aggr + next);
// connect the Source to the Sink, obtaining a RunnableFlow
final RunnableGraph<Future<Integer>> runnable =
source.toMat(sink, Keep.right());
// materialize the flow
final Future<Integer> sum = runnable.run(mat);
//#materialization-in-steps
int result = Await.result(sum, Duration.create(3, TimeUnit.SECONDS));
assertEquals(55, result);
}
@Test
public void materializationRunWith() throws Exception {
//#materialization-runWith
final Source<Integer, BoxedUnit> source =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
final Sink<Integer, Future<Integer>> sink =
Sink.fold(0, (aggr, next) -> aggr + next);
// materialize the flow, getting the Sinks materialized value
final Future<Integer> sum = source.runWith(sink, mat);
//#materialization-runWith
int result = Await.result(sum, Duration.create(3, TimeUnit.SECONDS));
assertEquals(55, result);
}
@Test
public void materializedMapUnique() throws Exception {
//#stream-reuse
// connect the Source to the Sink, obtaining a RunnableGraph
final Sink<Integer, Future<Integer>> sink =
Sink.fold(0, (aggr, next) -> aggr + next);
final RunnableGraph<Future<Integer>> runnable =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).toMat(sink, Keep.right());
// get the materialized value of the FoldSink
final Future<Integer> sum1 = runnable.run(mat);
final Future<Integer> sum2 = runnable.run(mat);
// sum1 and sum2 are different Futures!
//#stream-reuse
int result1 = Await.result(sum1, Duration.create(3, TimeUnit.SECONDS));
assertEquals(55, result1);
int result2 = Await.result(sum2, Duration.create(3, TimeUnit.SECONDS));
assertEquals(55, result2);
}
@Test
@SuppressWarnings("unused")
public void compoundSourceCannotBeUsedAsKey() throws Exception {
//#compound-source-is-not-keyed-runWith
final Object tick = new Object();
final FiniteDuration oneSecond = Duration.create(1, TimeUnit.SECONDS);
//akka.actor.Cancellable
final Source<Object, Cancellable> timer =
Source.tick(oneSecond, oneSecond, tick);
Sink.ignore().runWith(timer, mat);
final Source<String, Cancellable> timerMap = timer.map(t -> "tick");
// WRONG: returned type is not the timers Cancellable!
// Cancellable timerCancellable = Sink.ignore().runWith(timerMap, mat);
//#compound-source-is-not-keyed-runWith
//#compound-source-is-not-keyed-run
// retain the materialized map, in order to retrieve the timer's Cancellable
final Cancellable timerCancellable = timer.to(Sink.ignore()).run(mat);
timerCancellable.cancel();
//#compound-source-is-not-keyed-run
}
@Test
public void creatingSourcesSinks() throws Exception {
//#source-sink
// Create a source from an Iterable
List<Integer> list = new LinkedList<Integer>();
list.add(1);
list.add(2);
list.add(3);
Source.from(list);
// Create a source form a Future
Source.fromFuture(Futures.successful("Hello Streams!"));
// Create a source from a single element
Source.single("only one element");
// an empty source
Source.empty();
// Sink that folds over the stream and returns a Future
// of the final result in the MaterializedMap
Sink.fold(0, (Integer aggr, Integer next) -> aggr + next);
// Sink that returns a Future in the MaterializedMap,
// containing the first element of the stream
Sink.head();
// A Sink that consumes a stream without doing anything with the elements
Sink.ignore();
// A Sink that executes a side-effecting call for every element of the stream
Sink.foreach(System.out::println);
//#source-sink
}
@Test
public void variousWaysOfConnecting() throws Exception {
//#flow-connecting
// Explicitly creating and wiring up a Source, Sink and Flow
Source.from(Arrays.asList(1, 2, 3, 4))
.via(Flow.of(Integer.class).map(elem -> elem * 2))
.to(Sink.foreach(System.out::println));
// Starting from a Source
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 2, 3, 4))
.map(elem -> elem * 2);
source.to(Sink.foreach(System.out::println));
// Starting from a Sink
final Sink<Integer, BoxedUnit> sink = Flow.of(Integer.class)
.map(elem -> elem * 2).to(Sink.foreach(System.out::println));
Source.from(Arrays.asList(1, 2, 3, 4)).to(sink);
//#flow-connecting
}
@Test
public void transformingMaterialized() throws Exception {
FiniteDuration oneSecond = FiniteDuration.apply(1, TimeUnit.SECONDS);
Flow<Integer, Integer, Cancellable> throttler =
Flow.fromGraph(GraphDSL.create(
Source.tick(oneSecond, oneSecond, ""),
(b, tickSource) -> {
FanInShape2<String, Integer, Integer> zip = b.add(ZipWith.create(Keep.right()));
b.from(tickSource).toInlet(zip.in0());
return FlowShape.of(zip.in1(), zip.out());
}));
//#flow-mat-combine
// An empty source that can be shut down explicitly from the outside
Source<Integer, Promise<Option<Integer>>> source = Source.<Integer>maybe();
// A flow that internally throttles elements to 1/second, and returns a Cancellable
// which can be used to shut down the stream
Flow<Integer, Integer, Cancellable> flow = throttler;
// A sink that returns the first element of a stream in the returned Future
Sink<Integer, Future<Integer>> sink = Sink.head();
// By default, the materialized value of the leftmost stage is preserved
RunnableGraph<Promise<Option<Integer>>> r1 = source.via(flow).to(sink);
// Simple selection of materialized values by using Keep.right
RunnableGraph<Cancellable> r2 = source.viaMat(flow, Keep.right()).to(sink);
RunnableGraph<Future<Integer>> r3 = source.via(flow).toMat(sink, Keep.right());
// Using runWith will always give the materialized values of the stages added
// by runWith() itself
Future<Integer> r4 = source.via(flow).runWith(sink, mat);
Promise<Option<Integer>> r5 = flow.to(sink).runWith(source, mat);
Pair<Promise<Option<Integer>>, Future<Integer>> r6 = flow.runWith(source, sink, mat);
// Using more complext combinations
RunnableGraph<Pair<Promise<Option<Integer>>, Cancellable>> r7 =
source.viaMat(flow, Keep.both()).to(sink);
RunnableGraph<Pair<Promise<Option<Integer>>, Future<Integer>>> r8 =
source.via(flow).toMat(sink, Keep.both());
RunnableGraph<Pair<Pair<Promise<Option<Integer>>, Cancellable>, Future<Integer>>> r9 =
source.viaMat(flow, Keep.both()).toMat(sink, Keep.both());
RunnableGraph<Pair<Cancellable, Future<Integer>>> r10 =
source.viaMat(flow, Keep.right()).toMat(sink, Keep.both());
// It is also possible to map over the materialized values. In r9 we had a
// doubly nested pair, but we want to flatten it out
RunnableGraph<Cancellable> r11 =
r9.mapMaterializedValue( (nestedTuple) -> {
Promise<Option<Integer>> p = nestedTuple.first().first();
Cancellable c = nestedTuple.first().second();
Future<Integer> f = nestedTuple.second();
// Picking the Cancellable, but we could also construct a domain class here
return c;
});
//#flow-mat-combine
}
public void fusingAndAsync() {
//#explicit-fusing
Flow<Integer, Integer, BoxedUnit> flow =
Flow.of(Integer.class).map(x -> x * 2).filter(x -> x > 500);
Graph<FlowShape<Integer, Integer>, BoxedUnit> fused =
akka.stream.Fusing.aggressive(flow);
Source.fromIterator(() -> Stream.iterate(0, x -> x + 1).iterator())
.via(fused)
.take(1000);
//#explicit-fusing
//#flow-async
Source.range(1, 3)
.map(x -> x + 1)
.withAttributes(Attributes.asyncBoundary())
.map(x -> x * 2)
.to(Sink.ignore());
//#flow-async
}
}

View file

@ -0,0 +1,140 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.ActorMaterializerSettings;
import akka.stream.Materializer;
import akka.stream.Supervision;
import akka.stream.javadsl.Flow;
import akka.stream.ActorAttributes;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.japi.function.Function;
import akka.testkit.JavaTestKit;
public class FlowErrorDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
@Test(expected = ArithmeticException.class)
public void demonstrateFailStream() throws Exception {
//#stop
final Materializer mat = ActorMaterializer.create(system);
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
.map(elem -> 100 / elem);
final Sink<Integer, Future<Integer>> fold =
Sink.fold(0, (acc, elem) -> acc + elem);
final Future<Integer> result = source.runWith(fold, mat);
// division by zero will fail the stream and the
// result here will be a Future completed with Failure(ArithmeticException)
//#stop
Await.result(result, Duration.create(3, TimeUnit.SECONDS));
}
@Test
public void demonstrateResumeStream() throws Exception {
//#resume
final Function<Throwable, Supervision.Directive> decider = exc -> {
if (exc instanceof ArithmeticException)
return Supervision.resume();
else
return Supervision.stop();
};
final Materializer mat = ActorMaterializer.create(
ActorMaterializerSettings.create(system).withSupervisionStrategy(decider),
system);
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
.map(elem -> 100 / elem);
final Sink<Integer, Future<Integer>> fold =
Sink.fold(0, (acc, elem) -> acc + elem);
final Future<Integer> result = source.runWith(fold, mat);
// the element causing division by zero will be dropped
// result here will be a Future completed with Success(228)
//#resume
assertEquals(Integer.valueOf(228), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateResumeSectionStream() throws Exception {
//#resume-section
final Materializer mat = ActorMaterializer.create(system);
final Function<Throwable, Supervision.Directive> decider = exc -> {
if (exc instanceof ArithmeticException)
return Supervision.resume();
else
return Supervision.stop();
};
final Flow<Integer, Integer, BoxedUnit> flow =
Flow.of(Integer.class).filter(elem -> 100 / elem < 50).map(elem -> 100 / (5 - elem))
.withAttributes(ActorAttributes.withSupervisionStrategy(decider));
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
.via(flow);
final Sink<Integer, Future<Integer>> fold =
Sink.fold(0, (acc, elem) -> acc + elem);
final Future<Integer> result = source.runWith(fold, mat);
// the elements causing division by zero will be dropped
// result here will be a Future completed with Success(150)
//#resume-section
assertEquals(Integer.valueOf(150), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateRestartSectionStream() throws Exception {
//#restart-section
final Materializer mat = ActorMaterializer.create(system);
final Function<Throwable, Supervision.Directive> decider = exc -> {
if (exc instanceof IllegalArgumentException)
return Supervision.restart();
else
return Supervision.stop();
};
final Flow<Integer, Integer, BoxedUnit> flow =
Flow.of(Integer.class).scan(0, (acc, elem) -> {
if (elem < 0) throw new IllegalArgumentException("negative not allowed");
else return acc + elem;
})
.withAttributes(ActorAttributes.withSupervisionStrategy(decider));
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 3, -1, 5, 7))
.via(flow);
final Future<List<Integer>> result = source.grouped(1000)
.runWith(Sink.<List<Integer>>head(), mat);
// the negative element cause the scan stage to be restarted,
// i.e. start from 0 again
// result here will be a Future completed with Success(List(0, 1, 4, 0, 5, 12))
//#restart-section
assertEquals(
Arrays.asList(0, 1, 4, 0, 5, 12),
Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
}
}

View file

@ -0,0 +1,172 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import akka.stream.ClosedShape;
import akka.stream.SourceShape;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
public class FlowGraphDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowGraphDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void demonstrateBuildSimpleGraph() throws Exception {
//#simple-flow-graph
final Source<Integer, BoxedUnit> in = Source.from(Arrays.asList(1, 2, 3, 4, 5));
final Sink<List<String>, Future<List<String>>> sink = Sink.head();
final Sink<List<Integer>, Future<List<Integer>>> sink2 = Sink.head();
final Flow<Integer, Integer, BoxedUnit> f1 = Flow.of(Integer.class).map(elem -> elem + 10);
final Flow<Integer, Integer, BoxedUnit> f2 = Flow.of(Integer.class).map(elem -> elem + 20);
final Flow<Integer, String, BoxedUnit> f3 = Flow.of(Integer.class).map(elem -> elem.toString());
final Flow<Integer, Integer, BoxedUnit> f4 = Flow.of(Integer.class).map(elem -> elem + 30);
final RunnableGraph<Future<List<String>>> result =
RunnableGraph.<Future<List<String>>>fromGraph(
GraphDSL
.create(
sink,
(builder, out) -> {
final UniformFanOutShape<Integer, Integer> bcast = builder.add(Broadcast.create(2));
final UniformFanInShape<Integer, Integer> merge = builder.add(Merge.create(2));
final Outlet<Integer> source = builder.add(in).out();
builder.from(source).via(builder.add(f1))
.viaFanOut(bcast).via(builder.add(f2)).viaFanIn(merge)
.via(builder.add(f3.grouped(1000))).to(out);
builder.from(bcast).via(builder.add(f4)).toFanIn(merge);
return ClosedShape.getInstance();
}));
//#simple-flow-graph
final List<String> list = Await.result(result.run(mat), Duration.create(3, TimeUnit.SECONDS));
final String[] res = list.toArray(new String[] {});
Arrays.sort(res, null);
assertArrayEquals(new String[] { "31", "32", "33", "34", "35", "41", "42", "43", "44", "45" }, res);
}
@Test
@SuppressWarnings("unused")
public void demonstrateConnectErrors() {
try {
//#simple-graph
final RunnableGraph<BoxedUnit> g =
RunnableGraph.<BoxedUnit>fromGraph(
GraphDSL
.create((b) -> {
final SourceShape<Integer> source1 = b.add(Source.from(Arrays.asList(1, 2, 3, 4, 5)));
final SourceShape<Integer> source2 = b.add(Source.from(Arrays.asList(1, 2, 3, 4, 5)));
final FanInShape2<Integer, Integer, Pair<Integer, Integer>> zip = b.add(Zip.create());
b.from(source1).toInlet(zip.in0());
b.from(source2).toInlet(zip.in1());
return ClosedShape.getInstance();
}
)
);
// unconnected zip.out (!) => "The inlets [] and outlets [] must correspond to the inlets [] and outlets [ZipWith2.out]"
//#simple-graph
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertTrue(e != null && e.getMessage() != null && e.getMessage().contains("must correspond to"));
}
}
@Test
public void demonstrateReusingFlowInGraph() throws Exception {
//#flow-graph-reusing-a-flow
final Sink<Integer, Future<Integer>> topHeadSink = Sink.head();
final Sink<Integer, Future<Integer>> bottomHeadSink = Sink.head();
final Flow<Integer, Integer, BoxedUnit> sharedDoubler = Flow.of(Integer.class).map(elem -> elem * 2);
final RunnableGraph<Pair<Future<Integer>, Future<Integer>>> g =
RunnableGraph.<Pair<Future<Integer>, Future<Integer>>>fromGraph(
GraphDSL.create(
topHeadSink, // import this sink into the graph
bottomHeadSink, // and this as well
Keep.both(),
(b, top, bottom) -> {
final UniformFanOutShape<Integer, Integer> bcast =
b.add(Broadcast.create(2));
b.from(b.add(Source.single(1))).viaFanOut(bcast)
.via(b.add(sharedDoubler)).to(top);
b.from(bcast).via(b.add(sharedDoubler)).to(bottom);
return ClosedShape.getInstance();
}
)
);
//#flow-graph-reusing-a-flow
final Pair<Future<Integer>, Future<Integer>> pair = g.run(mat);
assertEquals(Integer.valueOf(2), Await.result(pair.first(), Duration.create(3, TimeUnit.SECONDS)));
assertEquals(Integer.valueOf(2), Await.result(pair.second(), Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateMatValue() throws Exception {
//#flow-graph-matvalue
final Sink<Integer, Future<Integer>> foldSink = Sink.<Integer, Integer> fold(0, (a, b) -> {
return a + b;
});
final Flow<Future<Integer>, Integer, BoxedUnit> flatten = Flow.<Future<Integer>>create()
.mapAsync(4, x -> {
return x;
});
final Flow<Integer, Integer, Future<Integer>> foldingFlow = Flow.fromGraph(
GraphDSL.create(foldSink,
(b, fold) -> {
return FlowShape.of(
fold.in(),
b.from(b.materializedValue()).via(b.add(flatten)).out());
}));
//#flow-graph-matvalue
//#flow-graph-matvalue-cycle
// This cannot produce any value:
final Source<Integer, Future<Integer>> cyclicSource = Source.fromGraph(
GraphDSL.create(foldSink,
(b, fold) -> {
// - Fold cannot complete until its upstream mapAsync completes
// - mapAsync cannot complete until the materialized Future produced by
// fold completes
// As a result this Source will never emit anything, and its materialited
// Future will never complete
b.from(b.materializedValue()).via(b.add(flatten)).to(fold);
return SourceShape.of(b.from(b.materializedValue()).via(b.add(flatten)).out());
}));
//#flow-graph-matvalue-cycle
}
}

View file

@ -0,0 +1,146 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream;
import scala.runtime.BoxedUnit;
import static org.junit.Assert.assertEquals;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.actor.ActorSystem;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
public class FlowParallelismDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
static class ScoopOfBatter {}
static class HalfCookedPancake {}
static class Pancake {}
//#pipelining
Flow<ScoopOfBatter, HalfCookedPancake, BoxedUnit> fryingPan1 =
Flow.of(ScoopOfBatter.class).map(batter -> new HalfCookedPancake());
Flow<HalfCookedPancake, Pancake, BoxedUnit> fryingPan2 =
Flow.of(HalfCookedPancake.class).map(halfCooked -> new Pancake());
//#pipelining
@Test
public void demonstratePipelining() {
//#pipelining
// With the two frying pans we can fully cook pancakes
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef = fryingPan1.via(fryingPan2);
//#pipelining
}
@Test
public void demonstrateParallelism() {
//#parallelism
Flow<ScoopOfBatter, Pancake, BoxedUnit> fryingPan =
Flow.of(ScoopOfBatter.class).map(batter -> new Pancake());
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef =
Flow.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<Pancake, Pancake> mergePancakes =
b.add(Merge.create(2));
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
b.add(Balance.create(2));
// Using two frying pans in parallel, both fully cooking a pancake from the batter.
// We always put the next scoop of batter to the first frying pan that becomes available.
b.from(dispatchBatter.out(0)).via(b.add(fryingPan)).toInlet(mergePancakes.in(0));
// Notice that we used the "fryingPan" flow without importing it via builder.add().
// Flows used this way are auto-imported, which in this case means that the two
// uses of "fryingPan" mean actually different stages in the graph.
b.from(dispatchBatter.out(1)).via(b.add(fryingPan)).toInlet(mergePancakes.in(1));
return FlowShape.of(dispatchBatter.in(), mergePancakes.out());
}));
//#parallelism
}
@Test
public void parallelPipeline() {
//#parallel-pipeline
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef =
Flow.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<Pancake, Pancake> mergePancakes =
b.add(Merge.create(2));
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
b.add(Balance.create(2));
// Using two pipelines, having two frying pans each, in total using
// four frying pans
b.from(dispatchBatter.out(0))
.via(b.add(fryingPan1))
.via(b.add(fryingPan2))
.toInlet(mergePancakes.in(0));
b.from(dispatchBatter.out(1))
.via(b.add(fryingPan1))
.via(b.add(fryingPan2))
.toInlet(mergePancakes.in(1));
return FlowShape.of(dispatchBatter.in(), mergePancakes.out());
}));
//#parallel-pipeline
}
@Test
public void pipelinedParallel() {
//#pipelined-parallel
Flow<ScoopOfBatter, HalfCookedPancake, BoxedUnit> pancakeChefs1 =
Flow.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<HalfCookedPancake, HalfCookedPancake> mergeHalfCooked =
b.add(Merge.create(2));
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
b.add(Balance.create(2));
// Two chefs work with one frying pan for each, half-frying the pancakes then putting
// them into a common pool
b.from(dispatchBatter.out(0)).via(b.add(fryingPan1)).toInlet(mergeHalfCooked.in(0));
b.from(dispatchBatter.out(1)).via(b.add(fryingPan1)).toInlet(mergeHalfCooked.in(1));
return FlowShape.of(dispatchBatter.in(), mergeHalfCooked.out());
}));
Flow<HalfCookedPancake, Pancake, BoxedUnit> pancakeChefs2 =
Flow.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<Pancake, Pancake> mergePancakes =
b.add(Merge.create(2));
final UniformFanOutShape<HalfCookedPancake, HalfCookedPancake> dispatchHalfCooked =
b.add(Balance.create(2));
// Two chefs work with one frying pan for each, finishing the pancakes then putting
// them into a common pool
b.from(dispatchHalfCooked.out(0)).via(b.add(fryingPan2)).toInlet(mergePancakes.in(0));
b.from(dispatchHalfCooked.out(1)).via(b.add(fryingPan2)).toInlet(mergePancakes.in(1));
return FlowShape.of(dispatchHalfCooked.in(), mergePancakes.out());
}));
Flow<ScoopOfBatter, Pancake, BoxedUnit> kitchen =
pancakeChefs1.via(pancakeChefs2);
//#pipelined-parallel
}
}

View file

@ -0,0 +1,256 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.stage.*;
import akka.stream.testkit.*;
import akka.stream.testkit.javadsl.*;
import akka.testkit.JavaTestKit;
public class FlowStagesDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowStagesDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
static //#one-to-one
public class Map<A, B> extends PushPullStage<A, B> {
private final Function<A, B> f;
public Map(Function<A, B> f) {
this.f = f;
}
@Override public SyncDirective onPush(A elem, Context<B> ctx) {
return ctx.push(f.apply(elem));
}
@Override public SyncDirective onPull(Context<B> ctx) {
return ctx.pull();
}
}
//#one-to-one
static //#many-to-one
public class Filter<A> extends PushPullStage<A, A> {
private final Predicate<A> p;
public Filter(Predicate<A> p) {
this.p = p;
}
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
if (p.test(elem)) return ctx.push(elem);
else return ctx.pull();
}
@Override public SyncDirective onPull(Context<A> ctx) {
return ctx.pull();
}
}
//#many-to-one
//#one-to-many
class Duplicator<A> extends PushPullStage<A, A> {
private A lastElem = null;
private boolean oneLeft = false;
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
lastElem = elem;
oneLeft = true;
return ctx.push(elem);
}
@Override public SyncDirective onPull(Context<A> ctx) {
if (!ctx.isFinishing()) {
// the main pulling logic is below as it is demonstrated on the illustration
if (oneLeft) {
oneLeft = false;
return ctx.push(lastElem);
} else
return ctx.pull();
} else {
// If we need to emit a final element after the upstream
// finished
if (oneLeft) return ctx.pushAndFinish(lastElem);
else return ctx.finish();
}
}
@Override public TerminationDirective onUpstreamFinish(Context<A> ctx) {
return ctx.absorbTermination();
}
}
//#one-to-many
static//#pushstage
public class Map2<A, B> extends PushStage<A, B> {
private final Function<A, B> f;
public Map2(Function<A, B> f) {
this.f = f;
}
@Override public SyncDirective onPush(A elem, Context<B> ctx) {
return ctx.push(f.apply(elem));
}
}
public class Filter2<A> extends PushStage<A, A> {
private final Predicate<A> p;
public Filter2(Predicate<A> p) {
this.p = p;
}
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
if (p.test(elem)) return ctx.push(elem);
else return ctx.pull();
}
}
//#pushstage
static //#doubler-stateful
public class Duplicator2<A> extends StatefulStage<A, A> {
@Override public StageState<A, A> initial() {
return new StageState<A, A>() {
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
return emit(Arrays.asList(elem, elem).iterator(), ctx);
}
};
}
}
//#doubler-stateful
@Test
public void demonstrateVariousPushPullStages() throws Exception {
final Sink<Integer, Future<List<Integer>>> sink =
Flow.of(Integer.class).grouped(10).toMat(Sink.head(), Keep.right());
//#stage-chain
final RunnableGraph<Future<List<Integer>>> runnable =
Source
.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
.transform(() -> new Filter<Integer>(elem -> elem % 2 == 0))
.transform(() -> new Duplicator<Integer>())
.transform(() -> new Map<Integer, Integer>(elem -> elem / 2))
.toMat(sink, Keep.right());
//#stage-chain
assertEquals(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5),
Await.result(runnable.run(mat), FiniteDuration.create(3, TimeUnit.SECONDS)));
}
//#detached
class Buffer2<T> extends DetachedStage<T, T> {
final private Integer SIZE = 2;
final private List<T> buf = new ArrayList<>(SIZE);
private Integer capacity = SIZE;
private boolean isFull() {
return capacity == 0;
}
private boolean isEmpty() {
return capacity == SIZE;
}
private T dequeue() {
capacity += 1;
return buf.remove(0);
}
private void enqueue(T elem) {
capacity -= 1;
buf.add(elem);
}
public DownstreamDirective onPull(DetachedContext<T> ctx) {
if (isEmpty()) {
if (ctx.isFinishing()) return ctx.finish(); // No more elements will arrive
else return ctx.holdDownstream(); // waiting until new elements
} else {
final T next = dequeue();
if (ctx.isHoldingUpstream()) return ctx.pushAndPull(next); // release upstream
else return ctx.push(next);
}
}
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
enqueue(elem);
if (isFull()) return ctx.holdUpstream(); // Queue is now full, wait until new empty slot
else {
if (ctx.isHoldingDownstream()) return ctx.pushAndPull(dequeue()); // Release downstream
else return ctx.pull();
}
}
public TerminationDirective onUpstreamFinish(DetachedContext<T> ctx) {
if (!isEmpty()) return ctx.absorbTermination(); // still need to flush from buffer
else return ctx.finish(); // already empty, finishing
}
}
//#detached
@Test
public void demonstrateDetachedStage() throws Exception {
final Pair<TestPublisher.Probe<Integer>,TestSubscriber.Probe<Integer>> pair =
TestSource.<Integer>probe(system)
.transform(() -> new Buffer2<Integer>())
.toMat(TestSink.probe(system), Keep.both())
.run(mat);
final TestPublisher.Probe<Integer> pub = pair.first();
final TestSubscriber.Probe<Integer> sub = pair.second();
final FiniteDuration timeout = Duration.create(100, TimeUnit.MILLISECONDS);
sub.request(2);
sub.expectNoMsg(timeout);
pub.sendNext(1);
pub.sendNext(2);
sub.expectNext(1, 2);
pub.sendNext(3);
pub.sendNext(4);
sub.expectNoMsg(timeout);
sub.request(2);
sub.expectNext(3, 4);
pub.sendComplete();
sub.expectComplete();
}
}

View file

@ -0,0 +1,159 @@
package docs.stream;
import java.util.Arrays;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.runtime.BoxedUnit;
import akka.actor.ActorSystem;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.scaladsl.MergePreferred.MergePreferredShape;
import akka.testkit.JavaTestKit;
public class GraphCyclesDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("GraphCyclesDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final static SilenceSystemOut.System System = SilenceSystemOut.get();
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 2, 3, 4, 5));
@Test
public void demonstrateDeadlockedCycle() {
//#deadlocked
// WARNING! The graph below deadlocks!
final Flow<Integer, Integer, BoxedUnit> printFlow =
Flow.of(Integer.class).map(s -> {
System.out.println(s);
return s;
});
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<Integer, Integer> merge = b.add(Merge.create(2));
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final Outlet<Integer> src = b.add(source).out();
final FlowShape<Integer, Integer> printer = b.add(printFlow);
final SinkShape<Integer> ignore = b.add(Sink.ignore());
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
b.to(merge) .fromFanOut(bcast);
return ClosedShape.getInstance();
}));
//#deadlocked
}
@Test
public void demonstrateUnfairCycle() {
final Flow<Integer, Integer, BoxedUnit> printFlow =
Flow.of(Integer.class).map(s -> {
System.out.println(s);
return s;
});
//#unfair
// WARNING! The graph below stops consuming from "source" after a few steps
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final MergePreferredShape<Integer> merge = b.add(MergePreferred.create(1));
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final Outlet<Integer> src = b.add(source).out();
final FlowShape<Integer, Integer> printer = b.add(printFlow);
final SinkShape<Integer> ignore = b.add(Sink.ignore());
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
b.to(merge.preferred()).fromFanOut(bcast);
return ClosedShape.getInstance();
}));
//#unfair
}
@Test
public void demonstrateDroppingCycle() {
final Flow<Integer, Integer, BoxedUnit> printFlow =
Flow.of(Integer.class).map(s -> {
System.out.println(s);
return s;
});
//#dropping
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final UniformFanInShape<Integer, Integer> merge = b.add(Merge.create(2));
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final FlowShape<Integer, Integer> droppyFlow = b.add(
Flow.of(Integer.class).buffer(10, OverflowStrategy.dropHead()));
final Outlet<Integer> src = b.add(source).out();
final FlowShape<Integer, Integer> printer = b.add(printFlow);
final SinkShape<Integer> ignore = b.add(Sink.ignore());
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
b.to(merge).via(droppyFlow).fromFanOut(bcast);
return ClosedShape.getInstance();
}));
//#dropping
}
@Test
public void demonstrateZippingCycle() {
final Flow<Integer, Integer, BoxedUnit> printFlow =
Flow.of(Integer.class).map(s -> {
System.out.println(s);
return s;
});
//#zipping-dead
// WARNING! The graph below never processes any elements
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final FanInShape2<Integer, Integer, Integer> zip =
b.add(ZipWith.create((Integer left, Integer right) -> left));
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final FlowShape<Integer, Integer> printer = b.add(printFlow);
final SinkShape<Integer> ignore = b.add(Sink.ignore());
b.from(b.add(source)).toInlet(zip.in0());
b.from(zip.out()).via(printer).viaFanOut(bcast).to(ignore);
b.to(zip.in1()) .fromFanOut(bcast);
return ClosedShape.getInstance();
}));
//#zipping-dead
}
@Test
public void demonstrateLiveZippingCycle() {
final Flow<Integer, Integer, BoxedUnit> printFlow =
Flow.of(Integer.class).map(s -> {
System.out.println(s);
return s;
});
//#zipping-live
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final FanInShape2<Integer, Integer, Integer> zip =
b.add(ZipWith.create((Integer left, Integer right) -> left));
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final UniformFanInShape<Integer, Integer> concat = b.add(Concat.create());
final FlowShape<Integer, Integer> printer = b.add(printFlow);
final SinkShape<Integer> ignore = b.add(Sink.ignore());
b.from(b.add(source)).toInlet(zip.in0());
b.from(zip.out()).via(printer).viaFanOut(bcast).to(ignore);
b.to(zip.in1()).viaFanIn(concat).from(b.add(Source.single(1)));
b.to(concat).fromFanOut(bcast);
return ClosedShape.getInstance();
}));
//#zipping-live
}
}

View file

@ -0,0 +1,712 @@
package docs.stream;
import akka.actor.ActorSystem;
//#imports
import akka.dispatch.Futures;
import akka.dispatch.Mapper;
import akka.dispatch.OnSuccess;
import akka.japi.Option;
import akka.japi.Predicate;
import akka.japi.function.Effect;
import akka.japi.function.Procedure;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.stage.*;
//#imports
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.testkit.JavaTestKit;
import akka.japi.Function;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import scala.Tuple2;
import scala.concurrent.Await;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import scala.concurrent.Promise;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.*;
import static org.junit.Assert.assertEquals;
public class GraphStageDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("FlowGraphDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#simple-source
public class NumbersSource extends GraphStage<SourceShape<Integer>> {
// Define the (sole) output port of this stage
public final Outlet<Integer> out = Outlet.create("NumbersSource.out");
// Define the shape of this stage, which is SourceShape with the port we defined above
private final SourceShape<Integer> shape = SourceShape.of(out);
@Override
public SourceShape<Integer> shape() {
return shape;
}
// This is where the actual (possibly stateful) logic is created
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape()) {
// All state MUST be inside the GraphStageLogic,
// never inside the enclosing GraphStage.
// This state is safe to access and modify from all the
// callbacks that are provided by GraphStageLogic and the
// registered handlers.
private int counter = 1;
{
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
push(out, counter);
counter += 1;
}
});
}
};
}
}
//#simple-source
@Test
public void demonstrateCustomSourceUsage() throws Exception {
//#simple-source-usage
// A GraphStage is a proper Graph, just like what GraphDSL.create would return
Graph<SourceShape<Integer>, BoxedUnit> sourceGraph = new NumbersSource();
// Create a Source from the Graph to access the DSL
Source<Integer, BoxedUnit> mySource = Source.fromGraph(sourceGraph);
// Returns 55
Future<Integer> result1 = mySource.take(10).runFold(0, (sum, next) -> sum + next, mat);
// The source is reusable. This returns 5050
Future<Integer> result2 = mySource.take(100).runFold(0, (sum, next) -> sum + next, mat);
//#simple-source-usage
assertEquals(Await.result(result1, Duration.create(3, "seconds")), (Integer) 55);
assertEquals(Await.result(result2, Duration.create(3, "seconds")), (Integer) 5050);
}
//#one-to-one
public class Map<A, B> extends GraphStage<FlowShape<A, B>> {
private final Function<A, B> f;
public Map(Function<A, B> f) {
this.f = f;
}
public final Inlet<A> in = Inlet.create("Map.in");
public final Outlet<B> out = Outlet.create("Map.out");
private final FlowShape<A, B> shape = FlowShape.of(in, out);
@Override
public FlowShape<A,B> shape() {
return shape;
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
try {
push(out, f.apply(grab(in)));
} catch (Exception ex) {
failStage(ex);
}
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
};
}
}
//#one-to-one
@Test
public void demonstrateOneToOne() throws Exception {
// tests:
final Graph<FlowShape<String, Integer>, BoxedUnit> stringLength =
Flow.fromGraph(new Map<String, Integer>(new Function<String, Integer>() {
@Override
public Integer apply(String str) {
return str.length();
}
}));
Future<Integer> result =
Source.from(Arrays.asList("one", "two", "three"))
.via(stringLength)
.runFold(0, (sum, n) -> sum + n, mat);
assertEquals(new Integer(11), Await.result(result, Duration.create(3, "seconds")));
}
//#many-to-one
public final class Filter<A> extends GraphStage<FlowShape<A, A>> {
private final Predicate<A> p;
public Filter(Predicate<A> p) {
this.p = p;
}
public final Inlet<A> in = Inlet.create("Filter.in");
public final Outlet<A> out = Outlet.create("Filter.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
if (p.test(elem)) {
push(out, elem);
} else {
pull(in);
}
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
};
}
}
//#many-to-one
@Test
public void demonstrateAManyToOneElementGraphStage() throws Exception {
// tests:
Graph<FlowShape<Integer, Integer>, BoxedUnit> evenFilter =
Flow.fromGraph(new Filter<Integer>(n -> n % 2 == 0));
Future<Integer> result =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6))
.via(evenFilter)
.runFold(0, (elem, sum) -> sum + elem, mat);
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
}
//#one-to-many
public class Duplicator<A> extends GraphStage<FlowShape<A, A>> {
public final Inlet<A> in = Inlet.create("Duplicator.in");
public final Outlet<A> out = Outlet.create("Duplicator.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
// Again: note that all mutable state
// MUST be inside the GraphStageLogic
Option<A> lastElem = Option.none();
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
lastElem = Option.some(elem);
push(out, elem);
}
@Override
public void onUpstreamFinish() {
if (lastElem.isDefined()) {
emit(out, lastElem.get());
}
complete(out);
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
if (lastElem.isDefined()) {
push(out, lastElem.get());
lastElem = Option.none();
} else {
pull(in);
}
}
});
}
};
}
}
//#one-to-many
@Test
public void demonstrateAOneToManyElementGraphStage() throws Exception {
// tests:
Graph<FlowShape<Integer, Integer>, BoxedUnit> duplicator =
Flow.fromGraph(new Duplicator<Integer>());
Future<Integer> result =
Source.from(Arrays.asList(1, 2, 3))
.via(duplicator)
.runFold(0, (n, sum) -> n + sum, mat);
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
}
//#simpler-one-to-many
public class Duplicator2<A> extends GraphStage<FlowShape<A, A>> {
public final Inlet<A> in = Inlet.create("Duplicator.in");
public final Outlet<A> out = Outlet.create("Duplicator.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
// this will temporarily suspend this handler until the two elems
// are emitted and then reinstates it
emitMultiple(out, Arrays.asList(elem, elem).iterator());
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
};
}
}
//#simpler-one-to-many
@Test
public void demonstrateASimplerOneToManyStage() throws Exception {
// tests:
Graph<FlowShape<Integer, Integer>, BoxedUnit> duplicator =
Flow.fromGraph(new Duplicator2<Integer>());
Future<Integer> result =
Source.from(Arrays.asList(1, 2, 3))
.via(duplicator)
.runFold(0, (n, sum) -> n + sum, mat);
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
}
@Test
public void demonstrateChainingOfGraphStages() throws Exception {
Graph<SinkShape<Integer>, Future<String>> sink = Sink.fold("", (acc, n) -> acc + n.toString());
//#graph-stage-chain
Future<String> resultFuture = Source.from(Arrays.asList(1,2,3,4,5))
.via(new Filter<Integer>((n) -> n % 2 == 0))
.via(new Duplicator<Integer>())
.via(new Map<Integer, Integer>((n) -> n / 2))
.runWith(sink, mat);
//#graph-stage-chain
assertEquals("1122", Await.result(resultFuture, Duration.create(3, "seconds")));
}
//#async-side-channel
// will close upstream when the future completes
public class KillSwitch<A> extends GraphStage<FlowShape<A, A>> {
private final Future<BoxedUnit> switchF;
public KillSwitch(Future<BoxedUnit> switchF) {
this.switchF = switchF;
}
public final Inlet<A> in = Inlet.create("KillSwitch.in");
public final Outlet<A> out = Outlet.create("KillSwitch.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
push(out, grab(in));
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
@Override
public void preStart() {
AsyncCallback<BoxedUnit> callback = createAsyncCallback(new Procedure<BoxedUnit>() {
@Override
public void apply(BoxedUnit param) throws Exception {
completeStage();
}
});
ExecutionContext ec = system.dispatcher();
switchF.onSuccess(new OnSuccess<BoxedUnit>() {
@Override
public void onSuccess(BoxedUnit result) throws Throwable {
callback.invoke(BoxedUnit.UNIT);
}
}, ec);
}
};
}
}
//#async-side-channel
@Test
public void demonstrateAnAsynchronousSideChannel() throws Exception{
// tests:
Promise<BoxedUnit> switchF = Futures.promise();
Graph<FlowShape<Integer, Integer>, BoxedUnit> killSwitch =
Flow.fromGraph(new KillSwitch<>(switchF.future()));
ExecutionContext ec = system.dispatcher();
// TODO this is probably racey, is there a way to make sure it happens after?
Future<Integer> valueAfterKill = switchF.future().flatMap(new Mapper<BoxedUnit, Future<Integer>>() {
@Override
public Future<Integer> apply(BoxedUnit parameter) {
return Futures.successful(4);
}
}, ec);
Future<Integer> result =
Source.from(Arrays.asList(1, 2, 3)).concat(Source.fromFuture(valueAfterKill))
.via(killSwitch)
.runFold(0, (n, sum) -> n + sum, mat);
switchF.success(BoxedUnit.UNIT);
assertEquals(new Integer(6), Await.result(result, Duration.create(3, "seconds")));
}
//#timed
// each time an event is pushed through it will trigger a period of silence
public class TimedGate<A> extends GraphStage<FlowShape<A, A>> {
private final FiniteDuration silencePeriod;
public TimedGate(FiniteDuration silencePeriod) {
this.silencePeriod = silencePeriod;
}
public final Inlet<A> in = Inlet.create("TimedGate.in");
public final Outlet<A> out = Outlet.create("TimedGate.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new TimerGraphStageLogic(shape) {
private boolean open = false;
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
if (open) pull(in);
else {
push(out, elem);
open = true;
scheduleOnce("key", silencePeriod);
}
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
@Override
public void onTimer(Object key) {
if (key.equals("key")) {
open = false;
}
}
};
}
}
//#timed
public void demonstrateAGraphStageWithATimer() throws Exception {
// tests:
Future<Integer> result =
Source.from(Arrays.asList(1, 2, 3))
.via(new TimedGate<>(Duration.create(2, "seconds")))
.takeWithin(Duration.create(250, "millis"))
.runFold(0, (n, sum) -> n + sum, mat);
assertEquals(new Integer(1), Await.result(result, Duration.create(3, "seconds")));
}
//#materialized
public class FirstValue<A> extends GraphStageWithMaterializedValue<FlowShape<A, A>, Future<A>> {
public final Inlet<A> in = Inlet.create("FirstValue.in");
public final Outlet<A> out = Outlet.create("FirstValue.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
@Override
public Tuple2<GraphStageLogic, Future<A>> createLogicAndMaterializedValue(Attributes inheritedAttributes) {
Promise<A> promise = Futures.promise();
GraphStageLogic logic = new GraphStageLogic(shape) {
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
promise.success(elem);
push(out, elem);
// replace handler with one just forwarding
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
push(out, grab(in));
}
});
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
pull(in);
}
});
}
};
return new Tuple2(logic, promise.future());
}
}
//#materialized
public void demonstrateACustomMaterializedValue() throws Exception {
// tests:
RunnableGraph<Future<Integer>> flow = Source.from(Arrays.asList(1, 2, 3))
.viaMat(new FirstValue(), Keep.right())
.to(Sink.ignore());
Future<Integer> result = flow.run(mat);
assertEquals(new Integer(1), Await.result(result, Duration.create(3, "seconds")));
}
//#detached
public class TwoBuffer<A> extends GraphStage<FlowShape<A, A>> {
public final Inlet<A> in = Inlet.create("TwoBuffer.in");
public final Outlet<A> out = Outlet.create("TwoBuffer.out");
private final FlowShape<A, A> shape = FlowShape.of(in, out);
@Override
public FlowShape<A, A> shape() {
return shape;
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new GraphStageLogic(shape) {
private final int SIZE = 2;
private Queue<A> buffer = new ArrayDeque<>(SIZE);
private boolean downstreamWaiting = false;
private boolean isBufferFull() {
return buffer.size() == SIZE;
}
@Override
public void preStart() {
// a detached stage needs to start upstream demand
// itself as it is not triggered by downstream demand
pull(in);
}
{
setHandler(in, new AbstractInHandler() {
@Override
public void onPush() {
A elem = grab(in);
buffer.add(elem);
if (downstreamWaiting) {
downstreamWaiting = false;
A bufferedElem = buffer.poll();
push(out, bufferedElem);
}
if (!isBufferFull()) {
pull(in);
}
}
@Override
public void onUpstreamFinish() {
if (!buffer.isEmpty()) {
// emit the rest if possible
emitMultiple(out, buffer.iterator());
}
completeStage();
}
});
setHandler(out, new AbstractOutHandler() {
@Override
public void onPull() {
if (buffer.isEmpty()) {
downstreamWaiting = true;
} else {
A elem = buffer.poll();
push(out, elem);
}
if (!isBufferFull() && !hasBeenPulled(in)) {
pull(in);
}
}
});
}
};
}
}
//#detached
public void demonstrateADetachedGraphStage() throws Exception {
// tests:
Future<Integer> result1 = Source.from(Arrays.asList(1, 2, 3))
.via(new TwoBuffer<>())
.runFold(0, (acc, n) -> acc + n, mat);
assertEquals(new Integer(6), Await.result(result1, Duration.create(3, "seconds")));
TestSubscriber.ManualProbe<Integer> subscriber = TestSubscriber.manualProbe(system);
TestPublisher.Probe<Integer> publisher = TestPublisher.probe(0, system);
RunnableGraph<BoxedUnit> flow2 =
Source.fromPublisher(publisher)
.via(new TwoBuffer<>())
.to(Sink.fromSubscriber(subscriber));
flow2.run(mat);
Subscription sub = subscriber.expectSubscription();
// this happens even though the subscriber has not signalled any demand
publisher.sendNext(1);
publisher.sendNext(2);
sub.cancel();
}
}

View file

@ -0,0 +1,596 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.*;
import akka.dispatch.Futures;
import akka.dispatch.MessageDispatcher;
import akka.japi.pf.ReceiveBuilder;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
import akka.testkit.TestProbe;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static akka.pattern.Patterns.ask;
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
import static docs.stream.TwitterStreamQuickstartDocTest.Model.tweets;
import static junit.framework.TestCase.assertTrue;
public class IntegrationDocTest {
private static final SilenceSystemOut.System System = SilenceSystemOut.get();
static ActorSystem system;
@BeforeClass
public static void setup() {
final Config config = ConfigFactory.parseString("" +
"blocking-dispatcher { \n" +
" executor = thread-pool-executor \n" +
" thread-pool-executor { \n" +
" core-pool-size-min = 10 \n" +
" core-pool-size-max = 10 \n" +
" } \n" +
"} \n" +
"akka.actor.default-mailbox.mailbox-type = akka.dispatch.UnboundedMailbox\n");
system = ActorSystem.create("ActorPublisherDocTest", config);
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
class AddressSystem {
//#email-address-lookup
public Future<Optional<String>> lookupEmail(String handle)
//#email-address-lookup
{
return Futures.successful(Optional.of(handle + "@somewhere.com"));
}
//#phone-lookup
public Future<Optional<String>> lookupPhoneNumber(String handle)
//#phone-lookup
{
return Futures.successful(Optional.of("" + handle.hashCode()));
}
}
class AddressSystem2 {
//#email-address-lookup2
public Future<String> lookupEmail(String handle)
//#email-address-lookup2
{
return Futures.successful(handle + "@somewhere.com");
}
}
static class Email {
public final String to;
public final String title;
public final String body;
public Email(String to, String title, String body) {
this.to = to;
this.title = title;
this.body = body;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Email email = (Email) o;
if (body != null ? !body.equals(email.body) : email.body != null) {
return false;
}
if (title != null ? !title.equals(email.title) : email.title != null) {
return false;
}
if (to != null ? !to.equals(email.to) : email.to != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = to != null ? to.hashCode() : 0;
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (body != null ? body.hashCode() : 0);
return result;
}
}
static class TextMessage {
public final String to;
public final String body;
TextMessage(String to, String body) {
this.to = to;
this.body = body;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TextMessage that = (TextMessage) o;
if (body != null ? !body.equals(that.body) : that.body != null) {
return false;
}
if (to != null ? !to.equals(that.to) : that.to != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = to != null ? to.hashCode() : 0;
result = 31 * result + (body != null ? body.hashCode() : 0);
return result;
}
}
static class EmailServer {
public final ActorRef probe;
public EmailServer(ActorRef probe) {
this.probe = probe;
}
//#email-server-send
public Future<Email> send(Email email) {
// ...
//#email-server-send
probe.tell(email.to, ActorRef.noSender());
return Futures.successful(email);
//#email-server-send
}
//#email-server-send
}
static class SmsServer {
public final ActorRef probe;
public SmsServer(ActorRef probe) {
this.probe = probe;
}
//#sms-server-send
public boolean send(TextMessage text) {
// ...
//#sms-server-send
probe.tell(text.to, ActorRef.noSender());
//#sms-server-send
return true;
}
//#sms-server-send
}
static class Save {
public final Tweet tweet;
Save(Tweet tweet) {
this.tweet = tweet;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Save save = (Save) o;
if (tweet != null ? !tweet.equals(save.tweet) : save.tweet != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return tweet != null ? tweet.hashCode() : 0;
}
}
static class SaveDone {
public static SaveDone INSTANCE = new SaveDone();
private SaveDone() {
}
}
static class DatabaseService extends AbstractActor {
public final ActorRef probe;
DatabaseService(ActorRef probe) {
this.probe = probe;
receive(ReceiveBuilder.match(Save.class, s -> {
probe.tell(s.tweet.author.handle, ActorRef.noSender());
sender().tell(SaveDone.INSTANCE, self());
}).build());
}
}
//#sometimes-slow-service
static class SometimesSlowService {
private final ExecutionContext ec;
public SometimesSlowService(ExecutionContext ec) {
this.ec = ec;
}
private final AtomicInteger runningCount = new AtomicInteger();
public Future<String> convert(String s) {
System.out.println("running: " + s + "(" + runningCount.incrementAndGet() + ")");
return Futures.future(() -> {
if (!s.isEmpty() && Character.isLowerCase(s.charAt(0)))
Thread.sleep(500);
else
Thread.sleep(20);
System.out.println("completed: " + s + "(" + runningCount.decrementAndGet() + ")");
return s.toUpperCase();
}, ec);
}
}
//#sometimes-slow-service
@Test
public void callingExternalServiceWithMapAsync() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
final AddressSystem addressSystem = new AddressSystem();
final EmailServer emailServer = new EmailServer(probe.ref());
{
//#tweet-authors
final Source<Author, BoxedUnit> authors = tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
//#tweet-authors
//#email-addresses-mapAsync
final Source<String, BoxedUnit> emailAddresses = authors
.mapAsync(4, author -> addressSystem.lookupEmail(author.handle))
.filter(o -> o.isPresent())
.map(o -> o.get());
//#email-addresses-mapAsync
//#send-emails
final RunnableGraph<BoxedUnit> sendEmails = emailAddresses
.mapAsync(4, address ->
emailServer.send(new Email(address, "Akka", "I like your tweet")))
.to(Sink.ignore());
sendEmails.run(mat);
//#send-emails
probe.expectMsg("rolandkuhn@somewhere.com");
probe.expectMsg("patriknw@somewhere.com");
probe.expectMsg("bantonsson@somewhere.com");
probe.expectMsg("drewhk@somewhere.com");
probe.expectMsg("ktosopl@somewhere.com");
probe.expectMsg("mmartynas@somewhere.com");
probe.expectMsg("akkateam@somewhere.com");
}
};
}
@Test
@SuppressWarnings("unused")
public void callingExternalServiceWithMapAsyncAndSupervision() throws Exception {
new JavaTestKit(system) {
final AddressSystem2 addressSystem = new AddressSystem2();
{
final Source<Author, BoxedUnit> authors = tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
//#email-addresses-mapAsync-supervision
final Attributes resumeAttrib =
ActorAttributes.withSupervisionStrategy(Supervision.getResumingDecider());
final Flow<Author, String, BoxedUnit> lookupEmail =
Flow.of(Author.class)
.mapAsync(4, author -> addressSystem.lookupEmail(author.handle))
.withAttributes(resumeAttrib);
final Source<String, BoxedUnit> emailAddresses = authors.via(lookupEmail);
//#email-addresses-mapAsync-supervision
}
};
}
@Test
public void callingExternalServiceWithMapAsyncUnordered() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
final AddressSystem addressSystem = new AddressSystem();
final EmailServer emailServer = new EmailServer(probe.ref());
{
//#external-service-mapAsyncUnordered
final Source<Author, BoxedUnit> authors =
tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
final Source<String, BoxedUnit> emailAddresses =
authors
.mapAsyncUnordered(4, author -> addressSystem.lookupEmail(author.handle))
.filter(o -> o.isPresent())
.map(o -> o.get());
final RunnableGraph<BoxedUnit> sendEmails =
emailAddresses
.mapAsyncUnordered(4, address ->
emailServer.send(new Email(address, "Akka", "I like your tweet")))
.to(Sink.ignore());
sendEmails.run(mat);
//#external-service-mapAsyncUnordered
}
};
}
@Test
public void carefulManagedBlockingWithMapAsync() throws Exception {
new JavaTestKit(system) {
final AddressSystem addressSystem = new AddressSystem();
final EmailServer emailServer = new EmailServer(getRef());
final SmsServer smsServer = new SmsServer(getRef());
{
final Source<Author, BoxedUnit> authors =
tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
final Source<String, BoxedUnit> phoneNumbers = authors.mapAsync(4, author -> addressSystem.lookupPhoneNumber(author.handle))
.filter(o -> o.isPresent())
.map(o -> o.get());
//#blocking-mapAsync
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
final RunnableGraph<BoxedUnit> sendTextMessages =
phoneNumbers
.mapAsync(4, phoneNo ->
Futures.future(() ->
smsServer.send(new TextMessage(phoneNo, "I like your tweet")),
blockingEc)
)
.to(Sink.ignore());
sendTextMessages.run(mat);
//#blocking-mapAsync
final Object[] got = receiveN(7);
final Set<Object> set = new HashSet<>(Arrays.asList(got));
assertTrue(set.contains(String.valueOf("rolandkuhn".hashCode())));
assertTrue(set.contains(String.valueOf("patriknw".hashCode())));
assertTrue(set.contains(String.valueOf("bantonsson".hashCode())));
assertTrue(set.contains(String.valueOf("drewhk".hashCode())));
assertTrue(set.contains(String.valueOf("ktosopl".hashCode())));
assertTrue(set.contains(String.valueOf("mmartynas".hashCode())));
assertTrue(set.contains(String.valueOf("akkateam".hashCode())));
}
};
}
@Test
public void carefulManagedBlockingWithMap() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
final AddressSystem addressSystem = new AddressSystem();
final EmailServer emailServer = new EmailServer(probe.ref());
final SmsServer smsServer = new SmsServer(probe.ref());
{
final Source<Author, BoxedUnit> authors =
tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
final Source<String, BoxedUnit> phoneNumbers = authors.mapAsync(4, author -> addressSystem.lookupPhoneNumber(author.handle))
.filter(o -> o.isPresent())
.map(o -> o.get());
//#blocking-map
final Flow<String, Boolean, BoxedUnit> send =
Flow.of(String.class)
.map(phoneNo -> smsServer.send(new TextMessage(phoneNo, "I like your tweet")))
.withAttributes(ActorAttributes.dispatcher("blocking-dispatcher"));
final RunnableGraph<?> sendTextMessages =
phoneNumbers.via(send).to(Sink.ignore());
sendTextMessages.run(mat);
//#blocking-map
probe.expectMsg(String.valueOf("rolandkuhn".hashCode()));
probe.expectMsg(String.valueOf("patriknw".hashCode()));
probe.expectMsg(String.valueOf("bantonsson".hashCode()));
probe.expectMsg(String.valueOf("drewhk".hashCode()));
probe.expectMsg(String.valueOf("ktosopl".hashCode()));
probe.expectMsg(String.valueOf("mmartynas".hashCode()));
probe.expectMsg(String.valueOf("akkateam".hashCode()));
}
};
}
@Test
public void callingActorServiceWithMapAsync() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
final EmailServer emailServer = new EmailServer(probe.ref());
final ActorRef database = system.actorOf(Props.create(DatabaseService.class, probe.ref()), "db");
{
//#save-tweets
final Source<Tweet, BoxedUnit> akkaTweets = tweets.filter(t -> t.hashtags().contains(AKKA));
final RunnableGraph<BoxedUnit> saveTweets =
akkaTweets
.mapAsync(4, tweet -> ask(database, new Save(tweet), 300))
.to(Sink.ignore());
//#save-tweets
saveTweets.run(mat);
probe.expectMsg("rolandkuhn");
probe.expectMsg("patriknw");
probe.expectMsg("bantonsson");
probe.expectMsg("drewhk");
probe.expectMsg("ktosopl");
probe.expectMsg("mmartynas");
probe.expectMsg("akkateam");
}
};
}
@Test
public void illustrateOrderingAndParallelismOfMapAsync() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
final EmailServer emailServer = new EmailServer(probe.ref());
class MockSystem {
class Println {
public <T> void println(T s) {
if (s.toString().startsWith("after:"))
probe.ref().tell(s, ActorRef.noSender());
}
}
public final Println out = new Println();
}
private final MockSystem System = new MockSystem();
{
//#sometimes-slow-mapAsync
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
final SometimesSlowService service = new SometimesSlowService(blockingEc);
final ActorMaterializer mat = ActorMaterializer.create(
ActorMaterializerSettings.create(system).withInputBuffer(4, 4), system);
Source.from(Arrays.asList("a", "B", "C", "D", "e", "F", "g", "H", "i", "J"))
.map(elem -> { System.out.println("before: " + elem); return elem; })
.mapAsync(4, service::convert)
.runForeach(elem -> System.out.println("after: " + elem), mat);
//#sometimes-slow-mapAsync
probe.expectMsg("after: A");
probe.expectMsg("after: B");
probe.expectMsg("after: C");
probe.expectMsg("after: D");
probe.expectMsg("after: E");
probe.expectMsg("after: F");
probe.expectMsg("after: G");
probe.expectMsg("after: H");
probe.expectMsg("after: I");
probe.expectMsg("after: J");
}
};
}
@Test
public void illustrateOrderingAndParallelismOfMapAsyncUnordered() throws Exception {
new JavaTestKit(system) {
final EmailServer emailServer = new EmailServer(getRef());
class MockSystem {
class Println {
public <T> void println(T s) {
if (s.toString().startsWith("after:"))
getRef().tell(s, ActorRef.noSender());
}
}
public final Println out = new Println();
}
private final MockSystem System = new MockSystem();
{
//#sometimes-slow-mapAsyncUnordered
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
final SometimesSlowService service = new SometimesSlowService(blockingEc);
final ActorMaterializer mat = ActorMaterializer.create(
ActorMaterializerSettings.create(system).withInputBuffer(4, 4), system);
Source.from(Arrays.asList("a", "B", "C", "D", "e", "F", "g", "H", "i", "J"))
.map(elem -> { System.out.println("before: " + elem); return elem; })
.mapAsyncUnordered(4, service::convert)
.runForeach(elem -> System.out.println("after: " + elem), mat);
//#sometimes-slow-mapAsyncUnordered
final Object[] got = receiveN(10);
final Set<Object> set = new HashSet<>(Arrays.asList(got));
assertTrue(set.contains("after: A"));
assertTrue(set.contains("after: B"));
assertTrue(set.contains("after: C"));
assertTrue(set.contains("after: D"));
assertTrue(set.contains("after: E"));
assertTrue(set.contains("after: F"));
assertTrue(set.contains("after: G"));
assertTrue(set.contains("after: H"));
assertTrue(set.contains("after: I"));
assertTrue(set.contains("after: J"));
}
};
}
}

View file

@ -0,0 +1,259 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.ActorSystem;
import akka.actor.Cancellable;
import akka.http.javadsl.model.Uri;
import akka.dispatch.Futures;
import akka.japi.function.Creator;
import akka.japi.Pair;
import akka.japi.function.Function;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.util.ByteString;
import scala.Option;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.concurrent.Promise;
import scala.runtime.BoxedUnit;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
import java.nio.charset.Charset;
public class MigrationsJava {
// This is compile-only code, no need for actually running anything.
public static ActorMaterializer mat = null;
public static ActorSystem sys = null;
public static class SomeInputStream extends InputStream {
public SomeInputStream() {
}
@Override
public int read() throws IOException {
return 0;
}
}
public static class SomeOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
return;
}
}
public static void main(String[] args) {
Outlet<Integer> outlet = null;
Outlet<Integer> outlet1 = null;
Outlet<Integer> outlet2 = null;
Inlet<Integer> inlet = null;
Inlet<Integer> inlet1 = null;
Inlet<Integer> inlet2 = null;
Flow<Integer, Integer, BoxedUnit> flow = Flow.of(Integer.class);
Flow<Integer, Integer, BoxedUnit> flow1 = Flow.of(Integer.class);
Flow<Integer, Integer, BoxedUnit> flow2 = Flow.of(Integer.class);
Promise<Option<Integer>> promise = null;
{
Graph<SourceShape<Integer>, BoxedUnit> graphSource = null;
Graph<SinkShape<Integer>, BoxedUnit> graphSink = null;
Graph<FlowShape<Integer, Integer>, BoxedUnit> graphFlow = null;
//#flow-wrap
Source<Integer, BoxedUnit> source = Source.fromGraph(graphSource);
Sink<Integer, BoxedUnit> sink = Sink.fromGraph(graphSink);
Flow<Integer, Integer, BoxedUnit> aflow = Flow.fromGraph(graphFlow);
Flow.fromSinkAndSource(Sink.<Integer>head(), Source.single(0));
Flow.fromSinkAndSourceMat(Sink.<Integer>head(), Source.single(0), Keep.left());
//#flow-wrap
Graph<BidiShape<Integer, Integer, Integer, Integer>, BoxedUnit> bidiGraph = null;
//#bidi-wrap
BidiFlow<Integer, Integer, Integer, Integer, BoxedUnit> bidiFlow =
BidiFlow.fromGraph(bidiGraph);
BidiFlow.fromFlows(flow1, flow2);
BidiFlow.fromFlowsMat(flow1, flow2, Keep.both());
//#bidi-wrap
}
{
//#graph-create
GraphDSL.create(builder -> {
//...
return ClosedShape.getInstance();
});
GraphDSL.create(builder -> {
//...
return new FlowShape<>(inlet, outlet);
});
//#graph-create
}
{
//#graph-create-2
GraphDSL.create(builder -> {
//...
return SourceShape.of(outlet);
});
GraphDSL.create(builder -> {
//...
return SinkShape.of(inlet);
});
GraphDSL.create(builder -> {
//...
return FlowShape.of(inlet, outlet);
});
GraphDSL.create(builder -> {
//...
return BidiShape.of(inlet1, outlet1, inlet2, outlet2);
});
//#graph-create-2
}
{
//#graph-builder
GraphDSL.create(builder -> {
builder.from(outlet).toInlet(inlet);
builder.from(outlet).via(builder.add(flow)).toInlet(inlet);
builder.from(builder.add(Source.single(0))).to(builder.add(Sink.head()));
//...
return ClosedShape.getInstance();
});
//#graph-builder
}
//#source-creators
Source<Integer, Promise<Option<Integer>>> src = Source.<Integer>maybe();
// Complete the promise with an empty option to emulate the old lazyEmpty
promise.trySuccess(scala.Option.empty());
final Source<String, Cancellable> ticks = Source.tick(
FiniteDuration.create(0, TimeUnit.MILLISECONDS),
FiniteDuration.create(200, TimeUnit.MILLISECONDS),
"tick");
final Source<Integer, BoxedUnit> pubSource =
Source.fromPublisher(TestPublisher.<Integer>manualProbe(true, sys));
final Source<Integer, BoxedUnit> futSource =
Source.fromFuture(Futures.successful(42));
final Source<Integer, Subscriber<Integer>> subSource =
Source.<Integer>asSubscriber();
//#source-creators
//#sink-creators
final Sink<Integer, BoxedUnit> subSink =
Sink.fromSubscriber(TestSubscriber.<Integer>manualProbe(sys));
//#sink-creators
//#sink-as-publisher
final Sink<Integer, Publisher<Integer>> pubSink =
Sink.<Integer>asPublisher(false);
final Sink<Integer, Publisher<Integer>> pubSinkFanout =
Sink.<Integer>asPublisher(true);
//#sink-as-publisher
//#empty-flow
Flow<Integer, Integer, BoxedUnit> emptyFlow = Flow.<Integer>create();
// or
Flow<Integer, Integer, BoxedUnit> emptyFlow2 = Flow.of(Integer.class);
//#empty-flow
//#flatMapConcat
Flow.<Source<Integer, BoxedUnit>>create().
<Integer, BoxedUnit>flatMapConcat(i -> i);
//#flatMapConcat
//#group-flatten
Flow.of(Integer.class)
.groupBy(2, in -> in % 2) // the first parameter sets max number of substreams
.map(subIn -> + 3)
.concatSubstreams();
//#group-flatten
final int maxDistinctWords = 1000;
//#group-fold
Flow.of(String.class)
.groupBy(maxDistinctWords, i -> i)
.fold(Pair.create("", 0), (pair, word) -> Pair.create(word, pair.second() + 1))
.mergeSubstreams();
//#group-fold
Uri uri = null;
//#raw-query
final akka.japi.Option<String> theRawQueryString = uri.rawQueryString();
//#raw-query
//#query-param
final akka.japi.Option<String> aQueryParam = uri.query().get("a");
//#query-param
//#file-source-sink
final Source<ByteString, Future<Long>> fileSrc =
FileIO.fromFile(new File("."));
final Source<ByteString, Future<Long>> otherFileSrc =
FileIO.fromFile(new File("."), 1024);
final Sink<ByteString, Future<Long>> fileSink =
FileIO.toFile(new File("."));
//#file-source-sink
//#input-output-stream-source-sink
final Source<ByteString, Future<java.lang.Long>> inputStreamSrc =
StreamConverters.fromInputStream((Creator<InputStream>) () -> new SomeInputStream());
final Source<ByteString, Future<java.lang.Long>> otherInputStreamSrc =
StreamConverters.fromInputStream((Creator<InputStream>) () -> new SomeInputStream(), 1024);
final Sink<ByteString, Future<java.lang.Long>> outputStreamSink =
StreamConverters.fromOutputStream((Creator<OutputStream>) () -> new SomeOutputStream());
//#input-output-stream-source-sink
//#output-input-stream-source-sink
final FiniteDuration timeout = FiniteDuration.Zero();
final Source<ByteString, OutputStream> outputStreamSrc =
StreamConverters.asOutputStream();
final Source<ByteString, OutputStream> otherOutputStreamSrc =
StreamConverters.asOutputStream(timeout);
final Sink<ByteString, InputStream> someInputStreamSink =
StreamConverters.asInputStream();
final Sink<ByteString, InputStream> someOtherInputStreamSink =
StreamConverters.asInputStream(timeout);
//#output-input-stream-source-sink
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.japi.tuple.Tuple3;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.stream.testkit.javadsl.TestSource;
import akka.testkit.JavaTestKit;
import akka.testkit.TestLatch;
import scala.collection.Iterator;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import scala.util.Random;
public class RateTransformationDocTest {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RateTransformationDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final Random r = new Random();
@Test
public void conflateShouldSummarize() throws Exception {
//#conflate-summarize
final Flow<Double, Tuple3<Double, Double, Integer>, BoxedUnit> statsFlow =
Flow.of(Double.class)
.conflate(elem -> Collections.singletonList(elem), (acc, elem) -> {
return Stream
.concat(acc.stream(), Collections.singletonList(elem).stream())
.collect(Collectors.toList());
})
.map(s -> {
final Double mean = s.stream().mapToDouble(d -> d).sum() / s.size();
final DoubleStream se = s.stream().mapToDouble(x -> Math.pow(x - mean, 2));
final Double stdDev = Math.sqrt(se.sum() / s.size());
return new Tuple3<>(stdDev, mean, s.size());
});
//#conflate-summarize
final Future<List<Tuple3<Double, Double, Integer>>> fut = Source.repeat(0).map(i -> r.nextGaussian())
.via(statsFlow)
.grouped(10)
.runWith(Sink.head(), mat);
final Duration timeout = Duration.create(100, TimeUnit.MILLISECONDS);
Await.result(fut, timeout);
}
@Test
public void conflateShouldSample() throws Exception {
//#conflate-sample
final Double p = 0.01;
final Flow<Double, Double, BoxedUnit> sampleFlow = Flow.of(Double.class)
.conflate(elem -> Collections.singletonList(elem), (acc, elem) -> {
if (r.nextDouble() < p) {
return Stream
.concat(acc.stream(), Collections.singletonList(elem).stream())
.collect(Collectors.toList());
}
return acc;
})
.mapConcat(d -> d);
//#conflate-sample
final Future<Double> fut = Source.from(new ArrayList<Double>(Collections.nCopies(1000, 1.0)))
.via(sampleFlow)
.runWith(Sink.fold(0.0, (agg, next) -> agg + next), mat);
final Duration timeout = Duration.create(1, TimeUnit.SECONDS);
final Double count = Await.result(fut, timeout);
}
@Test
public void expandShouldRepeatLast() throws Exception {
//#expand-last
final Flow<Double, Double, BoxedUnit> lastFlow = Flow.of(Double.class)
.expand(d -> d, s -> new Pair<>(s, s));
//#expand-last
final Pair<TestPublisher.Probe<Double>, Future<List<Double>>> probeFut = TestSource.<Double> probe(system)
.via(lastFlow)
.grouped(10)
.toMat(Sink.head(), Keep.both())
.run(mat);
final TestPublisher.Probe<Double> probe = probeFut.first();
final Future<List<Double>> fut = probeFut.second();
probe.sendNext(1.0);
final Duration timeout = Duration.create(1, TimeUnit.SECONDS);
final List<Double> expanded = Await.result(fut, timeout);
assertEquals(expanded.size(), 10);
assertEquals(expanded.stream().mapToDouble(d -> d).sum(), 10, 0.1);
}
@Test
public void expandShouldTrackDrift() throws Exception {
@SuppressWarnings("unused")
//#expand-drift
final Flow<Double, Pair<Double, Integer>, BoxedUnit> driftFlow = Flow.of(Double.class)
.expand(d -> new Pair<Double, Integer>(d, 0), t -> {
return new Pair<>(t, new Pair<>(t.first(), t.second() + 1));
});
//#expand-drift
final TestLatch latch = new TestLatch(2, system);
final Flow<Double, Pair<Double, Integer>, BoxedUnit> realDriftFlow = Flow.of(Double.class)
.expand(d -> { latch.countDown(); return new Pair<Double, Integer>(d, 0); }, t -> {
return new Pair<>(t, new Pair<>(t.first(), t.second() + 1));
});
final Pair<TestPublisher.Probe<Double>, TestSubscriber.Probe<Pair<Double, Integer>>> pubSub = TestSource.<Double> probe(system)
.via(realDriftFlow)
.toMat(TestSink.<Pair<Double, Integer>> probe(system), Keep.both())
.run(mat);
final TestPublisher.Probe<Double> pub = pubSub.first();
final TestSubscriber.Probe<Pair<Double, Integer>> sub = pubSub.second();
sub.request(1);
pub.sendNext(1.0);
sub.expectNext(new Pair<>(1.0, 0));
sub.requestNext(new Pair<>(1.0, 1));
sub.requestNext(new Pair<>(1.0, 2));
pub.sendNext(2.0);
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
sub.requestNext(new Pair<>(2.0, 0));
}
}

View file

@ -0,0 +1,264 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.japi.function.Creator;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
import akka.testkit.TestProbe;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
//#imports
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Processor;
//#imports
import org.reactivestreams.Subscription;
import scala.runtime.BoxedUnit;
import java.lang.Exception;
import static docs.stream.ReactiveStreamsDocTest.Fixture.Data.authors;
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
public class ReactiveStreamsDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("ReactiveStreamsDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
static class Fixture {
// below class additionally helps with aligning code includes nicely
static class Data {
static //#authors
final Flow<Tweet, Author, BoxedUnit> authors = Flow.of(Tweet.class)
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
//#authors
}
static interface RS {
//#tweets-publisher
Publisher<Tweet> tweets();
//#tweets-publisher
//#author-storage-subscriber
Subscriber<Author> storage();
//#author-storage-subscriber
//#author-alert-subscriber
Subscriber<Author> alert();
//#author-alert-subscriber
}
}
final TestProbe storageProbe = TestProbe.apply(system);
final TestProbe alertProbe = TestProbe.apply(system);
final Fixture.RS rs = new Fixture.RS() {
@Override
public Publisher<Tweet> tweets() {
return TwitterStreamQuickstartDocTest.Model.tweets.runWith(Sink.asPublisher(false), mat);
}
/**
* This is a minimal version of SubscriberProbe,
* which lives in akka-stream-testkit (test scope) and for
* now wanted to avoid setting up (test -> compile) dependency for maven).
*
* TODO: Once SubscriberProbe is easily used here replace this MPS with it.
*/
class MinimalProbeSubscriber<T> implements Subscriber<T> {
private final ActorRef ref;
public MinimalProbeSubscriber(ActorRef ref) {
this.ref = ref;
}
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(T t) {
ref.tell(t, ActorRef.noSender());
}
@Override
public void onError(Throwable t) {
ref.tell(t, ActorRef.noSender());
}
@Override
public void onComplete() {
ref.tell("complete", ActorRef.noSender());
}
}
@Override
public Subscriber<Author> storage() {
return new MinimalProbeSubscriber<>(storageProbe.ref());
}
@Override
public Subscriber<Author> alert() {
return new MinimalProbeSubscriber<>(alertProbe.ref());
}
};
@Test
public void reactiveStreamsPublisherViaFlowToSubscriber() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
{
//#connect-all
Source.fromPublisher(rs.tweets())
.via(authors)
.to(Sink.fromSubscriber(rs.storage()));
//#connect-all
}
};
}
@Test
public void flowAsPublisherAndSubscriber() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
{
//#flow-publisher-subscriber
final Processor<Tweet, Author> processor =
authors.toProcessor().run(mat);
rs.tweets().subscribe(processor);
processor.subscribe(rs.storage());
//#flow-publisher-subscriber
assertStorageResult();
}
};
}
@Test
public void sourceAsPublisher() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
{
//#source-publisher
final Publisher<Author> authorPublisher =
Source.fromPublisher(rs.tweets()).via(authors).runWith(Sink.asPublisher(false), mat);
authorPublisher.subscribe(rs.storage());
//#source-publisher
assertStorageResult();
}
};
}
@Test
public void sourceAsFanoutPublisher() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
{
//#source-fanoutPublisher
final Publisher<Author> authorPublisher =
Source.fromPublisher(rs.tweets())
.via(authors)
.runWith(Sink.asPublisher(true), mat);
authorPublisher.subscribe(rs.storage());
authorPublisher.subscribe(rs.alert());
//#source-fanoutPublisher
assertStorageResult();
}
};
}
@Test
public void sinkAsSubscriber() throws Exception {
new JavaTestKit(system) {
final TestProbe probe = new TestProbe(system);
{
//#sink-subscriber
final Subscriber<Author> storage = rs.storage();
final Subscriber<Tweet> tweetSubscriber =
authors
.to(Sink.fromSubscriber(storage))
.runWith(Source.asSubscriber(), mat);
rs.tweets().subscribe(tweetSubscriber);
//#sink-subscriber
assertStorageResult();
}
};
}
@Test
public void useProcessor() throws Exception {
new JavaTestKit(system) {
{
//#use-processor
// An example Processor factory
final Creator<Processor<Integer, Integer>> factory =
new Creator<Processor<Integer, Integer>>() {
public Processor<Integer, Integer> create() {
return Flow.of(Integer.class).toProcessor().run(mat);
}
};
final Flow<Integer, Integer, BoxedUnit> flow = Flow.fromProcessor(factory);
//#use-processor
}
};
}
void assertStorageResult() {
storageProbe.expectMsg(new Author("rolandkuhn"));
storageProbe.expectMsg(new Author("patriknw"));
storageProbe.expectMsg(new Author("bantonsson"));
storageProbe.expectMsg(new Author("drewhk"));
storageProbe.expectMsg(new Author("ktosopl"));
storageProbe.expectMsg(new Author("mmartynas"));
storageProbe.expectMsg(new Author("akkateam"));
storageProbe.expectMsg("complete");
}
}

View file

@ -0,0 +1,64 @@
package docs.stream;
import akka.actor.ActorRef;
import java.util.function.Predicate;
/**
* Acts as if `System.out.println()` yet swallows all messages. Useful for putting printlines in examples yet without poluting the build with them.
*/
public class SilenceSystemOut {
private SilenceSystemOut() {
}
public static System get() {
return new System(new System.Println() {
@Override
public void println(String s) {
// ignore
}
});
}
public static System get(ActorRef probe) {
return new System(new System.Println() {
@Override
public void println(String s) {
probe.tell(s, ActorRef.noSender());
}
});
}
public static System get(Predicate<String> filter, ActorRef probe) {
return new System(new System.Println() {
@Override
public void println(String s) {
if (filter.test(s))
probe.tell(s, ActorRef.noSender());
}
});
}
public static class System {
public final Println out;
public System(Println out) {
this.out = out;
}
public static abstract class Println {
public abstract void println(String s);
public void println(Object s) {
println(s.toString());
}
public void printf(String format, Object... args) {
println(String.format(format, args));
}
}
}
}

View file

@ -0,0 +1,128 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import akka.actor.ActorSystem;
import akka.actor.Cancellable;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
public class StreamBuffersRateDocTest {
static class Job {}
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("StreamBufferRateDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final SilenceSystemOut.System System = SilenceSystemOut.get();
@Test
public void demonstratePipelining() {
//#pipelining
Source.from(Arrays.asList(1, 2, 3))
.map(i -> {System.out.println("A: " + i); return i;})
.map(i -> {System.out.println("B: " + i); return i;})
.map(i -> {System.out.println("C: " + i); return i;})
.runWith(Sink.ignore(), mat);
//#pipelining
}
@Test
@SuppressWarnings("unused")
public void demonstrateBufferSizes() {
//#materializer-buffer
final Materializer materializer = ActorMaterializer.create(
ActorMaterializerSettings.create(system)
.withInputBuffer(64, 64), system);
//#materializer-buffer
//#section-buffer
final Flow<Integer, Integer, BoxedUnit> flow1 =
Flow.of(Integer.class)
.map(elem -> elem * 2) // the buffer size of this map is 1
.withAttributes(Attributes.inputBuffer(1, 1));
final Flow<Integer, Integer, BoxedUnit> flow2 =
flow1.via(
Flow.of(Integer.class)
.map(elem -> elem / 2)); // the buffer size of this map is the default
//#section-buffer
}
@Test
public void demonstrateBufferAbstractionLeak() {
//#buffering-abstraction-leak
final FiniteDuration oneSecond =
FiniteDuration.create(1, TimeUnit.SECONDS);
final Source<String, Cancellable> msgSource =
Source.tick(oneSecond, oneSecond, "message!");
final Source<String, Cancellable> tickSource =
Source.tick(oneSecond.mul(3), oneSecond.mul(3), "tick");
final Flow<String, Integer, BoxedUnit> conflate =
Flow.of(String.class).conflate(
first -> 1, (count, elem) -> count + 1);
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final FanInShape2<String, Integer, Integer> zipper =
b.add(ZipWith.create((String tick, Integer count) -> count));
b.from(b.add(msgSource)).via(b.add(conflate)).toInlet(zipper.in1());
b.from(b.add(tickSource)).toInlet(zipper.in0());
b.from(zipper.out()).to(b.add(Sink.foreach(elem -> System.out.println(elem))));
return ClosedShape.getInstance();
})).run(mat);
//#buffering-abstraction-leak
}
@Test
public void demonstrateExplicitBuffers() {
final Source<Job, BoxedUnit> inboundJobsConnector = Source.empty();
//#explicit-buffers-backpressure
// Getting a stream of jobs from an imaginary external system as a Source
final Source<Job, BoxedUnit> jobs = inboundJobsConnector;
jobs.buffer(1000, OverflowStrategy.backpressure());
//#explicit-buffers-backpressure
//#explicit-buffers-droptail
jobs.buffer(1000, OverflowStrategy.dropTail());
//#explicit-buffers-droptail
//#explicit-buffers-dropnew
jobs.buffer(1000, OverflowStrategy.dropNew());
//#explicit-buffers-dropnew
//#explicit-buffers-drophead
jobs.buffer(1000, OverflowStrategy.dropHead());
//#explicit-buffers-drophead
//#explicit-buffers-dropbuffer
jobs.buffer(1000, OverflowStrategy.dropBuffer());
//#explicit-buffers-dropbuffer
//#explicit-buffers-fail
jobs.buffer(1000, OverflowStrategy.fail());
//#explicit-buffers-fail
}
}

View file

@ -0,0 +1,176 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import static org.junit.Assert.assertEquals;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import akka.actor.*;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
public class StreamPartialFlowGraphDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("StreamPartialFlowGraphDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void demonstrateBuildWithOpenPorts() throws Exception {
//#simple-partial-flow-graph
final Graph<FanInShape2<Integer, Integer, Integer>, BoxedUnit> zip =
ZipWith.create((Integer left, Integer right) -> Math.max(left, right));
final Graph<UniformFanInShape<Integer, Integer>, BoxedUnit> pickMaxOfThree =
GraphDSL.create(builder -> {
final FanInShape2<Integer, Integer, Integer> zip1 = builder.add(zip);
final FanInShape2<Integer, Integer, Integer> zip2 = builder.add(zip);
builder.from(zip1.out()).toInlet(zip2.in0());
// return the shape, which has three inputs and one output
return new UniformFanInShape<Integer, Integer>(zip2.out(),
new Inlet[] {zip1.in0(), zip1.in1(), zip2.in1()});
});
final Sink<Integer, Future<Integer>> resultSink = Sink.<Integer>head();
final RunnableGraph<Future<Integer>> g =
RunnableGraph.<Future<Integer>>fromGraph(
GraphDSL.create(resultSink, (builder, sink) -> {
// import the partial flow graph explicitly
final UniformFanInShape<Integer, Integer> pm = builder.add(pickMaxOfThree);
builder.from(builder.add(Source.single(1))).toInlet(pm.in(0));
builder.from(builder.add(Source.single(2))).toInlet(pm.in(1));
builder.from(builder.add(Source.single(3))).toInlet(pm.in(2));
builder.from(pm.out()).to(sink);
return ClosedShape.getInstance();
}));
final Future<Integer> max = g.run(mat);
//#simple-partial-flow-graph
assertEquals(Integer.valueOf(3), Await.result(max, Duration.create(3, TimeUnit.SECONDS)));
}
//#source-from-partial-flow-graph
// first create an indefinite source of integer numbers
class Ints implements Iterator<Integer> {
private int next = 0;
@Override
public boolean hasNext() {
return true;
}
@Override
public Integer next() {
return next++;
}
}
//#source-from-partial-flow-graph
@Test
public void demonstrateBuildSourceFromPartialFlowGraphCreate() throws Exception {
//#source-from-partial-flow-graph
final Source<Integer, BoxedUnit> ints = Source.fromIterator(() -> new Ints());
final Source<Pair<Integer, Integer>, BoxedUnit> pairs = Source.fromGraph(
GraphDSL.create(
builder -> {
final FanInShape2<Integer, Integer, Pair<Integer, Integer>> zip =
builder.add(Zip.create());
builder.from(builder.add(ints.filter(i -> i % 2 == 0))).toInlet(zip.in0());
builder.from(builder.add(ints.filter(i -> i % 2 == 1))).toInlet(zip.in1());
return SourceShape.of(zip.out());
}));
final Future<Pair<Integer, Integer>> firstPair =
pairs.runWith(Sink.<Pair<Integer, Integer>>head(), mat);
//#source-from-partial-flow-graph
assertEquals(new Pair<>(0, 1), Await.result(firstPair, Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateBuildFlowFromPartialFlowGraphCreate() throws Exception {
//#flow-from-partial-flow-graph
final Flow<Integer, Pair<Integer, String>, BoxedUnit> pairs = Flow.fromGraph(GraphDSL.create(
b -> {
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
final FanInShape2<Integer, String, Pair<Integer, String>> zip =
b.add(Zip.create());
b.from(bcast).toInlet(zip.in0());
b.from(bcast).via(b.add(Flow.of(Integer.class).map(i -> i.toString()))).toInlet(zip.in1());
return FlowShape.of(bcast.in(), zip.out());
}));
//#flow-from-partial-flow-graph
final Future<Pair<Integer, String>> matSink =
//#flow-from-partial-flow-graph
Source.single(1).via(pairs).runWith(Sink.<Pair<Integer, String>>head(), mat);
//#flow-from-partial-flow-graph
assertEquals(new Pair<>(1, "1"), Await.result(matSink, Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateBuildSourceWithCombine() throws Exception {
//#source-combine
Source<Integer, BoxedUnit> source1 = Source.single(1);
Source<Integer, BoxedUnit> source2 = Source.single(2);
final Source<Integer, BoxedUnit> sources = Source.combine(source1, source2, new ArrayList<>(),
i -> Merge.<Integer>create(i));
//#source-combine
final Future<Integer> result=
//#source-combine
sources.runWith(Sink.<Integer, Integer>fold(0, (a,b) -> a + b), mat);
//#source-combine
assertEquals(Integer.valueOf(3), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
}
@Test
public void demonstrateBuildSinkWithCombine() throws Exception {
final JavaTestKit probe = new JavaTestKit(system);
ActorRef actorRef = probe.getRef();
//#sink-combine
Sink<Integer, BoxedUnit> sendRmotely = Sink.actorRef(actorRef, "Done");
Sink<Integer, Future<BoxedUnit>> localProcessing = Sink.<Integer>foreach(a -> { /*do something useful*/ } );
Sink<Integer, BoxedUnit> sinks = Sink.combine(sendRmotely,localProcessing, new ArrayList<>(), a -> Broadcast.create(a));
Source.<Integer>from(Arrays.asList(new Integer[]{0, 1, 2})).runWith(sinks, mat);
//#sink-combine
probe.expectMsgEquals(0);
probe.expectMsgEquals(1);
probe.expectMsgEquals(2);
}
}

View file

@ -0,0 +1,229 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.assertEquals;
import akka.actor.*;
import akka.dispatch.Futures;
import akka.testkit.*;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.testkit.*;
import akka.stream.testkit.javadsl.*;
import akka.testkit.TestProbe;
import scala.util.*;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
public class StreamTestKitDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("StreamTestKitDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void strictCollection() throws Exception {
//#strict-collection
final Sink<Integer, Future<Integer>> sinkUnderTest = Flow.of(Integer.class)
.map(i -> i * 2)
.toMat(Sink.fold(0, (agg, next) -> agg + next), Keep.right());
final Future<Integer> future = Source.from(Arrays.asList(1, 2, 3, 4))
.runWith(sinkUnderTest, mat);
final Integer result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
assert(result == 20);
//#strict-collection
}
@Test
public void groupedPartOfInfiniteStream() throws Exception {
//#grouped-infinite
final Source<Integer, BoxedUnit> sourceUnderTest = Source.repeat(1)
.map(i -> i * 2);
final Future<List<Integer>> future = sourceUnderTest
.grouped(10)
.runWith(Sink.head(), mat);
final List<Integer> result =
Await.result(future, Duration.create(1, TimeUnit.SECONDS));
assertEquals(result, Collections.nCopies(10, 2));
//#grouped-infinite
}
@Test
public void foldedStream() throws Exception {
//#folded-stream
final Flow<Integer, Integer, BoxedUnit> flowUnderTest = Flow.of(Integer.class)
.takeWhile(i -> i < 5);
final Future<Integer> future = Source.from(Arrays.asList(1, 2, 3, 4, 5, 6))
.via(flowUnderTest).runWith(Sink.fold(0, (agg, next) -> agg + next), mat);
final Integer result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
assert(result == 10);
//#folded-stream
}
@Test
public void pipeToTestProbe() throws Exception {
//#pipeto-testprobe
final Source<List<Integer>, BoxedUnit> sourceUnderTest = Source
.from(Arrays.asList(1, 2, 3, 4))
.grouped(2);
final TestProbe probe = new TestProbe(system);
final Future<List<List<Integer>>> future = sourceUnderTest
.grouped(2)
.runWith(Sink.head(), mat);
akka.pattern.Patterns.pipe(future, system.dispatcher()).to(probe.ref());
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS),
Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4))
);
//#pipeto-testprobe
}
public enum Tick { TOCK, COMPLETED };
@Test
public void sinkActorRef() throws Exception {
//#sink-actorref
final Source<Tick, Cancellable> sourceUnderTest = Source.tick(
FiniteDuration.create(0, TimeUnit.MILLISECONDS),
FiniteDuration.create(200, TimeUnit.MILLISECONDS),
Tick.TOCK);
final TestProbe probe = new TestProbe(system);
final Cancellable cancellable = sourceUnderTest
.to(Sink.actorRef(probe.ref(), Tick.COMPLETED)).run(mat);
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.TOCK);
probe.expectNoMsg(Duration.create(100, TimeUnit.MILLISECONDS));
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.TOCK);
cancellable.cancel();
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.COMPLETED);
//#sink-actorref
}
@Test
public void sourceActorRef() throws Exception {
//#source-actorref
final Sink<Integer, Future<String>> sinkUnderTest = Flow.of(Integer.class)
.map(i -> i.toString())
.toMat(Sink.fold("", (agg, next) -> agg + next), Keep.right());
final Pair<ActorRef, Future<String>> refAndFuture =
Source.<Integer>actorRef(8, OverflowStrategy.fail())
.toMat(sinkUnderTest, Keep.both())
.run(mat);
final ActorRef ref = refAndFuture.first();
final Future<String> future = refAndFuture.second();
ref.tell(1, ActorRef.noSender());
ref.tell(2, ActorRef.noSender());
ref.tell(3, ActorRef.noSender());
ref.tell(new akka.actor.Status.Success("done"), ActorRef.noSender());
final String result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
assertEquals(result, "123");
//#source-actorref
}
@Test
public void testSinkProbe() {
//#test-sink-probe
final Source<Integer, BoxedUnit> sourceUnderTest = Source.from(Arrays.asList(1, 2, 3, 4))
.filter(elem -> elem % 2 == 0)
.map(elem -> elem * 2);
sourceUnderTest
.runWith(TestSink.probe(system), mat)
.request(2)
.expectNext(4, 8)
.expectComplete();
//#test-sink-probe
}
@Test
public void testSourceProbe() {
//#test-source-probe
final Sink<Integer, BoxedUnit> sinkUnderTest = Sink.cancelled();
TestSource.<Integer>probe(system)
.toMat(sinkUnderTest, Keep.left())
.run(mat)
.expectCancellation();
//#test-source-probe
}
@Test
public void injectingFailure() throws Exception {
//#injecting-failure
final Sink<Integer, Future<Integer>> sinkUnderTest = Sink.head();
final Pair<TestPublisher.Probe<Integer>, Future<Integer>> probeAndFuture =
TestSource.<Integer>probe(system)
.toMat(sinkUnderTest, Keep.both())
.run(mat);
final TestPublisher.Probe<Integer> probe = probeAndFuture.first();
final Future<Integer> future = probeAndFuture.second();
probe.sendError(new Exception("boom"));
Await.ready(future, Duration.create(1, TimeUnit.SECONDS));
final Throwable exception = ((Failure)future.value().get()).exception();
assertEquals(exception.getMessage(), "boom");
//#injecting-failure
}
@Test
public void testSourceAndTestSink() throws Exception {
//#test-source-and-sink
final Flow<Integer, Integer, BoxedUnit> flowUnderTest = Flow.of(Integer.class)
.mapAsyncUnordered(2, sleep -> akka.pattern.Patterns.after(
Duration.create(10, TimeUnit.MILLISECONDS),
system.scheduler(),
system.dispatcher(),
Futures.successful(sleep)
));
final Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubAndSub =
TestSource.<Integer>probe(system)
.via(flowUnderTest)
.toMat(TestSink.<Integer>probe(system), Keep.both())
.run(mat);
final TestPublisher.Probe<Integer> pub = pubAndSub.first();
final TestSubscriber.Probe<Integer> sub = pubAndSub.second();
sub.request(3);
pub.sendNext(3);
pub.sendNext(2);
pub.sendNext(1);
sub.expectNextUnordered(1, 2, 3);
pub.sendError(new Exception("Power surge in the linear subroutine C-47!"));
final Throwable ex = sub.expectError();
assert(ex.getMessage().contains("C-47"));
//#test-source-and-sink
}
}

View file

@ -0,0 +1,362 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream;
import akka.actor.ActorSystem;
import akka.dispatch.Foreach;
import akka.japi.JavaPartialFunction;
import akka.testkit.JavaTestKit;
import akka.stream.*;
import akka.stream.javadsl.*;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Hashtag;
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
import static docs.stream.TwitterStreamQuickstartDocTest.Model.tweets;
@SuppressWarnings("unused")
public class TwitterStreamQuickstartDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("SampleActorTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
static abstract class Model {
//#model
public static class Author {
public final String handle;
public Author(String handle) {
this.handle = handle;
}
// ...
//#model
@Override
public String toString() {
return "Author(" + handle + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Author author = (Author) o;
if (handle != null ? !handle.equals(author.handle) : author.handle != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return handle != null ? handle.hashCode() : 0;
}
//#model
}
//#model
//#model
public static class Hashtag {
public final String name;
public Hashtag(String name) {
this.name = name;
}
// ...
//#model
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Hashtag other = (Hashtag) obj;
return name.equals(other.name);
}
@Override
public String toString() {
return "Hashtag(" + name + ")";
}
//#model
}
//#model
//#model
public static class Tweet {
public final Author author;
public final long timestamp;
public final String body;
public Tweet(Author author, long timestamp, String body) {
this.author = author;
this.timestamp = timestamp;
this.body = body;
}
public Set<Hashtag> hashtags() {
return Arrays.asList(body.split(" ")).stream()
.filter(a -> a.startsWith("#"))
.map(a -> new Hashtag(a))
.collect(Collectors.toSet());
}
// ...
//#model
@Override
public String toString() {
return "Tweet(" + author + "," + timestamp + "," + body + ")";
}
//#model
}
//#model
//#model
public static final Hashtag AKKA = new Hashtag("#akka");
//#model
public static final Source<Tweet, BoxedUnit> tweets = Source.from(
Arrays.asList(new Tweet[] {
new Tweet(new Author("rolandkuhn"), System.currentTimeMillis(), "#akka rocks!"),
new Tweet(new Author("patriknw"), System.currentTimeMillis(), "#akka !"),
new Tweet(new Author("bantonsson"), System.currentTimeMillis(), "#akka !"),
new Tweet(new Author("drewhk"), System.currentTimeMillis(), "#akka !"),
new Tweet(new Author("ktosopl"), System.currentTimeMillis(), "#akka on the rocks!"),
new Tweet(new Author("mmartynas"), System.currentTimeMillis(), "wow #akka !"),
new Tweet(new Author("akkateam"), System.currentTimeMillis(), "#akka rocks!"),
new Tweet(new Author("bananaman"), System.currentTimeMillis(), "#bananas rock!"),
new Tweet(new Author("appleman"), System.currentTimeMillis(), "#apples rock!"),
new Tweet(new Author("drama"), System.currentTimeMillis(), "we compared #apples to #oranges!")
}));
}
static abstract class Example0 {
//#tweet-source
Source<Tweet, BoxedUnit> tweets;
//#tweet-source
}
static abstract class Example1 {
//#first-sample
//#materializer-setup
final ActorSystem system = ActorSystem.create("reactive-tweets");
final Materializer mat = ActorMaterializer.create(system);
//#first-sample
//#materializer-setup
}
static class Example2 {
public void run(final Materializer mat) throws TimeoutException, InterruptedException {
//#backpressure-by-readline
final Future<?> completion =
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
.map(i -> { System.out.println("map => " + i); return i; })
.runForeach(i -> System.console().readLine("Element = %s continue reading? [press enter]\n", i), mat);
Await.ready(completion, FiniteDuration.create(1, TimeUnit.MINUTES));
//#backpressure-by-readline
}
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void demonstrateFilterAndMap() {
final SilenceSystemOut.System System = SilenceSystemOut.get();
//#first-sample
//#authors-filter-map
final Source<Author, BoxedUnit> authors =
tweets
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> t.author);
//#first-sample
//#authors-filter-map
new Object() {
//#authors-collect
JavaPartialFunction<Tweet, Author> collectFunction =
new JavaPartialFunction<Tweet, Author>() {
public Author apply(Tweet t, boolean isCheck) {
if (t.hashtags().contains(AKKA)) {
if (isCheck) return null; // to spare the expensive or side-effecting code
return t.author;
} else {
throw noMatch();
}
}
};
final Source<Author, BoxedUnit> authors =
tweets.collect(collectFunction);
//#authors-collect
};
//#first-sample
//#authors-foreachsink-println
authors.runWith(Sink.foreach(a -> System.out.println(a)), mat);
//#first-sample
//#authors-foreachsink-println
//#authors-foreach-println
authors.runForeach(a -> System.out.println(a), mat);
//#authors-foreach-println
}
@Test
public void demonstrateMapConcat() {
//#hashtags-mapConcat
final Source<Hashtag, BoxedUnit> hashtags =
tweets.mapConcat(t -> new ArrayList<Hashtag>(t.hashtags()));
//#hashtags-mapConcat
}
static abstract class HiddenDefinitions {
//#flow-graph-broadcast
Sink<Author, BoxedUnit> writeAuthors;
Sink<Hashtag, BoxedUnit> writeHashtags;
//#flow-graph-broadcast
}
@Test
public void demonstrateBroadcast() {
final Sink<Author, Future<BoxedUnit>> writeAuthors = Sink.ignore();
final Sink<Hashtag, Future<BoxedUnit>> writeHashtags = Sink.ignore();
//#flow-graph-broadcast
RunnableGraph.fromGraph(GraphDSL.create(b -> {
final UniformFanOutShape<Tweet, Tweet> bcast = b.add(Broadcast.create(2));
final FlowShape<Tweet, Author> toAuthor =
b.add(Flow.of(Tweet.class).map(t -> t.author));
final FlowShape<Tweet, Hashtag> toTags =
b.add(Flow.of(Tweet.class).mapConcat(t -> new ArrayList<Hashtag>(t.hashtags())));
final SinkShape<Author> authors = b.add(writeAuthors);
final SinkShape<Hashtag> hashtags = b.add(writeHashtags);
b.from(b.add(tweets)).viaFanOut(bcast).via(toAuthor).to(authors);
b.from(bcast).via(toTags).to(hashtags);
return ClosedShape.getInstance();
})).run(mat);
//#flow-graph-broadcast
}
long slowComputation(Tweet t) {
try {
// act as if performing some heavy computation
Thread.sleep(500);
} catch (InterruptedException e) {}
return 42;
}
@Test
public void demonstrateSlowProcessing() {
//#tweets-slow-consumption-dropHead
tweets
.buffer(10, OverflowStrategy.dropHead())
.map(t -> slowComputation(t))
.runWith(Sink.ignore(), mat);
//#tweets-slow-consumption-dropHead
}
@Test
public void demonstrateCountOnFiniteStream() {
//#tweets-fold-count
final Sink<Integer, Future<Integer>> sumSink =
Sink.<Integer, Integer>fold(0, (acc, elem) -> acc + elem);
final RunnableGraph<Future<Integer>> counter =
tweets.map(t -> 1).toMat(sumSink, Keep.right());
final Future<Integer> sum = counter.run(mat);
sum.foreach(new Foreach<Integer>() {
public void each(Integer c) {
System.out.println("Total tweets processed: " + c);
}
}, system.dispatcher());
//#tweets-fold-count
new Object() {
//#tweets-fold-count-oneline
final Future<Integer> sum = tweets.map(t -> 1).runWith(sumSink, mat);
//#tweets-fold-count-oneline
};
}
@Test
public void demonstrateMaterializeMultipleTimes() {
final Source<Tweet, BoxedUnit> tweetsInMinuteFromNow = tweets; // not really in second, just acting as if
//#tweets-runnable-flow-materialized-twice
final Sink<Integer, Future<Integer>> sumSink =
Sink.<Integer, Integer>fold(0, (acc, elem) -> acc + elem);
final RunnableGraph<Future<Integer>> counterRunnableGraph =
tweetsInMinuteFromNow
.filter(t -> t.hashtags().contains(AKKA))
.map(t -> 1)
.toMat(sumSink, Keep.right());
// materialize the stream once in the morning
final Future<Integer> morningTweetsCount = counterRunnableGraph.run(mat);
// and once in the evening, reusing the blueprint
final Future<Integer> eveningTweetsCount = counterRunnableGraph.run(mat);
//#tweets-runnable-flow-materialized-twice
}
}

View file

@ -0,0 +1,87 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream.io;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import akka.actor.ActorSystem;
import akka.stream.ActorAttributes;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.FileIO;
import docs.stream.SilenceSystemOut;
import docs.stream.cookbook.RecipeParseLines;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Future;
import scala.runtime.BoxedUnit;
import akka.stream.*;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
public class StreamFileDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("StreamFileDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final SilenceSystemOut.System System = SilenceSystemOut.get();
{
//#file-source
final File file = new File("example.csv");
//#file-source
}
@Test
public void demonstrateMaterializingBytesWritten() throws IOException {
final File file = File.createTempFile(getClass().getName(), ".tmp");
try {
//#file-source
Sink<ByteString, Future<BoxedUnit>> printlnSink =
Sink.foreach(chunk -> System.out.println(chunk.utf8String()));
Future<Long> bytesWritten =
FileIO.fromFile(file)
.to(printlnSink)
.run(mat);
//#file-source
} finally {
file.delete();
}
}
@Test
public void demonstrateSettingDispatchersInCode() throws IOException {
final File file = File.createTempFile(getClass().getName(), ".tmp");
try {
Sink<ByteString, Future<Long>> byteStringFutureSink =
//#custom-dispatcher-code
FileIO.toFile(file)
.withAttributes(ActorAttributes.dispatcher("custom-blocking-io-dispatcher"));
//#custom-dispatcher-code
} finally {
file.delete();
}
}
}

View file

@ -0,0 +1,184 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.stream.io;
import java.util.concurrent.ConcurrentLinkedQueue;
import akka.stream.io.Framing;
import docs.stream.SilenceSystemOut;
import java.net.InetSocketAddress;
import docs.util.SocketUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Future;
import scala.runtime.BoxedUnit;
import akka.actor.ActorSystem;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.javadsl.Tcp.*;
import akka.stream.stage.*;
import akka.testkit.JavaTestKit;
import akka.testkit.TestProbe;
import akka.util.ByteString;
public class StreamTcpDocTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("StreamTcpDocTest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final SilenceSystemOut.System System = SilenceSystemOut.get();
private final ConcurrentLinkedQueue<String> input = new ConcurrentLinkedQueue<String>();
{
input.add("Hello world");
input.add("What a lovely day");
}
private String readLine(String prompt) {
String s = input.poll();
return (s == null ? "q": s);
}
@Test
public void demonstrateSimpleServerConnection() {
{
//#echo-server-simple-bind
// IncomingConnection and ServerBinding imported from Tcp
final Source<IncomingConnection, Future<ServerBinding>> connections =
Tcp.get(system).bind("127.0.0.1", 8889);
//#echo-server-simple-bind
}
{
final InetSocketAddress localhost = SocketUtils.temporaryServerAddress();
final Source<IncomingConnection, Future<ServerBinding>> connections =
Tcp.get(system).bind(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
//#echo-server-simple-handle
connections.runForeach(connection -> {
System.out.println("New connection from: " + connection.remoteAddress());
final Flow<ByteString, ByteString, BoxedUnit> echo = Flow.of(ByteString.class)
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
.map(bytes -> bytes.utf8String())
.map(s -> s + "!!!\n")
.map(s -> ByteString.fromString(s));
connection.handleWith(echo, mat);
}, mat);
//#echo-server-simple-handle
}
}
@Test
public void actuallyWorkingClientServerApp() {
final InetSocketAddress localhost = SocketUtils.temporaryServerAddress();
final TestProbe serverProbe = new TestProbe(system);
final Source<IncomingConnection,Future<ServerBinding>> connections =
Tcp.get(system).bind(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
//#welcome-banner-chat-server
connections.runForeach(connection -> {
// server logic, parses incoming commands
final PushStage<String, String> commandParser = new PushStage<String, String>() {
@Override public SyncDirective onPush(String elem, Context<String> ctx) {
if (elem.equals("BYE"))
return ctx.finish();
else
return ctx.push(elem + "!");
}
};
final String welcomeMsg = "Welcome to: " + connection.localAddress() +
" you are: " + connection.remoteAddress() + "!\n";
final Source<ByteString, BoxedUnit> welcome =
Source.single(ByteString.fromString(welcomeMsg));
final Flow<ByteString, ByteString, BoxedUnit> echoFlow =
Flow.of(ByteString.class)
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
.map(bytes -> bytes.utf8String())
//#welcome-banner-chat-server
.map(command -> {
serverProbe.ref().tell(command, null);
return command;
})
//#welcome-banner-chat-server
.transform(() -> commandParser)
.map(s -> s + "\n")
.map(s -> ByteString.fromString(s));
final Flow<ByteString, ByteString, BoxedUnit> serverLogic =
Flow.fromGraph(GraphDSL.create(builder -> {
final UniformFanInShape<ByteString, ByteString> concat =
builder.add(Concat.create());
final FlowShape<ByteString, ByteString> echo = builder.add(echoFlow);
builder
.from(builder.add(welcome)).toFanIn(concat)
.from(echo).toFanIn(concat);
return FlowShape.of(echo.in(), concat.out());
}));
connection.handleWith(serverLogic, mat);
}, mat);
//#welcome-banner-chat-server
{
//#repl-client
final Flow<ByteString, ByteString, Future<OutgoingConnection>> connection =
Tcp.get(system).outgoingConnection("127.0.0.1", 8889);
//#repl-client
}
{
final Flow<ByteString, ByteString, Future<OutgoingConnection>> connection =
Tcp.get(system).outgoingConnection(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
//#repl-client
final PushStage<String, ByteString> replParser = new PushStage<String, ByteString>() {
@Override public SyncDirective onPush(String elem, Context<ByteString> ctx) {
if (elem.equals("q"))
return ctx.pushAndFinish(ByteString.fromString("BYE\n"));
else
return ctx.push(ByteString.fromString(elem + "\n"));
}
};
final Flow<ByteString, ByteString, BoxedUnit> repl = Flow.of(ByteString.class)
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
.map(bytes -> bytes.utf8String())
.map(text -> {System.out.println("Server: " + text); return "next";})
.map(elem -> readLine("> "))
.transform(() -> replParser);
connection.join(repl).run(mat);
//#repl-client
}
serverProbe.expectMsg("Hello world");
serverProbe.expectMsg("What a lovely day");
serverProbe.expectMsg("BYE");
}
}

View file

@ -0,0 +1,207 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.stage.Context;
import akka.stream.stage.PushPullStage;
import akka.stream.stage.PushStage;
import akka.stream.stage.SyncDirective;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.Tuple2;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class RecipeByteStrings extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeByteStrings");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
final Source<ByteString, BoxedUnit> rawBytes = Source.from(Arrays.asList(
ByteString.fromArray(new byte[] { 1, 2 }),
ByteString.fromArray(new byte[] { 3 }),
ByteString.fromArray(new byte[] { 4, 5, 6 }),
ByteString.fromArray(new byte[] { 7, 8, 9 })));
@Test
public void chunker() throws Exception {
new JavaTestKit(system) {
final int CHUNK_LIMIT = 2;
//#bytestring-chunker
class Chunker extends PushPullStage<ByteString, ByteString> {
private final int chunkSize;
private ByteString buffer = ByteString.empty();
public Chunker(int chunkSize) {
this.chunkSize = chunkSize;
}
@Override
public SyncDirective onPush(ByteString elem, Context<ByteString> ctx) {
buffer = buffer.concat(elem);
return emitChunkOrPull(ctx);
}
@Override
public SyncDirective onPull(Context<ByteString> ctx) {
return emitChunkOrPull(ctx);
}
public SyncDirective emitChunkOrPull(Context<ByteString> ctx) {
if (buffer.isEmpty()) {
return ctx.pull();
} else {
Tuple2<ByteString, ByteString> split = buffer.splitAt(chunkSize);
ByteString emit = split._1();
buffer = split._2();
return ctx.push(emit);
}
}
}
//#bytestring-chunker
{
//#bytestring-chunker2
Source<ByteString, BoxedUnit> chunksStream =
rawBytes.transform(() -> new Chunker(CHUNK_LIMIT));
//#bytestring-chunker2
Future<List<ByteString>> chunksFuture = chunksStream.grouped(10).runWith(Sink.head(), mat);
List<ByteString> chunks = Await.result(chunksFuture, FiniteDuration.create(3, TimeUnit.SECONDS));
for (ByteString chunk : chunks) {
assertTrue(chunk.size() <= 2);
}
ByteString sum = ByteString.empty();
for (ByteString chunk : chunks) {
sum = sum.concat(chunk);
}
assertEquals(sum, ByteString.fromArray(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
}
};
}
@Test
public void limiterShouldWork() throws Exception {
new JavaTestKit(system) {
final int SIZE_LIMIT = 9;
//#bytes-limiter
class ByteLimiter extends PushStage<ByteString, ByteString> {
final long maximumBytes;
private int count = 0;
public ByteLimiter(long maximumBytes) {
this.maximumBytes = maximumBytes;
}
@Override
public SyncDirective onPush(ByteString chunk, Context<ByteString> ctx) {
count += chunk.size();
if (count > maximumBytes) {
return ctx.fail(new IllegalStateException("Too much bytes"));
} else {
return ctx.push(chunk);
}
}
}
//#bytes-limiter
{
//#bytes-limiter2
Flow<ByteString, ByteString, BoxedUnit> limiter =
Flow.of(ByteString.class).transform(() -> new ByteLimiter(SIZE_LIMIT));
//#bytes-limiter2
final Source<ByteString, BoxedUnit> bytes1 = Source.from(Arrays.asList(
ByteString.fromArray(new byte[] { 1, 2 }),
ByteString.fromArray(new byte[] { 3 }),
ByteString.fromArray(new byte[] { 4, 5, 6 }),
ByteString.fromArray(new byte[] { 7, 8, 9 })));
final Source<ByteString, BoxedUnit> bytes2 = Source.from(Arrays.asList(
ByteString.fromArray(new byte[] { 1, 2 }),
ByteString.fromArray(new byte[] { 3 }),
ByteString.fromArray(new byte[] { 4, 5, 6 }),
ByteString.fromArray(new byte[] { 7, 8, 9, 10 })));
FiniteDuration threeSeconds = FiniteDuration.create(3, TimeUnit.SECONDS);
List<ByteString> got = Await.result(bytes1.via(limiter).grouped(10).runWith(Sink.head(), mat), threeSeconds);
ByteString acc = ByteString.empty();
for (ByteString b : got) {
acc = acc.concat(b);
}
assertEquals(acc, ByteString.fromArray(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
boolean thrown = false;
try {
Await.result(bytes2.via(limiter).grouped(10).runWith(Sink.head(), mat), threeSeconds);
} catch (IllegalStateException ex) {
thrown = true;
}
assertTrue("Expected IllegalStateException to be thrown", thrown);
}
};
}
@Test
public void compacting() throws Exception {
new JavaTestKit(system) {
{
final Source<ByteString, BoxedUnit> rawBytes = Source.from(Arrays.asList(
ByteString.fromArray(new byte[] { 1, 2 }),
ByteString.fromArray(new byte[] { 3 }),
ByteString.fromArray(new byte[] { 4, 5, 6 }),
ByteString.fromArray(new byte[] { 7, 8, 9 })));
//#compacting-bytestrings
Source<ByteString, BoxedUnit> compacted = rawBytes.map(bs -> bs.compact());
//#compacting-bytestrings
FiniteDuration timeout = FiniteDuration.create(3, TimeUnit.SECONDS);
List<ByteString> got = Await.result(compacted.grouped(10).runWith(Sink.head(), mat), timeout);
for (ByteString byteString : got) {
assertTrue(byteString.isCompact());
}
}
};
}
}

View file

@ -0,0 +1,105 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.stage.Context;
import akka.stream.stage.PushPullStage;
import akka.stream.stage.SyncDirective;
import akka.stream.stage.TerminationDirective;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class RecipeDigest extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeDigest");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void work() throws Exception {
new JavaTestKit(system) {
//#calculating-digest
public PushPullStage<ByteString, ByteString> digestCalculator(String algorithm)
throws NoSuchAlgorithmException {
return new PushPullStage<ByteString, ByteString>() {
final MessageDigest digest = MessageDigest.getInstance(algorithm);
@Override
public SyncDirective onPush(ByteString chunk, Context<ByteString> ctx) {
digest.update(chunk.toArray());
return ctx.pull();
}
@Override
public SyncDirective onPull(Context<ByteString> ctx) {
if (ctx.isFinishing()) {
return ctx.pushAndFinish(ByteString.fromArray(digest.digest()));
} else {
return ctx.pull();
}
}
@Override
public TerminationDirective onUpstreamFinish(Context<ByteString> ctx) {
// If the stream is finished, we need to emit the last element in the onPull block.
// It is not allowed to directly emit elements from a termination block
// (onUpstreamFinish or onUpstreamFailure)
return ctx.absorbTermination();
}
};
}
//#calculating-digest
{
Source<ByteString, BoxedUnit> data = Source.from(Arrays.asList(
ByteString.fromString("abcdbcdecdef"),
ByteString.fromString("defgefghfghighijhijkijkljklmklmnlmnomnopnopq")));
//#calculating-digest2
final Source<ByteString, BoxedUnit> digest = data
.transform(() -> digestCalculator("SHA-256"));
//#calculating-digest2
ByteString got = Await.result(digest.runWith(Sink.head(), mat), Duration.create(3, TimeUnit.SECONDS));
assertEquals(ByteString.fromInts(
0x24, 0x8d, 0x6a, 0x61,
0xd2, 0x06, 0x38, 0xb8,
0xe5, 0xc0, 0x26, 0x93,
0x0c, 0x3e, 0x60, 0x39,
0xa3, 0x3c, 0xe4, 0x59,
0x64, 0xff, 0x21, 0x67,
0xf6, 0xec, 0xed, 0xd4,
0x19, 0xdb, 0x06, 0xc1), got);
}
};
}
}

View file

@ -0,0 +1,75 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Future;
import scala.runtime.BoxedUnit;
import java.util.ArrayList;
import java.util.List;
public class RecipeDroppyBroadcast extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeLoggingElements");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void work() throws Exception {
new JavaTestKit(system) {
//#droppy-bcast
// Makes a sink drop elements if too slow
public <T> Sink<T, Future<BoxedUnit>> droppySink(Sink<T, Future<BoxedUnit>> sink, int size) {
return Flow.<T> create()
.buffer(size, OverflowStrategy.dropHead())
.toMat(sink, Keep.right());
}
//#droppy-bcast
{
final List<Integer> nums = new ArrayList<>();
for (int i = 0; i < 100; i++) {
nums.add(i + 1);
}
final Sink<Integer, Future<BoxedUnit>> mySink1 = Sink.ignore();
final Sink<Integer, Future<BoxedUnit>> mySink2 = Sink.ignore();
final Sink<Integer, Future<BoxedUnit>> mySink3 = Sink.ignore();
final Source<Integer, BoxedUnit> myData = Source.from(nums);
//#droppy-bcast2
RunnableGraph.fromGraph(GraphDSL.create(builder -> {
final int outputCount = 3;
final UniformFanOutShape<Integer, Integer> bcast =
builder.add(Broadcast.create(outputCount));
builder.from(builder.add(myData)).toFanOut(bcast);
builder.from(bcast).to(builder.add(droppySink(mySink1, 10)));
builder.from(bcast).to(builder.add(droppySink(mySink2, 10)));
builder.from(bcast).to(builder.add(droppySink(mySink3, 10)));
return ClosedShape.getInstance();
}));
//#droppy-bcast2
}
};
}
}

View file

@ -0,0 +1,62 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class RecipeFlattenList extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeFlattenList");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void workWithMapConcat() throws Exception {
new JavaTestKit(system) {
{
Source<List<Message>, BoxedUnit> someDataSource = Source
.from(Arrays.asList(Arrays.asList(new Message("1")), Arrays.asList(new Message("2"), new Message("3"))));
//#flattening-lists
Source<List<Message>, BoxedUnit> myData = someDataSource;
Source<Message, BoxedUnit> flattened = myData.mapConcat(i -> i);
//#flattening-lists
List<Message> got = Await.result(flattened.grouped(10).runWith(Sink.head(), mat),
new FiniteDuration(1, TimeUnit.SECONDS));
assertEquals(got.get(0), new Message("1"));
assertEquals(got.get(1), new Message("2"));
assertEquals(got.get(2), new Message("3"));
}
};
}
}

View file

@ -0,0 +1,236 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.*;
import akka.dispatch.Mapper;
import akka.japi.pf.ReceiveBuilder;
import akka.pattern.Patterns;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.testkit.JavaTestKit;
import akka.util.Timeout;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.PartialFunction;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static junit.framework.TestCase.assertTrue;
public class RecipeGlobalRateLimit extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeGlobalRateLimit");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
static
//#global-limiter-actor
public class Limiter extends AbstractActor {
public static class WantToPass {}
public static final WantToPass WANT_TO_PASS = new WantToPass();
public static class MayPass {}
public static final MayPass MAY_PASS = new MayPass();
public static class ReplenishTokens {}
public static final ReplenishTokens REPLENISH_TOKENS = new ReplenishTokens();
private final int maxAvailableTokens;
private final FiniteDuration tokenRefreshPeriod;
private final int tokenRefreshAmount;
private final List<ActorRef> waitQueue = new ArrayList<>();
private final Cancellable replenishTimer;
private int permitTokens;
public static Props props(int maxAvailableTokens, FiniteDuration tokenRefreshPeriod,
int tokenRefreshAmount) {
return Props.create(Limiter.class, maxAvailableTokens, tokenRefreshPeriod,
tokenRefreshAmount);
}
private Limiter(int maxAvailableTokens, FiniteDuration tokenRefreshPeriod,
int tokenRefreshAmount) {
this.maxAvailableTokens = maxAvailableTokens;
this.tokenRefreshPeriod = tokenRefreshPeriod;
this.tokenRefreshAmount = tokenRefreshAmount;
this.permitTokens = maxAvailableTokens;
this.replenishTimer = system.scheduler().schedule(
this.tokenRefreshPeriod,
this.tokenRefreshPeriod,
self(),
REPLENISH_TOKENS,
context().system().dispatcher(),
self());
receive(open());
}
PartialFunction<Object, BoxedUnit> open() {
return ReceiveBuilder
.match(ReplenishTokens.class, rt -> {
permitTokens = Math.min(permitTokens + tokenRefreshAmount, maxAvailableTokens);
})
.match(WantToPass.class, wtp -> {
permitTokens -= 1;
sender().tell(MAY_PASS, self());
if (permitTokens == 0) {
context().become(closed());
}
}).build();
}
PartialFunction<Object, BoxedUnit> closed() {
return ReceiveBuilder
.match(ReplenishTokens.class, rt -> {
permitTokens = Math.min(permitTokens + tokenRefreshAmount, maxAvailableTokens);
releaseWaiting();
})
.match(WantToPass.class, wtp -> {
waitQueue.add(sender());
})
.build();
}
private void releaseWaiting() {
final List<ActorRef> toBeReleased = new ArrayList<>(permitTokens);
for (int i = 0; i < permitTokens && i < waitQueue.size(); i++) {
toBeReleased.add(waitQueue.remove(i));
}
permitTokens -= toBeReleased.size();
toBeReleased.stream().forEach(ref -> ref.tell(MAY_PASS, self()));
if (permitTokens > 0) {
context().become(open());
}
}
@Override
public void postStop() {
replenishTimer.cancel();
waitQueue.stream().forEach(ref -> {
ref.tell(new Status.Failure(new IllegalStateException("limiter stopped")), self());
});
}
}
//#global-limiter-actor
@Test
public void work() throws Exception {
new JavaTestKit(system) {
//#global-limiter-flow
public <T> Flow<T, T, BoxedUnit> limitGlobal(ActorRef limiter, FiniteDuration maxAllowedWait) {
final int parallelism = 4;
final Flow<T, T, BoxedUnit> f = Flow.create();
return f.mapAsync(parallelism, element -> {
final Timeout triggerTimeout = new Timeout(maxAllowedWait);
final Future<Object> limiterTriggerFuture =
Patterns.ask(limiter, Limiter.WANT_TO_PASS, triggerTimeout);
return limiterTriggerFuture.map(new Mapper<Object, T>() {
@Override
public T apply(Object parameter) {
return element;
}
}, system.dispatcher());
});
}
//#global-limiter-flow
{
// Use a large period and emulate the timer by hand instead
ActorRef limiter = system.actorOf(Limiter.props(2, new FiniteDuration(100, TimeUnit.DAYS), 1), "limiter");
final Iterator<String> e1 = new Iterator<String>() {
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
return "E1";
}
};
final Iterator<String> e2 = new Iterator<String>() {
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
return "E2";
}
};
final FiniteDuration twoSeconds = Duration.create(2, TimeUnit.SECONDS);
final Sink<String, TestSubscriber.Probe<String>> sink = TestSink.probe(system);
final TestSubscriber.Probe<String> probe =
RunnableGraph.<TestSubscriber.Probe<String>>fromGraph(
GraphDSL.create(sink, (builder, s) -> {
final int inputPorts = 2;
final UniformFanInShape<String, String> merge = builder.add(Merge.create(inputPorts));
final SourceShape<String> source1 =
builder.add(Source.<String>fromIterator(() -> e1).via(limitGlobal(limiter, twoSeconds)));
final SourceShape<String> source2 =
builder.add(Source.<String>fromIterator(() -> e2).via(limitGlobal(limiter, twoSeconds)));
builder.from(source1).toFanIn(merge);
builder.from(source2).toFanIn(merge);
builder.from(merge).to(s);
return ClosedShape.getInstance();
})
).run(mat);
probe.expectSubscription().request(1000);
FiniteDuration fiveHundredMillis = FiniteDuration.create(500, TimeUnit.MILLISECONDS);
assertTrue(probe.expectNext().startsWith("E"));
assertTrue(probe.expectNext().startsWith("E"));
probe.expectNoMsg(fiveHundredMillis);
limiter.tell(Limiter.REPLENISH_TOKENS, getTestActor());
assertTrue(probe.expectNext().startsWith("E"));
probe.expectNoMsg(fiveHundredMillis);
final Set<String> resultSet = new HashSet<>();
for (int i = 0; i < 100; i++) {
limiter.tell(Limiter.REPLENISH_TOKENS, getTestActor());
resultSet.add(probe.expectNext());
}
assertTrue(resultSet.contains("E1"));
assertTrue(resultSet.contains("E2"));
probe.expectError();
}
};
}
}

View file

@ -0,0 +1,154 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Keep;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.stage.DetachedContext;
import akka.stream.stage.DetachedStage;
import akka.stream.stage.DownstreamDirective;
import akka.stream.stage.UpstreamDirective;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.stream.testkit.javadsl.TestSource;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.duration.FiniteDuration;
import java.util.concurrent.TimeUnit;
public class RecipeHold extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeMultiGroupBy");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#hold-version-1
class HoldWithInitial<T> extends DetachedStage<T, T> {
private T currentValue;
public HoldWithInitial(T initial) {
currentValue = initial;
}
@Override
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
currentValue = elem;
return ctx.pull();
}
@Override
public DownstreamDirective onPull(DetachedContext<T> ctx) {
return ctx.push(currentValue);
}
}
//#hold-version-1
//#hold-version-2
class HoldWithWait<T> extends DetachedStage<T, T> {
private T currentValue = null;
private boolean waitingFirstValue = true;
@Override
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
currentValue = elem;
waitingFirstValue = false;
if (ctx.isHoldingDownstream()) {
return ctx.pushAndPull(currentValue);
} else {
return ctx.pull();
}
}
@Override
public DownstreamDirective onPull(DetachedContext<T> ctx) {
if (waitingFirstValue) {
return ctx.holdDownstream();
} else {
return ctx.push(currentValue);
}
}
}
//#hold-version-2
@Test
public void workForVersion1() throws Exception {
new JavaTestKit(system) {
{
final Source<Integer, TestPublisher.Probe<Integer>> source = TestSource.probe(system);
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubSub =
source.transform(() -> new HoldWithInitial<>(0)).toMat(sink, Keep.both()).run(mat);
TestPublisher.Probe<Integer> pub = pubSub.first();
TestSubscriber.Probe<Integer> sub = pubSub.second();
sub.requestNext(0);
sub.requestNext(0);
pub.sendNext(1);
pub.sendNext(2);
sub.request(2);
sub.expectNext(2, 2);
pub.sendComplete();
sub.request(1);
sub.expectComplete();
}
};
}
@Test
public void workForVersion2() throws Exception {
new JavaTestKit(system) {
{
final Source<Integer, TestPublisher.Probe<Integer>> source = TestSource.probe(system);
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubSub =
source.transform(() -> new HoldWithWait<>()).toMat(sink, Keep.both()).run(mat);
TestPublisher.Probe<Integer> pub = pubSub.first();
TestSubscriber.Probe<Integer> sub = pubSub.second();
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
sub.request(1);
sub.expectNoMsg(timeout);
pub.sendNext(1);
sub.expectNext(1);
pub.sendNext(2);
pub.sendNext(3);
sub.request(2);
sub.expectNext(3, 3);
pub.sendComplete();
sub.request(1);
sub.expectComplete();
}
};
}
}

View file

@ -0,0 +1,58 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.runtime.BoxedUnit;
import java.util.concurrent.TimeUnit;
public class RecipeKeepAlive extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeKeepAlive");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
class Tick {}
public final Tick TICK = new Tick();
@Test
public void workForVersion1() throws Exception {
new JavaTestKit(system) {
{
final ByteString keepAliveMessage = ByteString.fromArray(new byte[]{11});
//@formatter:off
//#inject-keepalive
Flow<ByteString, ByteString, BoxedUnit> keepAliveInject =
Flow.of(ByteString.class).keepAlive(
scala.concurrent.duration.Duration.create(1, TimeUnit.SECONDS),
() -> keepAliveMessage);
//#inject-keepalive
//@formatter:on
// Enough to compile, tested elsewhere as a built-in stage
}
};
}
}

View file

@ -0,0 +1,95 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.stream.ActorMaterializer;
import akka.stream.Attributes;
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.DebugFilter;
import akka.testkit.JavaTestKit;
import com.typesafe.config.ConfigFactory;
import docs.stream.SilenceSystemOut;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.runtime.AbstractFunction0;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
public class RecipeLoggingElements extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeLoggingElements", ConfigFactory.parseString("akka.loglevel=DEBUG\nakka.loggers = [akka.testkit.TestEventListener]"));
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void workWithPrintln() throws Exception {
new JavaTestKit(system) {
final SilenceSystemOut.System System = SilenceSystemOut.get(getTestActor());
{
final Source<String, BoxedUnit> mySource = Source.from(Arrays.asList("1", "2", "3"));
//#println-debug
mySource.map(elem -> {
System.out.println(elem);
return elem;
});
//#println-debug
}
};
}
@Test
public void workWithLog() throws Exception {
new JavaTestKit(system) {
private <T> T analyse(T i) {
return i;
}
{
final Source<String, BoxedUnit> mySource = Source.from(Arrays.asList("1", "2", "3"));
final int onElement = Logging.WarningLevel();
final int onFinish = Logging.ErrorLevel();
final int onFailure = Logging.ErrorLevel();
//#log-custom
// customise log levels
mySource.log("before-map")
.withAttributes(Attributes.createLogLevels(onElement, onFinish, onFailure))
.map(i -> analyse(i));
// or provide custom logging adapter
final LoggingAdapter adapter = Logging.getLogger(system, "customLogger");
mySource.log("custom", adapter);
//#log-custom
new DebugFilter("customLogger", "[custom] Element: ", false, false, 3).intercept(new AbstractFunction0 () {
public Void apply() {
mySource.log("custom", adapter).runWith(Sink.ignore(), mat);
return null;
}
}, system);
}
};
}
}

View file

@ -0,0 +1,153 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.stream.testkit.javadsl.TestSource;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.duration.FiniteDuration;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class RecipeManualTrigger extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeKeepAlive");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
class Trigger {
}
public final Trigger TRIGGER = new Trigger();
@Test
public void zipped() throws Exception {
new JavaTestKit(system) {
{
final Source<Trigger, TestPublisher.Probe<Trigger>> triggerSource = TestSource.probe(system);
final Sink<Message, TestSubscriber.Probe<Message>> messageSink = TestSink.probe(system);
//#manually-triggered-stream
final RunnableGraph<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>> g =
RunnableGraph.<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>>fromGraph(
GraphDSL.create(
triggerSource,
messageSink,
(p, s) -> new Pair<>(p, s),
(builder, source, sink) -> {
SourceShape<Message> elements =
builder.add(Source.from(Arrays.asList("1", "2", "3", "4")).map(t -> new Message(t)));
FlowShape<Pair<Message, Trigger>, Message> takeMessage =
builder.add(Flow.<Pair<Message, Trigger>>create().map(p -> p.first()));
final FanInShape2<Message, Trigger, Pair<Message, Trigger>> zip =
builder.add(Zip.create());
builder.from(elements).toInlet(zip.in0());
builder.from(source).toInlet(zip.in1());
builder.from(zip.out()).via(takeMessage).to(sink);
return ClosedShape.getInstance();
}
)
);
//#manually-triggered-stream
Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>> pubSub = g.run(mat);
TestPublisher.Probe<Trigger> pub = pubSub.first();
TestSubscriber.Probe<Message> sub = pubSub.second();
FiniteDuration timeout = FiniteDuration.create(100, TimeUnit.MILLISECONDS);
sub.expectSubscription().request(1000);
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("1"));
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("2"));
sub.expectNext(new Message("3"));
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("4"));
sub.expectComplete();
}
};
}
@Test
public void zipWith() throws Exception {
new JavaTestKit(system) {
{
final Source<Trigger, TestPublisher.Probe<Trigger>> triggerSource = TestSource.probe(system);
final Sink<Message, TestSubscriber.Probe<Message>> messageSink = TestSink.probe(system);
//#manually-triggered-stream-zipwith
final RunnableGraph<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>> g =
RunnableGraph.<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>>fromGraph(
GraphDSL.create(
triggerSource,
messageSink,
(p, s) -> new Pair<>(p, s),
(builder, source, sink) -> {
final SourceShape<Message> elements =
builder.add(Source.from(Arrays.asList("1", "2", "3", "4")).map(t -> new Message(t)));
final FanInShape2<Message, Trigger, Message> zipWith =
builder.add(ZipWith.create((msg, trigger) -> msg));
builder.from(elements).toInlet(zipWith.in0());
builder.from(source).toInlet(zipWith.in1());
builder.from(zipWith.out()).to(sink);
return ClosedShape.getInstance();
}
)
);
//#manually-triggered-stream-zipwith
Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>> pubSub = g.run(mat);
TestPublisher.Probe<Trigger> pub = pubSub.first();
TestSubscriber.Probe<Message> sub = pubSub.second();
FiniteDuration timeout = FiniteDuration.create(100, TimeUnit.MILLISECONDS);
sub.expectSubscription().request(1000);
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("1"));
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("2"));
sub.expectNext(new Message("3"));
sub.expectNoMsg(timeout);
pub.sendNext(TRIGGER);
sub.expectNext(new Message("4"));
sub.expectComplete();
}
};
}
}

View file

@ -0,0 +1,97 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Keep;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.stream.testkit.javadsl.TestSource;
import akka.testkit.JavaTestKit;
import akka.testkit.TestLatch;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.concurrent.TimeUnit;
public class RecipeMissedTicks extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeMultiGroupBy");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void work() throws Exception {
new JavaTestKit(system) {
class Tick {
}
final Tick Tick = new Tick();
{
final Source<Tick, TestPublisher.Probe<Tick>> tickStream = TestSource.probe(system);
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
@SuppressWarnings("unused")
//#missed-ticks
final Flow<Tick, Integer, BoxedUnit> missedTicks =
Flow.of(Tick.class).conflate(tick -> 0, (missed, tick) -> missed + 1);
//#missed-ticks
final TestLatch latch = new TestLatch(3, system);
final Flow<Tick, Integer, BoxedUnit> realMissedTicks =
Flow.of(Tick.class).conflate(tick -> 0, (missed, tick) -> { latch.countDown(); return missed + 1; });
Pair<TestPublisher.Probe<Tick>, TestSubscriber.Probe<Integer>> pubSub =
tickStream.via(realMissedTicks).toMat(sink, Keep.both()).run(mat);
TestPublisher.Probe<Tick> pub = pubSub.first();
TestSubscriber.Probe<Integer> sub = pubSub.second();
pub.sendNext(Tick);
pub.sendNext(Tick);
pub.sendNext(Tick);
pub.sendNext(Tick);
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
sub.request(1);
sub.expectNext(3);
sub.request(1);
sub.expectNoMsg(timeout);
pub.sendNext(Tick);
sub.expectNext(0);
pub.sendComplete();
sub.request(1);
sub.expectComplete();
}
};
}
}

View file

@ -0,0 +1,154 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Function;
import akka.japi.Pair;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.javadsl.SubSource;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.util.stream.Collectors.toList;
import static junit.framework.TestCase.assertTrue;
public class RecipeMultiGroupByTest extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeMultiGroupBy");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
static class Topic {
private final String name;
public Topic(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Topic topic = (Topic) o;
if (name != null ? !name.equals(topic.name) : topic.name != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}
@Test
public void work() throws Exception {
new JavaTestKit(system) {
final List<Topic> extractTopics(Message m) {
final List<Topic> topics = new ArrayList<>(2);
if (m.msg.startsWith("1")) {
topics.add(new Topic("1"));
} else {
topics.add(new Topic("1"));
topics.add(new Topic("2"));
}
return topics;
}
{
final Source<Message, BoxedUnit> elems = Source
.from(Arrays.asList("1: a", "1: b", "all: c", "all: d", "1: e"))
.map(s -> new Message(s));
//#multi-groupby
final Function<Message, List<Topic>> topicMapper = m -> extractTopics(m);
final Source<Pair<Message, Topic>, BoxedUnit> messageAndTopic = elems
.mapConcat((Message msg) -> {
List<Topic> topicsForMessage = topicMapper.apply(msg);
// Create a (Msg, Topic) pair for each of the topics
// the message belongs to
return topicsForMessage
.stream()
.map(topic -> new Pair<Message, Topic>(msg, topic))
.collect(toList());
});
SubSource<Pair<Message, Topic>, BoxedUnit> multiGroups = messageAndTopic
.groupBy(2, pair -> pair.second())
.map(pair -> {
Message message = pair.first();
Topic topic = pair.second();
// do what needs to be done
//#multi-groupby
return pair;
//#multi-groupby
});
//#multi-groupby
Future<List<String>> result = multiGroups
.grouped(10)
.mergeSubstreams()
.map(pair -> {
Topic topic = pair.get(0).second();
return topic.name + mkString(pair.stream().map(p -> p.first().msg).collect(toList()), "[", ", ", "]");
})
.grouped(10)
.runWith(Sink.head(), mat);
List<String> got = Await.result(result, FiniteDuration.create(3, TimeUnit.SECONDS));
assertTrue(got.contains("1[1: a, 1: b, all: c, all: d, 1: e]"));
assertTrue(got.contains("2[all: c, all: d]"));
}
};
}
public static final String mkString(List<String> l, String start, String separate, String end) {
StringBuilder sb = new StringBuilder(start);
for (String s : l) {
sb.append(s).append(separate);
}
return sb
.delete(sb.length() - separate.length(), sb.length())
.append(end).toString();
}
}

View file

@ -0,0 +1,59 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.io.Framing;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import akka.util.ByteString;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class RecipeParseLines extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeLoggingElements");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void parseLines() throws Exception {
final Source<ByteString, BoxedUnit> rawData = Source.from(Arrays.asList(
ByteString.fromString("Hello World"),
ByteString.fromString("\r"),
ByteString.fromString("!\r"),
ByteString.fromString("\nHello Akka!\r\nHello Streams!"),
ByteString.fromString("\r\n\r\n")));
//#parse-lines
final Source<String, BoxedUnit> lines = rawData
.via(Framing.delimiter(ByteString.fromString("\r\n"), 100, true))
.map(b -> b.utf8String());
//#parse-lines
Await.result(lines.grouped(10).runWith(Sink.head(), mat), new FiniteDuration(1, TimeUnit.SECONDS));
}
}

View file

@ -0,0 +1,124 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.japi.function.Function;
import akka.japi.function.Function2;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RecipeReduceByKeyTest extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeLoggingElements");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void work() throws Exception {
new JavaTestKit(system) {
{
final Source<String, BoxedUnit> words = Source.from(Arrays.asList("hello", "world", "and", "hello", "akka"));
//#word-count
final int MAXIMUM_DISTINCT_WORDS = 1000;
final Source<Pair<String, Integer>, BoxedUnit> counts = words
// split the words into separate streams first
.groupBy(MAXIMUM_DISTINCT_WORDS, i -> i)
// add counting logic to the streams
.fold(new Pair<>("", 0), (pair, elem) -> new Pair<>(elem, pair.second() + 1))
// get a stream of word counts
.mergeSubstreams();
//#word-count
final Future<List<Pair<String, Integer>>> f = counts.grouped(10).runWith(Sink.head(), mat);
final Set<Pair<String, Integer>> result = Await.result(f, getRemainingTime()).stream().collect(Collectors.toSet());
final Set<Pair<String, Integer>> expected = new HashSet<>();
expected.add(new Pair<>("hello", 2));
expected.add(new Pair<>("world", 1));
expected.add(new Pair<>("and", 1));
expected.add(new Pair<>("akka", 1));
Assert.assertEquals(expected, result);
}
};
}
//#reduce-by-key-general
static public <In, K, Out> Flow<In, Pair<K, Out>, BoxedUnit> reduceByKey(
int maximumGroupSize,
Function<In, K> groupKey,
Function<K, Out> foldZero,
Function2<Out, In, Out> fold,
Materializer mat) {
return Flow.<In> create()
.groupBy(maximumGroupSize, i -> i)
.fold((Pair<K, Out>) null, (pair, elem) -> {
final K key = groupKey.apply(elem);
if (pair == null) return new Pair<>(key, fold.apply(foldZero.apply(key), elem));
else return new Pair<>(key, fold.apply(pair.second(), elem));
})
.mergeSubstreams();
}
//#reduce-by-key-general
@Test
public void workGeneralised() throws Exception {
new JavaTestKit(system) {
{
final Source<String, BoxedUnit> words = Source.from(Arrays.asList("hello", "world", "and", "hello", "akka"));
//#reduce-by-key-general2
final int MAXIMUM_DISTINCT_WORDS = 1000;
Source<Pair<String, Integer>, BoxedUnit> counts = words.via(reduceByKey(
MAXIMUM_DISTINCT_WORDS,
word -> word,
key -> 0,
(count, elem) -> count + 1,
mat));
//#reduce-by-key-general2
final Future<List<Pair<String, Integer>>> f = counts.grouped(10).runWith(Sink.head(), mat);
final Set<Pair<String, Integer>> result = Await.result(f, getRemainingTime()).stream().collect(Collectors.toSet());
final Set<Pair<String, Integer>> expected = new HashSet<>();
expected.add(new Pair<>("hello", 2));
expected.add(new Pair<>("world", 1));
expected.add(new Pair<>("and", 1));
expected.add(new Pair<>("akka", 1));
Assert.assertEquals(expected, result);
}
};
}
}

View file

@ -0,0 +1,78 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.stream.testkit.TestPublisher;
import akka.stream.testkit.TestSubscriber;
import akka.stream.testkit.javadsl.TestSink;
import akka.stream.testkit.javadsl.TestSource;
import akka.testkit.JavaTestKit;
import akka.testkit.TestLatch;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import scala.runtime.BoxedUnit;
import java.util.concurrent.TimeUnit;
public class RecipeSimpleDrop extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeSimpleDrop");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void work() throws Exception {
new JavaTestKit(system) {
{
@SuppressWarnings("unused")
//#simple-drop
final Flow<Message, Message, BoxedUnit> droppyStream =
Flow.of(Message.class).conflate(i -> i, (lastMessage, newMessage) -> newMessage);
//#simple-drop
final TestLatch latch = new TestLatch(2, system);
final Flow<Message, Message, BoxedUnit> realDroppyStream =
Flow.of(Message.class).conflate(i -> i, (lastMessage, newMessage) -> { latch.countDown(); return newMessage; });
final Pair<TestPublisher.Probe<Message>, TestSubscriber.Probe<Message>> pubSub = TestSource
.<Message> probe(system)
.via(realDroppyStream)
.toMat(TestSink.probe(system),
(pub, sub) -> new Pair<>(pub, sub))
.run(mat);
final TestPublisher.Probe<Message> pub = pubSub.first();
final TestSubscriber.Probe<Message> sub = pubSub.second();
pub.sendNext(new Message("1"));
pub.sendNext(new Message("2"));
pub.sendNext(new Message("3"));
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
sub.requestNext(new Message("3"));
pub.sendComplete();
sub.request(1);
sub.expectComplete();
}
};
}
}

View file

@ -0,0 +1,40 @@
package docs.stream.javadsl.cookbook;
public class RecipeTest {
final class Message {
public final String msg;
public Message(String msg) {
this.msg = msg;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Message message = (Message) o;
if (msg != null ? !msg.equals(message.msg) : message.msg != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return msg != null ? msg.hashCode() : 0;
}
}
final class Trigger {
}
final class Job {
}
}

View file

@ -0,0 +1,57 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class RecipeToStrict extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeLoggingElements");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
@Test
public void workWithPrintln() throws Exception {
new JavaTestKit(system) {
{
final Source<String, BoxedUnit> myData = Source.from(Arrays.asList("1", "2", "3"));
final int MAX_ALLOWED_SIZE = 100;
//#draining-to-list
final Future<List<String>> strings = myData
.grouped(MAX_ALLOWED_SIZE).runWith(Sink.head(), mat);
//#draining-to-list
Await.result(strings, new FiniteDuration(1, TimeUnit.SECONDS));
}
};
}
}

View file

@ -0,0 +1,87 @@
/**
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
*/
package docs.stream.javadsl.cookbook;
import akka.actor.ActorSystem;
import akka.stream.*;
import akka.stream.javadsl.*;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.BoxedUnit;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertTrue;
public class RecipeWorkerPool extends RecipeTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("RecipeWorkerPool");
}
@AfterClass
public static void tearDown() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
final Materializer mat = ActorMaterializer.create(system);
//#worker-pool
public static <In, Out> Flow<In, Out, BoxedUnit> balancer(
Flow<In, Out, BoxedUnit> worker, int workerCount) {
return Flow.fromGraph(GraphDSL.create(b -> {
boolean waitForAllDownstreams = true;
final UniformFanOutShape<In, In> balance =
b.add(Balance.<In>create(workerCount, waitForAllDownstreams));
final UniformFanInShape<Out, Out> merge =
b.add(Merge.<Out>create(workerCount));
for (int i = 0; i < workerCount; i++) {
b.from(balance.out(i)).via(b.add(worker)).toInlet(merge.in(i));
}
return FlowShape.of(balance.in(), merge.out());
}));
}
//#worker-pool
@Test
public void workForVersion1() throws Exception {
new JavaTestKit(system) {
{
Source<Message, BoxedUnit> data =
Source
.from(Arrays.asList("1", "2", "3", "4", "5"))
.map(t -> new Message(t));
Flow<Message, Message, BoxedUnit> worker = Flow.of(Message.class).map(m -> new Message(m.msg + " done"));
//#worker-pool2
Flow<Message, Message, BoxedUnit> balancer = balancer(worker, 3);
Source<Message, BoxedUnit> processedJobs = data.via(balancer);
//#worker-pool2
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
Future<List<String>> future = processedJobs.map(m -> m.msg).grouped(10).runWith(Sink.head(), mat);
List<String> got = Await.result(future, timeout);
assertTrue(got.contains("1 done"));
assertTrue(got.contains("2 done"));
assertTrue(got.contains("3 done"));
assertTrue(got.contains("4 done"));
assertTrue(got.contains("5 done"));
}
};
}
}