/** * Copyright (C) 2009-2012 Typesafe Inc. */ package docs.future; //#imports1 import akka.dispatch.*; import scala.concurrent.ExecutionContext; import scala.concurrent.Future; import scala.concurrent.Await; import akka.util.Timeout; //#imports1 //#imports2 import scala.concurrent.util.Duration; import akka.japi.Function; import java.util.concurrent.Callable; import static akka.dispatch.Futures.future; import static java.util.concurrent.TimeUnit.SECONDS; //#imports2 //#imports3 import static akka.dispatch.Futures.sequence; //#imports3 //#imports4 import static akka.dispatch.Futures.traverse; //#imports4 //#imports5 import akka.japi.Function2; import static akka.dispatch.Futures.fold; //#imports5 //#imports6 import static akka.dispatch.Futures.reduce; //#imports6 //#imports7 import scala.concurrent.ExecutionContext; import scala.concurrent.ExecutionContext$; //#imports7 //#imports8 import static akka.pattern.Patterns.after; //#imports8 import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.After; import org.junit.Before; import org.junit.Test; import akka.testkit.AkkaSpec; import akka.actor.Status.Failure; import akka.actor.ActorSystem; import akka.actor.UntypedActor; import akka.actor.ActorRef; import akka.actor.Props; import akka.pattern.Patterns; import static org.junit.Assert.*; public class FutureDocTestBase { ActorSystem system; @Before public void setUp() { system = ActorSystem.create("MySystem", AkkaSpec.testConf()); } @After public void tearDown() { system.shutdown(); } @SuppressWarnings("unchecked") @Test public void useCustomExecutionContext() throws Exception { ExecutorService yourExecutorServiceGoesHere = Executors.newSingleThreadExecutor(); //#diy-execution-context ExecutionContext ec = ExecutionContexts.fromExecutorService(yourExecutorServiceGoesHere); //Use ec with your Futures Future f1 = Futures.successful("foo"); // Then you shut down the ExecutorService at the end of your application. yourExecutorServiceGoesHere.shutdown(); //#diy-execution-context } @Test public void useBlockingFromActor() throws Exception { ActorRef actor = system.actorOf(new Props(MyActor.class)); String msg = "hello"; //#ask-blocking Timeout timeout = new Timeout(Duration.create(5, "seconds")); Future future = Patterns.ask(actor, msg, timeout); String result = (String) Await.result(future, timeout.duration()); //#ask-blocking assertEquals("HELLO", result); } @Test public void useFutureEval() throws Exception { //#future-eval Future f = future(new Callable() { public String call() { return "Hello" + "World"; } }, system.dispatcher()); String result = (String) Await.result(f, Duration.create(1, SECONDS)); //#future-eval assertEquals("HelloWorld", result); } @Test public void useMap() throws Exception { //#map final ExecutionContext ec = system.dispatcher(); Future f1 = future(new Callable() { public String call() { return "Hello" + "World"; } }, ec); Future f2 = f1.map(new Mapper() { public Integer apply(String s) { return s.length(); } }, ec); int result = Await.result(f2, Duration.create(1, SECONDS)); assertEquals(10, result); //#map } @Test public void useMap2() throws Exception { //#map2 final ExecutionContext ec = system.dispatcher(); Future f1 = future(new Callable() { public String call() throws Exception { Thread.sleep(100); return "Hello" + "World"; } }, ec); Future f2 = f1.map(new Mapper() { public Integer apply(String s) { return s.length(); } }, ec); //#map2 int result = Await.result(f2, Duration.create(1, SECONDS)); assertEquals(10, result); } @Test public void useMap3() throws Exception { //#map3 final ExecutionContext ec = system.dispatcher(); Future f1 = future(new Callable() { public String call() { return "Hello" + "World"; } }, ec); Thread.sleep(100); Future f2 = f1.map(new Mapper() { public Integer apply(String s) { return s.length(); } }, ec); //#map3 int result = Await.result(f2, Duration.create(1, SECONDS)); assertEquals(10, result); } @Test public void useFlatMap() throws Exception { //#flat-map final ExecutionContext ec = system.dispatcher(); Future f1 = future(new Callable() { public String call() { return "Hello" + "World"; } }, ec); Future f2 = f1.flatMap(new Mapper>() { public Future apply(final String s) { return future(new Callable() { public Integer call() { return s.length(); } }, ec); } }, ec); //#flat-map int result = Await.result(f2, Duration.create(1, SECONDS)); assertEquals(10, result); } @Test public void useSequence() throws Exception { List> source = new ArrayList>(); source.add(Futures.successful(1)); source.add(Futures.successful(2)); //#sequence final ExecutionContext ec = system.dispatcher(); //Some source generating a sequence of Future:s Iterable> listOfFutureInts = source; // now we have a Future[Iterable[Integer]] Future> futureListOfInts = sequence(listOfFutureInts, ec); // Find the sum of the odd numbers Future futureSum = futureListOfInts.map( new Mapper, Long>() { public Long apply(Iterable ints) { long sum = 0; for (Integer i : ints) sum += i; return sum; } }, ec); long result = Await.result(futureSum, Duration.create(1, SECONDS)); //#sequence assertEquals(3L, result); } @Test public void useTraverse() throws Exception { //#traverse final ExecutionContext ec = system.dispatcher(); //Just a sequence of Strings Iterable listStrings = Arrays.asList("a", "b", "c"); Future> futureResult = traverse(listStrings, new Function>() { public Future apply(final String r) { return future(new Callable() { public String call() { return r.toUpperCase(); } }, ec); } }, ec); //Returns the sequence of strings as upper case Iterable result = Await.result(futureResult, Duration.create(1, SECONDS)); assertEquals(Arrays.asList("A", "B", "C"), result); //#traverse } @Test public void useFold() throws Exception { List> source = new ArrayList>(); source.add(Futures.successful("a")); source.add(Futures.successful("b")); //#fold final ExecutionContext ec = system.dispatcher(); //A sequence of Futures, in this case Strings Iterable> futures = source; //Start value is the empty string Future resultFuture = fold("", futures, new Function2() { public String apply(String r, String t) { return r + t; //Just concatenate } }, ec); String result = Await.result(resultFuture, Duration.create(1, SECONDS)); //#fold assertEquals("ab", result); } @Test public void useReduce() throws Exception { List> source = new ArrayList>(); source.add(Futures.successful("a")); source.add(Futures.successful("b")); //#reduce final ExecutionContext ec = system.dispatcher(); //A sequence of Futures, in this case Strings Iterable> futures = source; Future resultFuture = reduce(futures, new Function2() { public Object apply(Object r, String t) { return r + t; //Just concatenate } }, ec); Object result = Await.result(resultFuture, Duration.create(1, SECONDS)); //#reduce assertEquals("ab", result); } @Test public void useSuccessfulAndFailed() throws Exception { final ExecutionContext ec = system.dispatcher(); //#successful Future future = Futures.successful("Yay!"); //#successful //#failed Future otherFuture = Futures.failed( new IllegalArgumentException("Bang!")); //#failed Object result = Await.result(future, Duration.create(1, SECONDS)); assertEquals("Yay!", result); Throwable result2 = Await.result(otherFuture.failed(), Duration.create(1, SECONDS)); assertEquals("Bang!", result2.getMessage()); } @Test public void useFilter() throws Exception { //#filter final ExecutionContext ec = system.dispatcher(); Future future1 = Futures.successful(4); Future successfulFilter = future1.filter(Filter.filterOf( new Function() { public Boolean apply(Integer i) { return i % 2 == 0; } }), ec); Future failedFilter = future1.filter(Filter.filterOf( new Function() { public Boolean apply(Integer i) { return i % 2 != 0; } }), ec); //When filter fails, the returned Future will be failed with a scala.MatchError //#filter } public void sendToTheInternetz(String s) { } public void sendToIssueTracker(Throwable t) { } @Test public void useAndThen() { //#and-then final ExecutionContext ec = system.dispatcher(); Future future1 = Futures.successful("value").andThen( new OnComplete() { public void onComplete(Throwable failure, String result) { if (failure != null) sendToIssueTracker(failure); } }, ec).andThen(new OnComplete() { public void onComplete(Throwable failure, String result) { if (result != null) sendToTheInternetz(result); } }, ec); //#and-then } @Test public void useRecover() throws Exception { //#recover final ExecutionContext ec = system.dispatcher(); Future future = future(new Callable() { public Integer call() { return 1 / 0; } }, ec).recover(new Recover() { public Integer recover(Throwable problem) throws Throwable { if (problem instanceof ArithmeticException) return 0; else throw problem; } }, ec); int result = Await.result(future, Duration.create(1, SECONDS)); assertEquals(result, 0); //#recover } @Test public void useTryRecover() throws Exception { //#try-recover final ExecutionContext ec = system.dispatcher(); Future future = future(new Callable() { public Integer call() { return 1 / 0; } }, ec).recoverWith(new Recover>() { public Future recover(Throwable problem) throws Throwable { if (problem instanceof ArithmeticException) { return future(new Callable() { public Integer call() { return 0; } }, ec); } else throw problem; } }, ec); int result = Await.result(future, Duration.create(1, SECONDS)); assertEquals(result, 0); //#try-recover } @Test public void useOnSuccessOnFailureAndOnComplete() throws Exception { { Future future = Futures.successful("foo"); //#onSuccess final ExecutionContext ec = system.dispatcher(); future.onSuccess(new OnSuccess() { public void onSuccess(String result) { if ("bar" == result) { //Do something if it resulted in "bar" } else { //Do something if it was some other String } } }, ec); //#onSuccess } { Future future = Futures.failed(new IllegalStateException("OHNOES")); //#onFailure final ExecutionContext ec = system.dispatcher(); future.onFailure(new OnFailure() { public void onFailure(Throwable failure) { if (failure instanceof IllegalStateException) { //Do something if it was this particular failure } else { //Do something if it was some other failure } } }, ec); //#onFailure } { Future future = Futures.successful("foo"); //#onComplete final ExecutionContext ec = system.dispatcher(); future.onComplete(new OnComplete() { public void onComplete(Throwable failure, String result) { if (failure != null) { //We got a failure, handle it here } else { // We got a result, do something with it } } }, ec); //#onComplete } } @Test public void useOrAndZip() throws Exception { { //#zip final ExecutionContext ec = system.dispatcher(); Future future1 = Futures.successful("foo"); Future future2 = Futures.successful("bar"); Future future3 = future1.zip(future2).map( new Mapper, String>() { public String apply(scala.Tuple2 zipped) { return zipped._1() + " " + zipped._2(); } }, ec); String result = Await.result(future3, Duration.create(1, SECONDS)); assertEquals("foo bar", result); //#zip } { //#fallback-to Future future1 = Futures.failed(new IllegalStateException("OHNOES1")); Future future2 = Futures.failed(new IllegalStateException("OHNOES2")); Future future3 = Futures.successful("bar"); // Will have "bar" in this case Future future4 = future1.fallbackTo(future2).fallbackTo(future3); String result = Await.result(future4, Duration.create(1, SECONDS)); assertEquals("bar", result); //#fallback-to } } @Test(expected = IllegalStateException.class) public void useAfter() throws Exception { //#after final ExecutionContext ec = system.dispatcher(); Future failExc = Futures.failed(new IllegalStateException("OHNOES1")); Future delayed = Patterns.after(Duration.create(500, "millis"), system.scheduler(), ec, failExc); Future future = future(new Callable() { public String call() throws InterruptedException { Thread.sleep(1000); return "foo"; } }, ec); Future result = future.either(delayed); //#after Await.result(result, Duration.create(2, SECONDS)); } public static class MyActor extends UntypedActor { public void onReceive(Object message) { if (message instanceof String) { getSender().tell(((String) message).toUpperCase(), getSelf()); } else if (message instanceof Integer) { int i = ((Integer) message).intValue(); if (i < 0) { getSender().tell(new Failure(new ArithmeticException("Negative values not supported")), getSelf()); } else { getSender().tell(i, getSelf()); } } else { unhandled(message); } } } }