/** * Copyright (C) 2009-2017 Lightbend Inc. */ package jdocs.actor; import akka.actor.*; import akka.testkit.javadsl.TestKit; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import jdocs.AbstractJavaTest; import static jdocs.actor.Messages.Swap.Swap; import static jdocs.actor.Messages.*; import akka.actor.CoordinatedShutdown; import akka.util.Timeout; import akka.Done; import java.util.Optional; import java.util.concurrent.TimeUnit; import akka.testkit.TestActors; import scala.concurrent.Await; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; //#import-props import akka.actor.Props; //#import-props //#import-actorRef import akka.actor.ActorRef; import akka.actor.ActorSystem; //#import-actorRef //#import-identify import akka.actor.ActorIdentity; import akka.actor.ActorSelection; import akka.actor.Identify; //#import-identify //#import-ask import static akka.pattern.PatternsCS.ask; import static akka.pattern.PatternsCS.pipe; import java.util.concurrent.CompletableFuture; //#import-ask //#import-gracefulStop import static akka.pattern.PatternsCS.gracefulStop; import akka.pattern.AskTimeoutException; import scala.concurrent.duration.Duration; import java.util.concurrent.CompletionStage; //#import-gracefulStop //#import-terminated import akka.actor.Terminated; //#import-terminated public class ActorDocTest extends AbstractJavaTest { public static Config config = ConfigFactory.parseString( "akka {\n" + " loggers = [\"akka.testkit.TestEventListener\"]\n" + " loglevel = \"WARNING\"\n" + " stdout-loglevel = \"WARNING\"\n" + "}\n" ); static ActorSystem system = null; @BeforeClass public static void beforeClass() { system = ActorSystem.create("ActorDocTest", config); } @AfterClass public static void afterClass() throws Exception { Await.ready(system.terminate(), Duration.create(5, TimeUnit.SECONDS)); } static //#context-actorOf public class FirstActor extends AbstractActor { final ActorRef child = getContext().actorOf(Props.create(MyActor.class), "myChild"); //#plus-some-behavior @Override public Receive createReceive() { return receiveBuilder() .matchAny(x -> getSender().tell(x, getSelf())) .build(); } //#plus-some-behavior } //#context-actorOf static public class SomeActor extends AbstractActor { //#createReceive @Override public Receive createReceive() { return receiveBuilder() .match(String.class, s -> System.out.println(s.toLowerCase())) .build(); } //#createReceive } static //#well-structured public class WellStructuredActor extends AbstractActor { public static class Msg1 {} public static class Msg2 {} public static class Msg3 {} @Override public Receive createReceive() { return receiveBuilder() .match(Msg1.class, this::receiveMsg1) .match(Msg2.class, this::receiveMsg2) .match(Msg3.class, this::receiveMsg3) .build(); } private void receiveMsg1(Msg1 msg) { // actual work } private void receiveMsg2(Msg2 msg) { // actual work } private void receiveMsg3(Msg3 msg) { // actual work } } //#well-structured static //#optimized public class OptimizedActor extends UntypedAbstractActor { public static class Msg1 {} public static class Msg2 {} public static class Msg3 {} @Override public void onReceive(Object msg) throws Exception { if (msg instanceof Msg1) receiveMsg1((Msg1) msg); else if (msg instanceof Msg2) receiveMsg2((Msg2) msg); else if (msg instanceof Msg3) receiveMsg3((Msg3) msg); else unhandled(msg); } private void receiveMsg1(Msg1 msg) { // actual work } private void receiveMsg2(Msg2 msg) { // actual work } private void receiveMsg3(Msg3 msg) { // actual work } } //#optimized static public class ActorWithArgs extends AbstractActor { private final String args; public ActorWithArgs(String args) { this.args = args; } @Override public Receive createReceive() { return receiveBuilder().matchAny(x -> { }).build(); } } static //#props-factory public class DemoActor extends AbstractActor { /** * Create Props for an actor of this type. * @param magicNumber The magic number to be passed to this actor’s constructor. * @return a Props for creating this actor, which can then be further configured * (e.g. calling `.withDispatcher()` on it) */ static Props props(Integer magicNumber) { // You need to specify the actual type of the returned actor // since Java 8 lambdas have some runtime type information erased return Props.create(DemoActor.class, () -> new DemoActor(magicNumber)); } private final Integer magicNumber; public DemoActor(Integer magicNumber) { this.magicNumber = magicNumber; } @Override public Receive createReceive() { return receiveBuilder() .match(Integer.class, i -> { getSender().tell(i + magicNumber, getSelf()); }) .build(); } } //#props-factory static //#props-factory public class SomeOtherActor extends AbstractActor { // Props(new DemoActor(42)) would not be safe ActorRef demoActor = getContext().actorOf(DemoActor.props(42), "demo"); // ... //#props-factory @Override public Receive createReceive() { return AbstractActor.emptyBehavior(); } //#props-factory } //#props-factory static //#messages-in-companion public class DemoMessagesActor extends AbstractLoggingActor { static public class Greeting { private final String from; public Greeting(String from) { this.from = from; } public String getGreeter() { return from; } } @Override public Receive createReceive() { return receiveBuilder() .match(Greeting.class, g -> { log().info("I was greeted by {}", g.getGreeter()); }) .build(); } } //#messages-in-companion public static class LifecycleMethods extends AbstractActor { @Override public Receive createReceive() { return AbstractActor.emptyBehavior(); } /* * This section must be kept in sync with the actual Actor trait. * * BOYSCOUT RULE: whenever you read this, verify that! */ //#lifecycle-callbacks public void preStart() { } public void preRestart(Throwable reason, Optional message) { for (ActorRef each : getContext().getChildren()) { getContext().unwatch(each); getContext().stop(each); } postStop(); } public void postRestart(Throwable reason) { preStart(); } public void postStop() { } //#lifecycle-callbacks } public static class Hook extends AbstractActor { ActorRef target = null; @Override public Receive createReceive() { return AbstractActor.emptyBehavior(); } //#preStart @Override public void preStart() { target = getContext().actorOf(Props.create(MyActor.class, "target")); } //#preStart //#postStop @Override public void postStop() { //#clean-up-some-resources final String message = "stopped"; //#tell // don’t forget to think about who is the sender (2nd argument) target.tell(message, getSelf()); //#tell final Object result = ""; //#forward target.forward(result, getContext()); //#forward target = null; //#clean-up-some-resources } //#postStop // compilation test only public void compileSelections() { //#selection-local // will look up this absolute path getContext().actorSelection("/user/serviceA/actor"); // will look up sibling beneath same supervisor getContext().actorSelection("../joe"); //#selection-local //#selection-wildcard // will look all children to serviceB with names starting with worker getContext().actorSelection("/user/serviceB/worker*"); // will look up all siblings beneath same supervisor getContext().actorSelection("../*"); //#selection-wildcard //#selection-remote getContext().actorSelection("akka.tcp://app@otherhost:1234/user/serviceB"); //#selection-remote } } public static class ReplyException extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .matchAny(x -> { //#reply-exception try { String result = operation(); getSender().tell(result, getSelf()); } catch (Exception e) { getSender().tell(new akka.actor.Status.Failure(e), getSelf()); throw e; } //#reply-exception }) .build(); } private String operation() { return "Hi"; } } static //#gracefulStop-actor public class Manager extends AbstractActor { private static enum Shutdown { Shutdown } public static final Shutdown SHUTDOWN = Shutdown.Shutdown; private ActorRef worker = getContext().watch(getContext().actorOf(Props.create(Cruncher.class), "worker")); @Override public Receive createReceive() { return receiveBuilder() .matchEquals("job", s -> { worker.tell("crunch", getSelf()); }) .matchEquals(SHUTDOWN, x -> { worker.tell(PoisonPill.getInstance(), getSelf()); getContext().become(shuttingDown()); }) .build(); } private AbstractActor.Receive shuttingDown() { return receiveBuilder() .matchEquals("job", s -> getSender().tell("service unavailable, shutting down", getSelf()) ) .match(Terminated.class, t -> t.actor().equals(worker), t -> getContext().stop(getSelf()) ) .build(); } } //#gracefulStop-actor @Test public void usePatternsGracefulStop() throws Exception { ActorRef actorRef = system.actorOf(Props.create(Manager.class)); //#gracefulStop try { CompletionStage stopped = gracefulStop(actorRef, Duration.create(5, TimeUnit.SECONDS), Manager.SHUTDOWN); stopped.toCompletableFuture().get(6, TimeUnit.SECONDS); // the actor has been stopped } catch (AskTimeoutException e) { // the actor wasn't stopped within 5 seconds } //#gracefulStop } public static class Cruncher extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder().matchEquals("crunch", s -> { }).build(); } } static //#swapper public class Swapper extends AbstractLoggingActor { @Override public Receive createReceive() { return receiveBuilder() .matchEquals(Swap, s -> { log().info("Hi"); getContext().become(receiveBuilder(). matchEquals(Swap, x -> { log().info("Ho"); getContext().unbecome(); // resets the latest 'become' (just for fun) }).build(), false); // push on top instead of replace }).build(); } } //#swapper static //#swapper public class SwapperApp { public static void main(String[] args) { ActorSystem system = ActorSystem.create("SwapperSystem"); ActorRef swapper = system.actorOf(Props.create(Swapper.class), "swapper"); swapper.tell(Swap, ActorRef.noSender()); // logs Hi swapper.tell(Swap, ActorRef.noSender()); // logs Ho swapper.tell(Swap, ActorRef.noSender()); // logs Hi swapper.tell(Swap, ActorRef.noSender()); // logs Ho swapper.tell(Swap, ActorRef.noSender()); // logs Hi swapper.tell(Swap, ActorRef.noSender()); // logs Ho system.terminate(); } } //#swapper @Test public void creatingActorWithSystemActorOf() { //#system-actorOf // ActorSystem is a heavy object: create only one per application final ActorSystem system = ActorSystem.create("MySystem", config); final ActorRef myActor = system.actorOf(Props.create(MyActor.class), "myactor"); //#system-actorOf try { new TestKit(system) { { myActor.tell("hello", getRef()); expectMsgEquals("hello"); } }; } finally { TestKit.shutdownActorSystem(system); } } @Test public void creatingGraduallyBuiltActorWithSystemActorOf() { final ActorSystem system = ActorSystem.create("MySystem", config); final ActorRef actor = system.actorOf(Props.create(GraduallyBuiltActor.class), "graduallyBuiltActor"); try { new TestKit(system) { { actor.tell("hello", getRef()); expectMsgEquals("hello"); } }; } finally { TestKit.shutdownActorSystem(system); } } @Test public void creatingPropsConfig() { //#creating-props Props props1 = Props.create(MyActor.class); Props props2 = Props.create(ActorWithArgs.class, () -> new ActorWithArgs("arg")); // careful, see below Props props3 = Props.create(ActorWithArgs.class, "arg"); //#creating-props //#creating-props-deprecated // NOT RECOMMENDED within another actor: // encourages to close over enclosing class Props props7 = Props.create(ActorWithArgs.class, () -> new ActorWithArgs("arg")); //#creating-props-deprecated } @Test(expected=IllegalArgumentException.class) public void creatingPropsIllegal() { //#creating-props-illegal // This will throw an IllegalArgumentException since some runtime // type information of the lambda is erased. // Use Props.create(actorClass, Creator) instead. Props props = Props.create(() -> new ActorWithArgs("arg")); //#creating-props-illegal } static //#receive-timeout public class ReceiveTimeoutActor extends AbstractActor { //#receive-timeout ActorRef target = getContext().getSystem().deadLetters(); //#receive-timeout public ReceiveTimeoutActor() { // To set an initial delay getContext().setReceiveTimeout(Duration.create(10, TimeUnit.SECONDS)); } @Override public Receive createReceive() { return receiveBuilder() .matchEquals("Hello", s -> { // To set in a response to a message getContext().setReceiveTimeout(Duration.create(1, TimeUnit.SECONDS)); //#receive-timeout target = getSender(); target.tell("Hello world", getSelf()); //#receive-timeout }) .match(ReceiveTimeout.class, r -> { // To turn it off getContext().setReceiveTimeout(Duration.Undefined()); //#receive-timeout target.tell("timeout", getSelf()); //#receive-timeout }) .build(); } } //#receive-timeout @Test public void using_receiveTimeout() { final ActorRef myActor = system.actorOf(Props.create(ReceiveTimeoutActor.class)); new TestKit(system) { { myActor.tell("Hello", getRef()); expectMsgEquals("Hello world"); expectMsgEquals("timeout"); } }; } static //#hot-swap-actor public class HotSwapActor extends AbstractActor { private AbstractActor.Receive angry; private AbstractActor.Receive happy; public HotSwapActor() { angry = receiveBuilder() .matchEquals("foo", s -> { getSender().tell("I am already angry?", getSelf()); }) .matchEquals("bar", s -> { getContext().become(happy); }) .build(); happy = receiveBuilder() .matchEquals("bar", s -> { getSender().tell("I am already happy :-)", getSelf()); }) .matchEquals("foo", s -> { getContext().become(angry); }) .build(); } @Override public Receive createReceive() { return receiveBuilder() .matchEquals("foo", s -> getContext().become(angry) ) .matchEquals("bar", s -> getContext().become(happy) ) .build(); } } //#hot-swap-actor @Test public void using_hot_swap() { final ActorRef actor = system.actorOf(Props.create(HotSwapActor.class), "hot"); new TestKit(system) { { actor.tell("foo", getRef()); actor.tell("foo", getRef()); expectMsgEquals("I am already angry?"); actor.tell("bar", getRef()); actor.tell("bar", getRef()); expectMsgEquals("I am already happy :-)"); actor.tell("foo", getRef()); actor.tell("foo", getRef()); expectMsgEquals("I am already angry?"); expectNoMsg(Duration.create(1, TimeUnit.SECONDS)); } }; } static //#stash public class ActorWithProtocol extends AbstractActorWithStash { @Override public Receive createReceive() { return receiveBuilder() .matchEquals("open", s -> { getContext().become(receiveBuilder() .matchEquals("write", ws -> { /* do writing */ }) .matchEquals("close", cs -> { unstashAll(); getContext().unbecome(); }) .matchAny(msg -> stash()) .build(), false); }) .matchAny(msg -> stash()) .build(); } } //#stash @Test public void using_Stash() { final ActorRef actor = system.actorOf(Props.create(ActorWithProtocol.class), "stash"); } static //#watch public class WatchActor extends AbstractActor { private final ActorRef child = getContext().actorOf(Props.empty(), "target"); private ActorRef lastSender = system.deadLetters(); public WatchActor() { getContext().watch(child); // <-- this is the only call needed for registration } @Override public Receive createReceive() { return receiveBuilder() .matchEquals("kill", s -> { getContext().stop(child); lastSender = getSender(); }) .match(Terminated.class, t -> t.actor().equals(child), t -> { lastSender.tell("finished", getSelf()); }) .build(); } } //#watch @Test public void using_watch() { ActorRef actor = system.actorOf(Props.create(WatchActor.class)); new TestKit(system) { { actor.tell("kill", getRef()); expectMsgEquals("finished"); } }; } static //#identify public class Follower extends AbstractActor { final Integer identifyId = 1; public Follower(){ ActorSelection selection = getContext().actorSelection("/user/another"); selection.tell(new Identify(identifyId), getSelf()); } @Override public Receive createReceive() { return receiveBuilder() .match(ActorIdentity.class, id -> id.getActorRef().isPresent(), id -> { ActorRef ref = id.getActorRef().get(); getContext().watch(ref); getContext().become(active(ref)); }) .match(ActorIdentity.class, id -> !id.getActorRef().isPresent(), id -> { getContext().stop(getSelf()); }) .build(); } final AbstractActor.Receive active(final ActorRef another) { return receiveBuilder() .match(Terminated.class, t -> t.actor().equals(another), t -> getContext().stop(getSelf()) ) .build(); } } //#identify @Test public void using_Identify() { ActorRef a = system.actorOf(Props.empty()); ActorRef b = system.actorOf(Props.create(Follower.class)); new TestKit(system) { { watch(b); system.stop(a); assertEquals(expectMsgClass(Duration.create(2, TimeUnit.SECONDS), Terminated.class).actor(), b); } }; } @Test public void usePatternsAskPipe() { new TestKit(system) { { ActorRef actorA = system.actorOf(TestActors.echoActorProps()); ActorRef actorB = system.actorOf(TestActors.echoActorProps()); ActorRef actorC = getRef(); //#ask-pipe Timeout t = new Timeout(Duration.create(5, TimeUnit.SECONDS)); // using 1000ms timeout CompletableFuture future1 = ask(actorA, "request", 1000).toCompletableFuture(); // using timeout from above CompletableFuture future2 = ask(actorB, "another request", t).toCompletableFuture(); CompletableFuture transformed = CompletableFuture.allOf(future1, future2) .thenApply(v -> { String x = (String) future1.join(); String s = (String) future2.join(); return new Result(x, s); }); pipe(transformed, system.dispatcher()).to(actorC); //#ask-pipe expectMsgEquals(new Result("request", "another request")); } }; } @Test public void useKill() { new TestKit(system) { { ActorRef victim = system.actorOf(TestActors.echoActorProps()); watch(victim); //#kill victim.tell(akka.actor.Kill.getInstance(), ActorRef.noSender()); //#kill expectTerminated(Duration.create(3, TimeUnit.SECONDS), victim); } }; } @Test public void usePoisonPill() { new TestKit(system) { { ActorRef victim = system.actorOf(TestActors.echoActorProps()); watch(victim); //#poison-pill victim.tell(akka.actor.PoisonPill.getInstance(), ActorRef.noSender()); //#poison-pill expectTerminated(Duration.create(3, TimeUnit.SECONDS), victim); } }; } @Test public void coordinatedShutdown() { final ActorRef someActor = system.actorOf(Props.create(FirstActor.class)); //#coordinated-shutdown-addTask CoordinatedShutdown.get(system).addTask( CoordinatedShutdown.PhaseBeforeServiceUnbind(), "someTaskName", () -> { return akka.pattern.PatternsCS.ask(someActor, "stop", new Timeout(5, TimeUnit.SECONDS)) .thenApply(reply -> Done.getInstance()); }); //#coordinated-shutdown-addTask //#coordinated-shutdown-jvm-hook CoordinatedShutdown.get(system).addJvmShutdownHook(() -> System.out.println("custom JVM shutdown hook...") ); //#coordinated-shutdown-jvm-hook // don't run this if (false) { //#coordinated-shutdown-run CompletionStage done = CoordinatedShutdown.get(system).runAll(); //#coordinated-shutdown-run } } }