@Test public void demonstrateBroadcast() { final Sink<Author, CompletionStage<Done>> writeAuthors = Sink.ignore(); final Sink<Hashtag, CompletionStage<Done>> 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 }
@Test public void reusingComponents() throws Exception { final Source<Integer, NotUsed> 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, NotUsed> 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, NotUsed> 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<NotUsed> runnableGraph = nestedSource.to(nestedSink); // Usage is uniform, no matter if modules are composite or atomic final RunnableGraph<NotUsed> runnableGraph2 = Source.single(0).to(Sink.fold(0, (acc, i) -> acc + i)); // #reuse }
@Test public void partialGraph() throws Exception { // #partial-graph final Graph<FlowShape<Integer, Integer>, NotUsed> 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, NotUsed> flow = Flow.fromGraph(partial); // Simple way to create a graph backed Source final Source<Integer, NotUsed> 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, NotUsed> sink = Flow.of(Integer.class).map(i -> i * 2).drop(10).named("nestedFlow").to(Sink.head()); // Putting all together final RunnableGraph<NotUsed> closed = source.via(flow.filter(i -> i > 1)).to(sink); // #partial-flow-dsl }
@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 materializedValues() throws Exception { // #mat-combine-1 // Materializes to Promise<BoxedUnit> (red) final Source<Integer, CompletableFuture<Optional<Integer>>> source = Source.<Integer>maybe(); // Materializes to BoxedUnit (black) final Flow<Integer, Integer, NotUsed> flow1 = Flow.of(Integer.class).take(100); // Materializes to Promise<Option<>> (red) final Source<Integer, CompletableFuture<Optional<Integer>>> nestedSource = source.viaMat(flow1, Keep.left()).named("nestedSource"); // #mat-combine-1 // #mat-combine-2 // Materializes to BoxedUnit (orange) final Flow<Integer, ByteString, NotUsed> flow2 = Flow.of(Integer.class).map(i -> ByteString.fromString(i.toString())); // Materializes to Future<OutgoingConnection> (yellow) final Flow<ByteString, ByteString, CompletionStage<OutgoingConnection>> flow3 = Tcp.get(system).outgoingConnection("localhost", 8080); // Materializes to Future<OutgoingConnection> (yellow) final Flow<Integer, ByteString, CompletionStage<OutgoingConnection>> nestedFlow = flow2.viaMat(flow3, Keep.right()).named("nestedFlow"); // #mat-combine-2 // #mat-combine-3 // Materializes to Future<String> (green) final Sink<ByteString, CompletionStage<String>> sink = Sink.<String, ByteString>fold("", (acc, i) -> acc + i.utf8String()); // Materializes to Pair<Future<OutgoingConnection>, Future<String>> (blue) final Sink<Integer, Pair<CompletionStage<OutgoingConnection>, CompletionStage<String>>> nestedSink = nestedFlow.toMat(sink, Keep.both()); // #mat-combine-3 // #mat-combine-4b // Materializes to Future<MyClass> (purple) final RunnableGraph<CompletionStage<MyClass>> runnableGraph = nestedSource.toMat(nestedSink, Combiner::f); // #mat-combine-4b }
@Test public void demonstrateASimplerOneToManyStage() throws Exception { // tests: Graph<FlowShape<Integer, Integer>, NotUsed> duplicator = Flow.fromGraph(new Duplicator2<Integer>()); CompletionStage<Integer> result = Source.from(Arrays.asList(1, 2, 3)).via(duplicator).runFold(0, (n, sum) -> n + sum, mat); assertEquals(new Integer(12), result.toCompletableFuture().get(3, TimeUnit.SECONDS)); }
@Test public void mustBeAbleToUseQueue() throws Exception { final Pair<SourceQueueWithComplete<String>, CompletionStage<List<String>>> x = Flow.of(String.class) .runWith(Source.queue(2, OverflowStrategy.fail()), Sink.seq(), materializer); final SourceQueueWithComplete<String> source = x.first(); final CompletionStage<List<String>> result = x.second(); source.offer("hello"); source.offer("world"); source.complete(); assertEquals( result.toCompletableFuture().get(3, TimeUnit.SECONDS), Arrays.asList("hello", "world")); }
@Test public void attributes() throws Exception { // #attributes-inheritance final Source<Integer, NotUsed> nestedSource = Source.single(0).map(i -> i + 1).named("nestedSource"); // Wrap, no inputBuffer set final Flow<Integer, Integer, NotUsed> 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, NotUsed> 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 }
@Test public void demonstrateAManyToOneElementGraphStage() throws Exception { // tests: Graph<FlowShape<Integer, Integer>, NotUsed> evenFilter = Flow.fromGraph(new Filter<Integer>(n -> n % 2 == 0)); CompletionStage<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), result.toCompletableFuture().get(3, TimeUnit.SECONDS)); }
@Test public void nestedFlow() throws Exception { // #nested-flow final Source<Integer, NotUsed> 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, NotUsed> 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, NotUsed> 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<NotUsed> runnableGraph = nestedSource.to(nestedSink); // #nested-flow }
// #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()); })); }
@Test public void demonstrateOneToOne() throws Exception { // tests: final Graph<FlowShape<String, Integer>, NotUsed> stringLength = Flow.fromGraph( new Map<String, Integer>( new Function<String, Integer>() { @Override public Integer apply(String str) { return str.length(); } })); CompletionStage<Integer> result = Source.from(Arrays.asList("one", "two", "three")) .via(stringLength) .runFold(0, (sum, n) -> sum + n, mat); assertEquals(new Integer(11), result.toCompletableFuture().get(3, TimeUnit.SECONDS)); }
@Test public void mustBeAbleToUseConflate() throws Exception { final JavaTestKit probe = new JavaTestKit(system); final List<String> input = Arrays.asList("A", "B", "C"); CompletionStage<String> future = Source.from(input) .conflateWithSeed(s -> s, (aggr, in) -> aggr + in) .runFold("", (aggr, in) -> aggr + in, materializer); String result = future.toCompletableFuture().get(3, TimeUnit.SECONDS); assertEquals("ABC", result); final Flow<String, String, NotUsed> flow2 = Flow.of(String.class).conflate((a, b) -> a + b); CompletionStage<String> future2 = Source.from(input) .conflate((String a, String b) -> a + b) .runFold("", (a, b) -> a + b, materializer); String result2 = future2.toCompletableFuture().get(3, TimeUnit.SECONDS); assertEquals("ABC", result2); }
@Test public void mustWorksForTwoStreams() throws Exception { final Flow<Integer, Integer, NotUsed> sharedThrottle = Flow.of(Integer.class) .throttle(1, FiniteDuration.create(1, TimeUnit.DAYS), 1, ThrottleMode.enforcing()); CompletionStage<List<Integer>> result1 = Source.single(1).via(sharedThrottle).via(sharedThrottle).runWith(Sink.seq(), materializer); // If there is accidental shared state then we would not be able to pass through the single // element assertEquals( result1.toCompletableFuture().get(3, TimeUnit.SECONDS), Collections.singletonList(1)); // It works with a new stream, too CompletionStage<List<Integer>> result2 = Source.single(1).via(sharedThrottle).via(sharedThrottle).runWith(Sink.seq(), materializer); assertEquals( result2.toCompletableFuture().get(3, TimeUnit.SECONDS), Collections.singletonList(1)); }
@Test public void demonstrateAnAsynchronousSideChannel() throws Exception { // tests: CompletableFuture<Done> switchF = new CompletableFuture<>(); Graph<FlowShape<Integer, Integer>, NotUsed> killSwitch = Flow.fromGraph(new KillSwitch<>(switchF)); ExecutionContext ec = system.dispatcher(); CompletionStage<Integer> valueAfterKill = switchF.thenApply(in -> 4); CompletionStage<Integer> result = Source.from(Arrays.asList(1, 2, 3)) .concat(Source.fromCompletionStage(valueAfterKill)) .via(killSwitch) .runFold(0, (n, sum) -> n + sum, mat); switchF.complete(Done.getInstance()); assertEquals(new Integer(6), result.toCompletableFuture().get(3, TimeUnit.SECONDS)); }
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( new Function<Source<Integer, BoxedUnit>, Source<Integer, BoxedUnit>>() { @Override public Source<Integer, BoxedUnit> apply(Source<Integer, BoxedUnit> param) throws Exception { return param; } }); // #flatMapConcat 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( new Creator<InputStream>() { public InputStream create() { return new SomeInputStream(); } }); final Source<ByteString, Future<java.lang.Long>> otherInputStreamSrc = StreamConverters.fromInputStream( new Creator<InputStream>() { public InputStream create() { return new SomeInputStream(); } }, 1024); final Sink<ByteString, Future<java.lang.Long>> outputStreamSink = StreamConverters.fromOutputStream( new Creator<OutputStream>() { public OutputStream create() { return 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 }