move code to src/test
* so that it compiles and tests pass * fix some additional snip references in getting started
This commit is contained in:
parent
413df8e0f4
commit
59f53e1a22
289 changed files with 45 additions and 45 deletions
13
akka-docs/src/test/java/jdocs/AbstractJavaTest.scala
Normal file
13
akka-docs/src/test/java/jdocs/AbstractJavaTest.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
/**
|
||||
* Base class for all runnable example tests written in Java
|
||||
*/
|
||||
abstract class AbstractJavaTest extends JUnitSuite {
|
||||
|
||||
}
|
||||
837
akka-docs/src/test/java/jdocs/actor/ActorDocTest.java
Normal file
837
akka-docs/src/test/java/jdocs/actor/ActorDocTest.java
Normal file
|
|
@ -0,0 +1,837 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
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<Object> 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<Boolean> 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<Object> future1 =
|
||||
ask(actorA, "request", 1000).toCompletableFuture();
|
||||
|
||||
// using timeout from above
|
||||
CompletableFuture<Object> future2 =
|
||||
ask(actorB, "another request", t).toCompletableFuture();
|
||||
|
||||
CompletableFuture<Result> 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> done = CoordinatedShutdown.get(system).runAll();
|
||||
//#coordinated-shutdown-run
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
//#bytebufserializer-with-manifest
|
||||
import akka.serialization.ByteBufferSerializer;
|
||||
import akka.serialization.SerializerWithStringManifest;
|
||||
|
||||
//#bytebufserializer-with-manifest
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteBufferSerializerDocTest {
|
||||
|
||||
|
||||
static //#bytebufserializer-with-manifest
|
||||
class ExampleByteBufSerializer extends SerializerWithStringManifest
|
||||
implements ByteBufferSerializer {
|
||||
|
||||
@Override
|
||||
public int identifier() {
|
||||
return 1337;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String manifest(Object o) {
|
||||
return "serialized-" + o.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBinary(Object o) {
|
||||
// in production code, aquire this from a BufferPool
|
||||
final ByteBuffer buf = ByteBuffer.allocate(256);
|
||||
|
||||
toBinary(o, buf);
|
||||
buf.flip();
|
||||
final byte[] bytes = new byte[buf.remaining()];
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinary(byte[] bytes, String manifest) {
|
||||
return fromBinary(ByteBuffer.wrap(bytes), manifest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBinary(Object o, ByteBuffer buf) {
|
||||
// Implement actual serialization here
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinary(ByteBuffer buf, String manifest) {
|
||||
// Implement actual deserialization here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//#bytebufserializer-with-manifest
|
||||
|
||||
static class OnlyForDocInclude {
|
||||
static
|
||||
//#ByteBufferSerializer-interface
|
||||
interface ByteBufferSerializer {
|
||||
/**
|
||||
* Serializes the given object into the `ByteBuffer`.
|
||||
*/
|
||||
void toBinary(Object o, ByteBuffer buf);
|
||||
|
||||
/**
|
||||
* Produces an object from a `ByteBuffer`, with an optional type-hint;
|
||||
* the class should be loaded using ActorSystem.dynamicAccess.
|
||||
*/
|
||||
Object fromBinary(ByteBuffer buf, String manifest);
|
||||
}
|
||||
//#ByteBufferSerializer-interface
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
|
||||
//#import
|
||||
import akka.actor.Actor;
|
||||
import akka.actor.IndirectActorProducer;
|
||||
//#import
|
||||
|
||||
public class DependencyInjectionDocTest extends AbstractJavaTest {
|
||||
|
||||
public static class TheActor extends AbstractActor {
|
||||
|
||||
final String s;
|
||||
|
||||
public TheActor(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, msg -> {
|
||||
getSender().tell(s, getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
static ActorSystem system = null;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
system = ActorSystem.create("DependencyInjectionDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
Await.ready(system.terminate(), Duration.create("5 seconds"));
|
||||
}
|
||||
|
||||
//this is just to make the test below a tiny fraction nicer
|
||||
private ActorSystem getContext() {
|
||||
return system;
|
||||
}
|
||||
|
||||
static
|
||||
//#creating-indirectly
|
||||
class DependencyInjector implements IndirectActorProducer {
|
||||
final Object applicationContext;
|
||||
final String beanName;
|
||||
|
||||
public DependencyInjector(Object applicationContext, String beanName) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Actor> actorClass() {
|
||||
return TheActor.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheActor produce() {
|
||||
TheActor result;
|
||||
//#obtain-fresh-Actor-instance-from-DI-framework
|
||||
result = new TheActor((String) applicationContext);
|
||||
//#obtain-fresh-Actor-instance-from-DI-framework
|
||||
return result;
|
||||
}
|
||||
}
|
||||
//#creating-indirectly
|
||||
|
||||
@Test
|
||||
public void indirectActorOf() {
|
||||
final String applicationContext = "...";
|
||||
//#creating-indirectly
|
||||
|
||||
final ActorRef myActor = getContext().actorOf(
|
||||
Props.create(DependencyInjector.class, applicationContext, "TheActor"),
|
||||
"TheActor");
|
||||
//#creating-indirectly
|
||||
new TestKit(system) {
|
||||
{
|
||||
myActor.tell("hello", getRef());
|
||||
expectMsgEquals("...");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
469
akka-docs/src/test/java/jdocs/actor/FaultHandlingDocSample.java
Normal file
469
akka-docs/src/test/java/jdocs/actor/FaultHandlingDocSample.java
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
//#all
|
||||
//#imports
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Mapper;
|
||||
import akka.event.LoggingReceive;
|
||||
import akka.japi.pf.DeciderBuilder;
|
||||
import akka.pattern.Patterns;
|
||||
import akka.util.Timeout;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import static akka.japi.Util.classTag;
|
||||
import static akka.actor.SupervisorStrategy.restart;
|
||||
import static akka.actor.SupervisorStrategy.stop;
|
||||
import static akka.actor.SupervisorStrategy.escalate;
|
||||
|
||||
import static akka.pattern.Patterns.ask;
|
||||
import static akka.pattern.Patterns.pipe;
|
||||
|
||||
import static jdocs.actor.FaultHandlingDocSample.WorkerApi.*;
|
||||
import static jdocs.actor.FaultHandlingDocSample.CounterServiceApi.*;
|
||||
import static jdocs.actor.FaultHandlingDocSample.CounterApi.*;
|
||||
import static jdocs.actor.FaultHandlingDocSample.StorageApi.*;
|
||||
|
||||
//#imports
|
||||
|
||||
public class FaultHandlingDocSample {
|
||||
|
||||
/**
|
||||
* Runs the sample
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Config config = ConfigFactory.parseString(
|
||||
"akka.loglevel = \"DEBUG\"\n" +
|
||||
"akka.actor.debug {\n" +
|
||||
" receive = on\n" +
|
||||
" lifecycle = on\n" +
|
||||
"}\n");
|
||||
|
||||
ActorSystem system = ActorSystem.create("FaultToleranceSample", config);
|
||||
ActorRef worker = system.actorOf(Props.create(Worker.class), "worker");
|
||||
ActorRef listener = system.actorOf(Props.create(Listener.class), "listener");
|
||||
// start the work and listen on progress
|
||||
// note that the listener is used as sender of the tell,
|
||||
// i.e. it will receive replies from the worker
|
||||
worker.tell(Start, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on progress from the worker and shuts down the system when enough
|
||||
* work has been done.
|
||||
*/
|
||||
public static class Listener extends AbstractLoggingActor {
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
// If we don't get any progress within 15 seconds then the service
|
||||
// is unavailable
|
||||
getContext().setReceiveTimeout(Duration.create("15 seconds"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return LoggingReceive.create(receiveBuilder().
|
||||
match(Progress.class, progress -> {
|
||||
log().info("Current progress: {} %", progress.percent);
|
||||
if (progress.percent >= 100.0) {
|
||||
log().info("That's all, shutting down");
|
||||
getContext().getSystem().terminate();
|
||||
}
|
||||
}).
|
||||
matchEquals(ReceiveTimeout.getInstance(), x -> {
|
||||
// No progress within 15 seconds, ServiceUnavailable
|
||||
log().error("Shutting down due to unavailable service");
|
||||
getContext().getSystem().terminate();
|
||||
}).build(), getContext());
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface WorkerApi {
|
||||
public static final Object Start = "Start";
|
||||
public static final Object Do = "Do";
|
||||
|
||||
public static class Progress {
|
||||
public final double percent;
|
||||
|
||||
public Progress(double percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Worker performs some work when it receives the Start message. It will
|
||||
* continuously notify the sender of the Start message of current Progress.
|
||||
* The Worker supervise the CounterService.
|
||||
*/
|
||||
public static class Worker extends AbstractLoggingActor {
|
||||
final Timeout askTimeout = new Timeout(Duration.create(5, "seconds"));
|
||||
|
||||
// The sender of the initial Start message will continuously be notified
|
||||
// about progress
|
||||
ActorRef progressListener;
|
||||
final ActorRef counterService = getContext().actorOf(
|
||||
Props.create(CounterService.class), "counter");
|
||||
final int totalCount = 51;
|
||||
|
||||
// Stop the CounterService child if it throws ServiceUnavailable
|
||||
private static final SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(DeciderBuilder.
|
||||
match(ServiceUnavailable.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return LoggingReceive.create(receiveBuilder().
|
||||
matchEquals(Start, x -> progressListener == null, x -> {
|
||||
progressListener = getSender();
|
||||
getContext().getSystem().scheduler().schedule(
|
||||
Duration.Zero(), Duration.create(1, "second"), getSelf(), Do,
|
||||
getContext().dispatcher(), null
|
||||
);
|
||||
}).
|
||||
matchEquals(Do, x -> {
|
||||
counterService.tell(new Increment(1), getSelf());
|
||||
counterService.tell(new Increment(1), getSelf());
|
||||
counterService.tell(new Increment(1), getSelf());
|
||||
// Send current progress to the initial sender
|
||||
pipe(Patterns.ask(counterService, GetCurrentCount, askTimeout)
|
||||
.mapTo(classTag(CurrentCount.class))
|
||||
.map(new Mapper<CurrentCount, Progress>() {
|
||||
public Progress apply(CurrentCount c) {
|
||||
return new Progress(100.0 * c.count / totalCount);
|
||||
}
|
||||
}, getContext().dispatcher()), getContext().dispatcher())
|
||||
.to(progressListener);
|
||||
}).build(), getContext());
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface CounterServiceApi {
|
||||
|
||||
public static final Object GetCurrentCount = "GetCurrentCount";
|
||||
|
||||
public static class CurrentCount {
|
||||
public final String key;
|
||||
public final long count;
|
||||
|
||||
public CurrentCount(String key, long count) {
|
||||
this.key = key;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s, %s)", getClass().getSimpleName(), key, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Increment {
|
||||
public final long n;
|
||||
|
||||
public Increment(long n) {
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), n);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServiceUnavailable extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public ServiceUnavailable(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Adds the value received in Increment message to a persistent counter.
|
||||
* Replies with CurrentCount when it is asked for CurrentCount. CounterService
|
||||
* supervise Storage and Counter.
|
||||
*/
|
||||
public static class CounterService extends AbstractLoggingActor {
|
||||
|
||||
// Reconnect message
|
||||
static final Object Reconnect = "Reconnect";
|
||||
|
||||
private static class SenderMsgPair {
|
||||
final ActorRef sender;
|
||||
final Object msg;
|
||||
|
||||
SenderMsgPair(ActorRef sender, Object msg) {
|
||||
this.msg = msg;
|
||||
this.sender = sender;
|
||||
}
|
||||
}
|
||||
|
||||
final String key = getSelf().path().name();
|
||||
ActorRef storage;
|
||||
ActorRef counter;
|
||||
final List<SenderMsgPair> backlog = new ArrayList<>();
|
||||
final int MAX_BACKLOG = 10000;
|
||||
|
||||
// Restart the storage child when StorageException is thrown.
|
||||
// After 3 restarts within 5 seconds it will be stopped.
|
||||
private static final SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(3, Duration.create("5 seconds"), DeciderBuilder.
|
||||
match(StorageException.class, e -> restart()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
initStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* The child storage is restarted in case of failure, but after 3 restarts,
|
||||
* and still failing it will be stopped. Better to back-off than
|
||||
* continuously failing. When it has been stopped we will schedule a
|
||||
* Reconnect after a delay. Watch the child so we receive Terminated message
|
||||
* when it has been terminated.
|
||||
*/
|
||||
void initStorage() {
|
||||
storage = getContext().watch(getContext().actorOf(
|
||||
Props.create(Storage.class), "storage"));
|
||||
// Tell the counter, if any, to use the new storage
|
||||
if (counter != null)
|
||||
counter.tell(new UseStorage(storage), getSelf());
|
||||
// We need the initial value to be able to operate
|
||||
storage.tell(new Get(key), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return LoggingReceive.create(receiveBuilder().
|
||||
match(Entry.class, entry -> entry.key.equals(key) && counter == null, entry -> {
|
||||
// Reply from Storage of the initial value, now we can create the Counter
|
||||
final long value = entry.value;
|
||||
counter = getContext().actorOf(Props.create(Counter.class, key, value));
|
||||
// Tell the counter to use current storage
|
||||
counter.tell(new UseStorage(storage), getSelf());
|
||||
// and send the buffered backlog to the counter
|
||||
for (SenderMsgPair each : backlog) {
|
||||
counter.tell(each.msg, each.sender);
|
||||
}
|
||||
backlog.clear();
|
||||
}).
|
||||
match(Increment.class, increment -> {
|
||||
forwardOrPlaceInBacklog(increment);
|
||||
}).
|
||||
matchEquals(GetCurrentCount, gcc -> {
|
||||
forwardOrPlaceInBacklog(gcc);
|
||||
}).
|
||||
match(Terminated.class, o -> {
|
||||
// After 3 restarts the storage child is stopped.
|
||||
// We receive Terminated because we watch the child, see initStorage.
|
||||
storage = null;
|
||||
// Tell the counter that there is no storage for the moment
|
||||
counter.tell(new UseStorage(null), getSelf());
|
||||
// Try to re-establish storage after while
|
||||
getContext().getSystem().scheduler().scheduleOnce(
|
||||
Duration.create(10, "seconds"), getSelf(), Reconnect,
|
||||
getContext().dispatcher(), null);
|
||||
}).
|
||||
matchEquals(Reconnect, o -> {
|
||||
// Re-establish storage after the scheduled delay
|
||||
initStorage();
|
||||
}).build(), getContext());
|
||||
}
|
||||
|
||||
void forwardOrPlaceInBacklog(Object msg) {
|
||||
// We need the initial value from storage before we can start delegate to
|
||||
// the counter. Before that we place the messages in a backlog, to be sent
|
||||
// to the counter when it is initialized.
|
||||
if (counter == null) {
|
||||
if (backlog.size() >= MAX_BACKLOG)
|
||||
throw new ServiceUnavailable("CounterService not available," +
|
||||
" lack of initial value");
|
||||
backlog.add(new SenderMsgPair(getSender(), msg));
|
||||
} else {
|
||||
counter.forward(msg, getContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface CounterApi {
|
||||
public static class UseStorage {
|
||||
public final ActorRef storage;
|
||||
|
||||
public UseStorage(ActorRef storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* The in memory count variable that will send current value to the Storage,
|
||||
* if there is any storage available at the moment.
|
||||
*/
|
||||
public static class Counter extends AbstractLoggingActor {
|
||||
final String key;
|
||||
long count;
|
||||
ActorRef storage;
|
||||
|
||||
public Counter(String key, long initialValue) {
|
||||
this.key = key;
|
||||
this.count = initialValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return LoggingReceive.create(receiveBuilder().
|
||||
match(UseStorage.class, useStorage -> {
|
||||
storage = useStorage.storage;
|
||||
storeCount();
|
||||
}).
|
||||
match(Increment.class, increment -> {
|
||||
count += increment.n;
|
||||
storeCount();
|
||||
}).
|
||||
matchEquals(GetCurrentCount, gcc -> {
|
||||
getSender().tell(new CurrentCount(key, count), getSelf());
|
||||
}).build(), getContext());
|
||||
}
|
||||
|
||||
void storeCount() {
|
||||
// Delegate dangerous work, to protect our valuable state.
|
||||
// We can continue without storage.
|
||||
if (storage != null) {
|
||||
storage.tell(new Store(new Entry(key, count)), getSelf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface StorageApi {
|
||||
|
||||
public static class Store {
|
||||
public final Entry entry;
|
||||
|
||||
public Store(Entry entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entry {
|
||||
public final String key;
|
||||
public final long value;
|
||||
|
||||
public Entry(String key, long value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s, %s)", getClass().getSimpleName(), key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Get {
|
||||
public final String key;
|
||||
|
||||
public Get(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StorageException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public StorageException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Saves key/value pairs to persistent storage when receiving Store message.
|
||||
* Replies with current value when receiving Get message. Will throw
|
||||
* StorageException if the underlying data store is out of order.
|
||||
*/
|
||||
public static class Storage extends AbstractLoggingActor {
|
||||
|
||||
final DummyDB db = DummyDB.instance;
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return LoggingReceive.create(receiveBuilder().
|
||||
match(Store.class, store -> {
|
||||
db.save(store.entry.key, store.entry.value);
|
||||
}).
|
||||
match(Get.class, get -> {
|
||||
Long value = db.load(get.key);
|
||||
getSender().tell(new Entry(get.key, value == null ?
|
||||
Long.valueOf(0L) : value), getSelf());
|
||||
}).build(), getContext());
|
||||
}
|
||||
}
|
||||
|
||||
//#dummydb
|
||||
public static class DummyDB {
|
||||
public static final DummyDB instance = new DummyDB();
|
||||
private final Map<String, Long> db = new HashMap<String, Long>();
|
||||
|
||||
private DummyDB() {
|
||||
}
|
||||
|
||||
public synchronized void save(String key, Long value) throws StorageException {
|
||||
if (11 <= value && value <= 14)
|
||||
throw new StorageException("Simulated store failure " + value);
|
||||
db.put(key, value);
|
||||
}
|
||||
|
||||
public synchronized Long load(String key) throws StorageException {
|
||||
return db.get(key);
|
||||
}
|
||||
}
|
||||
//#dummydb
|
||||
}
|
||||
//#all
|
||||
212
akka-docs/src/test/java/jdocs/actor/FaultHandlingTest.java
Normal file
212
akka-docs/src/test/java/jdocs/actor/FaultHandlingTest.java
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
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 java.util.Optional;
|
||||
|
||||
import static akka.pattern.Patterns.ask;
|
||||
|
||||
//#testkit
|
||||
import akka.testkit.TestProbe;
|
||||
import akka.testkit.ErrorFilter;
|
||||
import akka.testkit.EventFilter;
|
||||
import akka.testkit.TestEvent;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static akka.japi.Util.immutableSeq;
|
||||
import scala.concurrent.Await;
|
||||
|
||||
//#testkit
|
||||
|
||||
//#supervisor
|
||||
import akka.japi.pf.DeciderBuilder;
|
||||
import static akka.actor.SupervisorStrategy.resume;
|
||||
import static akka.actor.SupervisorStrategy.restart;
|
||||
import static akka.actor.SupervisorStrategy.stop;
|
||||
import static akka.actor.SupervisorStrategy.escalate;
|
||||
|
||||
//#supervisor
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.AfterClass;
|
||||
|
||||
//#testkit
|
||||
public class FaultHandlingTest extends AbstractJavaTest {
|
||||
//#testkit
|
||||
|
||||
public static Config config = ConfigFactory.parseString(
|
||||
"akka {\n" +
|
||||
" loggers = [\"akka.testkit.TestEventListener\"]\n" +
|
||||
" loglevel = \"WARNING\"\n" +
|
||||
" stdout-loglevel = \"WARNING\"\n" +
|
||||
"}\n");
|
||||
|
||||
static
|
||||
//#supervisor
|
||||
public class Supervisor extends AbstractActor {
|
||||
|
||||
//#strategy
|
||||
private static SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder.
|
||||
match(ArithmeticException.class, e -> resume()).
|
||||
match(NullPointerException.class, e -> restart()).
|
||||
match(IllegalArgumentException.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
//#strategy
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Props.class, props -> {
|
||||
getSender().tell(getContext().actorOf(props), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
//#supervisor
|
||||
|
||||
static
|
||||
//#supervisor2
|
||||
public class Supervisor2 extends AbstractActor {
|
||||
|
||||
//#strategy2
|
||||
private static SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder.
|
||||
match(ArithmeticException.class, e -> resume()).
|
||||
match(NullPointerException.class, e -> restart()).
|
||||
match(IllegalArgumentException.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
//#strategy2
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Props.class, props -> {
|
||||
getSender().tell(getContext().actorOf(props), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRestart(Throwable cause, Optional<Object> msg) {
|
||||
// do not kill all children, which is the default here
|
||||
}
|
||||
}
|
||||
|
||||
//#supervisor2
|
||||
|
||||
static
|
||||
//#child
|
||||
public class Child extends AbstractActor {
|
||||
int state = 0;
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Exception.class, exception -> { throw exception; })
|
||||
.match(Integer.class, i -> state = i)
|
||||
.matchEquals("get", s -> getSender().tell(state, getSelf()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
//#child
|
||||
|
||||
//#testkit
|
||||
static ActorSystem system;
|
||||
Duration timeout = Duration.create(5, SECONDS);
|
||||
|
||||
@BeforeClass
|
||||
public static void start() {
|
||||
system = ActorSystem.create("FaultHandlingTest", config);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mustEmploySupervisorStrategy() throws Exception {
|
||||
// code here
|
||||
//#testkit
|
||||
EventFilter ex1 = new ErrorFilter(ArithmeticException.class);
|
||||
EventFilter ex2 = new ErrorFilter(NullPointerException.class);
|
||||
EventFilter ex3 = new ErrorFilter(IllegalArgumentException.class);
|
||||
EventFilter ex4 = new ErrorFilter(Exception.class);
|
||||
EventFilter[] ignoreExceptions = { ex1, ex2, ex3, ex4 };
|
||||
system.eventStream().publish(new TestEvent.Mute(immutableSeq(ignoreExceptions)));
|
||||
|
||||
//#create
|
||||
Props superprops = Props.create(Supervisor.class);
|
||||
ActorRef supervisor = system.actorOf(superprops, "supervisor");
|
||||
ActorRef child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
//#create
|
||||
|
||||
//#resume
|
||||
child.tell(42, ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(42);
|
||||
child.tell(new ArithmeticException(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(42);
|
||||
//#resume
|
||||
|
||||
//#restart
|
||||
child.tell(new NullPointerException(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
//#restart
|
||||
|
||||
//#stop
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
probe.watch(child);
|
||||
child.tell(new IllegalArgumentException(), ActorRef.noSender());
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
//#stop
|
||||
|
||||
//#escalate-kill
|
||||
child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
probe.watch(child);
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
child.tell(new Exception(), ActorRef.noSender());
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
//#escalate-kill
|
||||
|
||||
//#escalate-restart
|
||||
superprops = Props.create(Supervisor2.class);
|
||||
supervisor = system.actorOf(superprops);
|
||||
child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
child.tell(23, ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(23);
|
||||
child.tell(new Exception(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
//#escalate-restart
|
||||
//#testkit
|
||||
}
|
||||
|
||||
}
|
||||
//#testkit
|
||||
39
akka-docs/src/test/java/jdocs/actor/GraduallyBuiltActor.java
Normal file
39
akka-docs/src/test/java/jdocs/actor/GraduallyBuiltActor.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
//#imports
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
|
||||
//#imports
|
||||
|
||||
//#actor
|
||||
public class GraduallyBuiltActor extends AbstractActor {
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder builder = ReceiveBuilder.create();
|
||||
|
||||
builder.match(String.class, s -> {
|
||||
log.info("Received String message: {}", s);
|
||||
//#actor
|
||||
//#reply
|
||||
getSender().tell(s, getSelf());
|
||||
//#reply
|
||||
//#actor
|
||||
});
|
||||
|
||||
// do some other stuff in between
|
||||
|
||||
builder.matchAny(o -> log.info("received unknown message"));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
//#actor
|
||||
28
akka-docs/src/test/java/jdocs/actor/ImmutableMessage.java
Normal file
28
akka-docs/src/test/java/jdocs/actor/ImmutableMessage.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
//#immutable-message
|
||||
public class ImmutableMessage {
|
||||
private final int sequenceNumber;
|
||||
private final List<String> values;
|
||||
|
||||
public ImmutableMessage(int sequenceNumber, List<String> values) {
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.values = Collections.unmodifiableList(new ArrayList<String>(values));
|
||||
}
|
||||
|
||||
public int getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
//#immutable-message
|
||||
66
akka-docs/src/test/java/jdocs/actor/InboxDocTest.java
Normal file
66
akka-docs/src/test/java/jdocs/actor/InboxDocTest.java
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Inbox;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.Terminated;
|
||||
import akka.testkit.AkkaSpec;
|
||||
|
||||
public class InboxDocTest extends AbstractJavaTest {
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("InboxDocTest", AkkaSpec.testConf());
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
@Test
|
||||
public void demonstrateInbox() {
|
||||
final TestKit probe = new TestKit(system);
|
||||
final ActorRef target = probe.getRef();
|
||||
//#inbox
|
||||
final Inbox inbox = Inbox.create(system);
|
||||
inbox.send(target, "hello");
|
||||
//#inbox
|
||||
probe.expectMsgEquals("hello");
|
||||
probe.send(probe.getLastSender(), "world");
|
||||
//#inbox
|
||||
try {
|
||||
assert inbox.receive(Duration.create(1, TimeUnit.SECONDS)).equals("world");
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
// timeout
|
||||
}
|
||||
//#inbox
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateWatch() {
|
||||
final TestKit probe = new TestKit(system);
|
||||
final ActorRef target = probe.getRef();
|
||||
//#watch
|
||||
final Inbox inbox = Inbox.create(system);
|
||||
inbox.watch(target);
|
||||
target.tell(PoisonPill.getInstance(), ActorRef.noSender());
|
||||
try {
|
||||
assert inbox.receive(Duration.create(1, TimeUnit.SECONDS)) instanceof Terminated;
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
// timeout
|
||||
}
|
||||
//#watch
|
||||
}
|
||||
|
||||
}
|
||||
161
akka-docs/src/test/java/jdocs/actor/InitializationDocTest.java
Normal file
161
akka-docs/src/test/java/jdocs/actor/InitializationDocTest.java
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.japi.pf.FI;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class InitializationDocTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system = null;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
system = ActorSystem.create("InitializationDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
Await.ready(system.terminate(), Duration.create("5 seconds"));
|
||||
}
|
||||
|
||||
static public class PreStartInitExample extends AbstractActor {
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return AbstractActor.emptyBehavior();
|
||||
}
|
||||
|
||||
//#preStartInit
|
||||
@Override
|
||||
public void preStart() {
|
||||
// Initialize children here
|
||||
}
|
||||
|
||||
// Overriding postRestart to disable the call to preStart()
|
||||
// after restarts
|
||||
@Override
|
||||
public void postRestart(Throwable reason) {
|
||||
}
|
||||
|
||||
// The default implementation of preRestart() stops all the children
|
||||
// of the actor. To opt-out from stopping the children, we
|
||||
// have to override preRestart()
|
||||
@Override
|
||||
public void preRestart(Throwable reason, Optional<Object> message)
|
||||
throws Exception {
|
||||
// Keep the call to postStop(), but no stopping of children
|
||||
postStop();
|
||||
}
|
||||
//#preStartInit
|
||||
|
||||
}
|
||||
|
||||
public static class MessageInitExample extends AbstractActor {
|
||||
private String initializeMe = null;
|
||||
|
||||
//#messageInit
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("init", m1 -> {
|
||||
initializeMe = "Up and running";
|
||||
getContext().become(receiveBuilder()
|
||||
.matchEquals("U OK?", m2 -> {
|
||||
getSender().tell(initializeMe, getSelf());
|
||||
})
|
||||
.build());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#messageInit
|
||||
}
|
||||
|
||||
public class GenericMessage<T> {
|
||||
T value;
|
||||
|
||||
public GenericMessage(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GenericActor extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchUnchecked(GenericMessage.class, (GenericMessage<String> msg) -> {
|
||||
GenericMessage<String> message = msg;
|
||||
getSender().tell(message.value.toUpperCase(), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
static class GenericActorWithPredicate extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
FI.TypedPredicate<GenericMessage<String>> typedPredicate = s -> !s.value.isEmpty();
|
||||
|
||||
return receiveBuilder()
|
||||
.matchUnchecked(GenericMessage.class, typedPredicate, (GenericMessage<String> msg) -> {
|
||||
getSender().tell(msg.value.toUpperCase(), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIt() {
|
||||
|
||||
new TestKit(system) {{
|
||||
ActorRef testactor = system.actorOf(Props.create(MessageInitExample.class), "testactor");
|
||||
String msg = "U OK?";
|
||||
|
||||
testactor.tell(msg, getRef());
|
||||
expectNoMsg(Duration.create(1, TimeUnit.SECONDS));
|
||||
|
||||
testactor.tell("init", getRef());
|
||||
testactor.tell(msg, getRef());
|
||||
expectMsgEquals("Up and running");
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenericActor() {
|
||||
new TestKit(system) {{
|
||||
ActorRef genericTestActor = system.actorOf(Props.create(GenericActor.class), "genericActor");
|
||||
GenericMessage<String> genericMessage = new GenericMessage<String>("a");
|
||||
|
||||
genericTestActor.tell(genericMessage, getRef());
|
||||
expectMsgEquals("A");
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actorShouldNotRespondForEmptyMessage() {
|
||||
new TestKit(system) {{
|
||||
ActorRef genericTestActor = system.actorOf(Props.create(GenericActorWithPredicate.class), "genericActorWithPredicate");
|
||||
GenericMessage<String> emptyGenericMessage = new GenericMessage<String>("");
|
||||
GenericMessage<String> nonEmptyGenericMessage = new GenericMessage<String>("a");
|
||||
|
||||
genericTestActor.tell(emptyGenericMessage, getRef());
|
||||
expectNoMsg();
|
||||
|
||||
genericTestActor.tell(nonEmptyGenericMessage, getRef());
|
||||
expectMsgEquals("A");
|
||||
}};
|
||||
}
|
||||
}
|
||||
149
akka-docs/src/test/java/jdocs/actor/Messages.java
Normal file
149
akka-docs/src/test/java/jdocs/actor/Messages.java
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Messages {
|
||||
static
|
||||
//#immutable-message
|
||||
public class ImmutableMessage {
|
||||
private final int sequenceNumber;
|
||||
private final List<String> values;
|
||||
|
||||
public ImmutableMessage(int sequenceNumber, List<String> values) {
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.values = Collections.unmodifiableList(new ArrayList<String>(values));
|
||||
}
|
||||
|
||||
public int getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
//#immutable-message
|
||||
|
||||
public static class DoIt {
|
||||
private final ImmutableMessage msg;
|
||||
|
||||
DoIt(ImmutableMessage msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public ImmutableMessage getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DoIt doIt = (DoIt) o;
|
||||
|
||||
if (!msg.equals(doIt.msg)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return msg.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DoIt{" +
|
||||
"msg=" + msg +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Message {
|
||||
final String str;
|
||||
|
||||
Message(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Message message = (Message) o;
|
||||
|
||||
if (!str.equals(message.str)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return str.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Message{" +
|
||||
"str='" + str + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Swap {
|
||||
Swap
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
final String x;
|
||||
final String s;
|
||||
|
||||
public Result(String x, String s) {
|
||||
this.x = x;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((s == null) ? 0 : s.hashCode());
|
||||
result = prime * result + ((x == null) ? 0 : x.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Result other = (Result) obj;
|
||||
if (s == null) {
|
||||
if (other.s != null)
|
||||
return false;
|
||||
} else if (!s.equals(other.s))
|
||||
return false;
|
||||
if (x == null) {
|
||||
if (other.x != null)
|
||||
return false;
|
||||
} else if (!x.equals(other.x))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
akka-docs/src/test/java/jdocs/actor/MyActor.java
Normal file
33
akka-docs/src/test/java/jdocs/actor/MyActor.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
//#imports
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
//#imports
|
||||
|
||||
//#my-actor
|
||||
public class MyActor extends AbstractActor {
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, s -> {
|
||||
log.info("Received String message: {}", s);
|
||||
//#my-actor
|
||||
//#reply
|
||||
getSender().tell(s, getSelf());
|
||||
//#reply
|
||||
//#my-actor
|
||||
})
|
||||
.matchAny(o -> log.info("received unknown message"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#my-actor
|
||||
14
akka-docs/src/test/java/jdocs/actor/MyBoundedActor.java
Normal file
14
akka-docs/src/test/java/jdocs/actor/MyBoundedActor.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
//#my-bounded-untyped-actor
|
||||
import akka.dispatch.BoundedMessageQueueSemantics;
|
||||
import akka.dispatch.RequiresMessageQueue;
|
||||
|
||||
public class MyBoundedActor extends MyActor
|
||||
implements RequiresMessageQueue<BoundedMessageQueueSemantics> {
|
||||
}
|
||||
//#my-bounded-untyped-actor
|
||||
29
akka-docs/src/test/java/jdocs/actor/MyStoppingActor.java
Normal file
29
akka-docs/src/test/java/jdocs/actor/MyStoppingActor.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
//#my-stopping-actor
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
public class MyStoppingActor extends AbstractActor {
|
||||
|
||||
ActorRef child = null;
|
||||
|
||||
// ... creation of child ...
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("interrupt-child", m ->
|
||||
getContext().stop(child)
|
||||
)
|
||||
.matchEquals("done", m ->
|
||||
getContext().stop(getSelf())
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#my-stopping-actor
|
||||
|
||||
35
akka-docs/src/test/java/jdocs/actor/SampleActor.java
Normal file
35
akka-docs/src/test/java/jdocs/actor/SampleActor.java
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
//#sample-actor
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
public class SampleActor extends AbstractActor {
|
||||
|
||||
private Receive guarded = receiveBuilder()
|
||||
.match(String.class, s -> s.contains("guard"), s -> {
|
||||
getSender().tell("contains(guard): " + s, getSelf());
|
||||
getContext().unbecome();
|
||||
})
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Double.class, d -> {
|
||||
getSender().tell(d.isNaN() ? 0 : d, getSelf());
|
||||
})
|
||||
.match(Integer.class, i -> {
|
||||
getSender().tell(i * 10, getSelf());
|
||||
})
|
||||
.match(String.class, s -> s.startsWith("guard"), s -> {
|
||||
getSender().tell("startsWith(guard): " + s.toUpperCase(), getSelf());
|
||||
getContext().become(guarded, false);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#sample-actor
|
||||
56
akka-docs/src/test/java/jdocs/actor/SampleActorTest.java
Normal file
56
akka-docs/src/test/java/jdocs/actor/SampleActorTest.java
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SampleActorTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("SampleActorTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSampleActor()
|
||||
{
|
||||
new TestKit(system) {{
|
||||
final ActorRef subject = system.actorOf(Props.create(SampleActor.class), "sample-actor");
|
||||
final ActorRef probeRef = getRef();
|
||||
|
||||
subject.tell(47.11, probeRef);
|
||||
subject.tell("and no guard in the beginning", probeRef);
|
||||
subject.tell("guard is a good thing", probeRef);
|
||||
subject.tell(47.11, probeRef);
|
||||
subject.tell(4711, probeRef);
|
||||
subject.tell("and no guard in the beginning", probeRef);
|
||||
subject.tell(4711, probeRef);
|
||||
subject.tell("and an unmatched message", probeRef);
|
||||
|
||||
expectMsgEquals(47.11);
|
||||
assertTrue(expectMsgClass(String.class).startsWith("startsWith(guard):"));
|
||||
assertTrue(expectMsgClass(String.class).startsWith("contains(guard):"));
|
||||
expectMsgEquals(47110);
|
||||
expectNoMsg();
|
||||
}};
|
||||
}
|
||||
}
|
||||
78
akka-docs/src/test/java/jdocs/actor/SchedulerDocTest.java
Normal file
78
akka-docs/src/test/java/jdocs/actor/SchedulerDocTest.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
//#imports1
|
||||
import akka.actor.Props;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
//#imports1
|
||||
|
||||
//#imports2
|
||||
import akka.actor.Cancellable;
|
||||
//#imports2
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import org.junit.*;
|
||||
|
||||
public class SchedulerDocTest extends AbstractJavaTest {
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource = new AkkaJUnitActorSystemResource("SchedulerDocTest",
|
||||
AkkaSpec.testConf());
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
private ActorRef testActor = system.actorOf(Props.create(MyActor.class));
|
||||
|
||||
@Test
|
||||
public void scheduleOneOffTask() {
|
||||
//#schedule-one-off-message
|
||||
system.scheduler().scheduleOnce(Duration.create(50, TimeUnit.MILLISECONDS),
|
||||
testActor, "foo", system.dispatcher(), null);
|
||||
//#schedule-one-off-message
|
||||
|
||||
//#schedule-one-off-thunk
|
||||
system.scheduler().scheduleOnce(Duration.create(50, TimeUnit.MILLISECONDS),
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
testActor.tell(System.currentTimeMillis(), ActorRef.noSender());
|
||||
}
|
||||
}, system.dispatcher());
|
||||
//#schedule-one-off-thunk
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleRecurringTask() {
|
||||
//#schedule-recurring
|
||||
class Ticker extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("Tick", m -> {
|
||||
// Do someting
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
ActorRef tickActor = system.actorOf(Props.create(Ticker.class, this));
|
||||
|
||||
//This will schedule to send the Tick-message
|
||||
//to the tickActor after 0ms repeating every 50ms
|
||||
Cancellable cancellable = system.scheduler().schedule(Duration.Zero(),
|
||||
Duration.create(50, TimeUnit.MILLISECONDS), tickActor, "Tick",
|
||||
system.dispatcher(), null);
|
||||
|
||||
//This cancels further Ticks to be sent
|
||||
cancellable.cancel();
|
||||
//#schedule-recurring
|
||||
system.stop(tickActor);
|
||||
}
|
||||
}
|
||||
252
akka-docs/src/test/java/jdocs/actor/TypedActorDocTest.java
Normal file
252
akka-docs/src/test/java/jdocs/actor/TypedActorDocTest.java
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.actor;
|
||||
|
||||
//#imports
|
||||
import akka.actor.TypedActor;
|
||||
import akka.actor.*;
|
||||
import akka.japi.*;
|
||||
import akka.dispatch.Futures;
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import akka.routing.RoundRobinGroup;
|
||||
//#imports
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TypedActorDocTest extends AbstractJavaTest {
|
||||
Object someReference = null;
|
||||
ActorSystem system = null;
|
||||
|
||||
static
|
||||
//#typed-actor-iface
|
||||
public interface Squarer {
|
||||
//#typed-actor-iface-methods
|
||||
void squareDontCare(int i); //fire-forget
|
||||
|
||||
Future<Integer> square(int i); //non-blocking send-request-reply
|
||||
|
||||
Option<Integer> squareNowPlease(int i);//blocking send-request-reply
|
||||
|
||||
int squareNow(int i); //blocking send-request-reply
|
||||
//#typed-actor-iface-methods
|
||||
}
|
||||
//#typed-actor-iface
|
||||
|
||||
static
|
||||
//#typed-actor-impl
|
||||
class SquarerImpl implements Squarer {
|
||||
private String name;
|
||||
|
||||
public SquarerImpl() {
|
||||
this.name = "default";
|
||||
}
|
||||
|
||||
public SquarerImpl(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
//#typed-actor-impl-methods
|
||||
|
||||
public void squareDontCare(int i) {
|
||||
int sq = i * i; //Nobody cares :(
|
||||
}
|
||||
|
||||
public Future<Integer> square(int i) {
|
||||
return Futures.successful(i * i);
|
||||
}
|
||||
|
||||
public Option<Integer> squareNowPlease(int i) {
|
||||
return Option.some(i * i);
|
||||
}
|
||||
|
||||
public int squareNow(int i) {
|
||||
return i * i;
|
||||
}
|
||||
//#typed-actor-impl-methods
|
||||
}
|
||||
//#typed-actor-impl
|
||||
|
||||
@Test public void mustGetTheTypedActorExtension() {
|
||||
|
||||
try {
|
||||
//#typed-actor-extension-tools
|
||||
|
||||
//Returns the Typed Actor Extension
|
||||
TypedActorExtension extension =
|
||||
TypedActor.get(system); //system is an instance of ActorSystem
|
||||
|
||||
//Returns whether the reference is a Typed Actor Proxy or not
|
||||
TypedActor.get(system).isTypedActor(someReference);
|
||||
|
||||
//Returns the backing Akka Actor behind an external Typed Actor Proxy
|
||||
TypedActor.get(system).getActorRefFor(someReference);
|
||||
|
||||
//Returns the current ActorContext,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
ActorContext context = TypedActor.context();
|
||||
|
||||
//Returns the external proxy of the current Typed Actor,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
Squarer sq = TypedActor.<Squarer>self();
|
||||
|
||||
//Returns a contextual instance of the Typed Actor Extension
|
||||
//this means that if you create other Typed Actors with this,
|
||||
//they will become children to the current Typed Actor.
|
||||
TypedActor.get(TypedActor.context());
|
||||
|
||||
//#typed-actor-extension-tools
|
||||
} catch (Exception e) {
|
||||
//dun care
|
||||
}
|
||||
}
|
||||
@Test public void createATypedActor() {
|
||||
try {
|
||||
//#typed-actor-create1
|
||||
Squarer mySquarer =
|
||||
TypedActor.get(system).typedActorOf(
|
||||
new TypedProps<SquarerImpl>(Squarer.class, SquarerImpl.class));
|
||||
//#typed-actor-create1
|
||||
//#typed-actor-create2
|
||||
Squarer otherSquarer =
|
||||
TypedActor.get(system).typedActorOf(
|
||||
new TypedProps<SquarerImpl>(Squarer.class,
|
||||
new Creator<SquarerImpl>() {
|
||||
public SquarerImpl create() { return new SquarerImpl("foo"); }
|
||||
}),
|
||||
"name");
|
||||
//#typed-actor-create2
|
||||
|
||||
//#typed-actor-calls
|
||||
//#typed-actor-call-oneway
|
||||
mySquarer.squareDontCare(10);
|
||||
//#typed-actor-call-oneway
|
||||
|
||||
//#typed-actor-call-future
|
||||
Future<Integer> fSquare = mySquarer.square(10); //A Future[Int]
|
||||
//#typed-actor-call-future
|
||||
|
||||
//#typed-actor-call-option
|
||||
Option<Integer> oSquare = mySquarer.squareNowPlease(10); //Option[Int]
|
||||
//#typed-actor-call-option
|
||||
|
||||
//#typed-actor-call-strict
|
||||
int iSquare = mySquarer.squareNow(10); //Int
|
||||
//#typed-actor-call-strict
|
||||
//#typed-actor-calls
|
||||
|
||||
assertEquals(100, Await.result(fSquare,
|
||||
Duration.create(3, TimeUnit.SECONDS)).intValue());
|
||||
|
||||
assertEquals(100, oSquare.get().intValue());
|
||||
|
||||
assertEquals(100, iSquare);
|
||||
|
||||
//#typed-actor-stop
|
||||
TypedActor.get(system).stop(mySquarer);
|
||||
//#typed-actor-stop
|
||||
|
||||
//#typed-actor-poisonpill
|
||||
TypedActor.get(system).poisonPill(otherSquarer);
|
||||
//#typed-actor-poisonpill
|
||||
} catch(Exception e) {
|
||||
//Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void createHierarchies() {
|
||||
try {
|
||||
//#typed-actor-hierarchy
|
||||
Squarer childSquarer =
|
||||
TypedActor.get(TypedActor.context()).
|
||||
typedActorOf(
|
||||
new TypedProps<SquarerImpl>(Squarer.class, SquarerImpl.class)
|
||||
);
|
||||
//Use "childSquarer" as a Squarer
|
||||
//#typed-actor-hierarchy
|
||||
} catch (Exception e) {
|
||||
//dun care
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void proxyAnyActorRef() {
|
||||
try {
|
||||
final ActorRef actorRefToRemoteActor = system.deadLetters();
|
||||
//#typed-actor-remote
|
||||
Squarer typedActor =
|
||||
TypedActor.get(system).
|
||||
typedActorOf(
|
||||
new TypedProps<Squarer>(Squarer.class),
|
||||
actorRefToRemoteActor
|
||||
);
|
||||
//Use "typedActor" as a FooBar
|
||||
//#typed-actor-remote
|
||||
} catch (Exception e) {
|
||||
//dun care
|
||||
}
|
||||
}
|
||||
|
||||
//#typed-router-types
|
||||
interface HasName {
|
||||
String name();
|
||||
}
|
||||
|
||||
class Named implements HasName {
|
||||
private int id = new Random().nextInt(1024);
|
||||
|
||||
@Override public String name() { return "name-" + id; }
|
||||
}
|
||||
//#typed-router-types
|
||||
|
||||
|
||||
@Test public void typedRouterPattern() {
|
||||
try {
|
||||
//#typed-router
|
||||
// prepare routees
|
||||
TypedActorExtension typed = TypedActor.get(system);
|
||||
|
||||
Named named1 =
|
||||
typed.typedActorOf(new TypedProps<Named>(Named.class));
|
||||
|
||||
Named named2 =
|
||||
typed.typedActorOf(new TypedProps<Named>(Named.class));
|
||||
|
||||
List<Named> routees = new ArrayList<Named>();
|
||||
routees.add(named1);
|
||||
routees.add(named2);
|
||||
|
||||
List<String> routeePaths = new ArrayList<String>();
|
||||
routeePaths.add(typed.getActorRefFor(named1).path().toStringWithoutAddress());
|
||||
routeePaths.add(typed.getActorRefFor(named2).path().toStringWithoutAddress());
|
||||
|
||||
// prepare untyped router
|
||||
ActorRef router = system.actorOf(new RoundRobinGroup(routeePaths).props(), "router");
|
||||
|
||||
// prepare typed proxy, forwarding MethodCall messages to `router`
|
||||
Named typedRouter = typed.typedActorOf(new TypedProps<Named>(Named.class), router);
|
||||
|
||||
System.out.println("actor was: " + typedRouter.name()); // name-243
|
||||
System.out.println("actor was: " + typedRouter.name()); // name-614
|
||||
System.out.println("actor was: " + typedRouter.name()); // name-243
|
||||
System.out.println("actor was: " + typedRouter.name()); // name-614
|
||||
|
||||
//#typed-router
|
||||
typed.poisonPill(named1);
|
||||
typed.poisonPill(named2);
|
||||
typed.poisonPill(typedRouter);
|
||||
|
||||
} catch (Exception e) {
|
||||
//dun care
|
||||
}
|
||||
}
|
||||
}
|
||||
136
akka-docs/src/test/java/jdocs/actor/fsm/Buncher.java
Normal file
136
akka-docs/src/test/java/jdocs/actor/fsm/Buncher.java
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor.fsm;
|
||||
|
||||
//#simple-imports
|
||||
import akka.actor.AbstractFSM;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.japi.pf.UnitMatch;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import scala.concurrent.duration.Duration;
|
||||
//#simple-imports
|
||||
|
||||
import static jdocs.actor.fsm.Buncher.Data;
|
||||
import static jdocs.actor.fsm.Buncher.State.*;
|
||||
import static jdocs.actor.fsm.Buncher.State;
|
||||
import static jdocs.actor.fsm.Buncher.Uninitialized.*;
|
||||
import static jdocs.actor.fsm.Events.*;
|
||||
|
||||
//#simple-fsm
|
||||
public class Buncher extends AbstractFSM<State, Data> {
|
||||
{
|
||||
//#fsm-body
|
||||
startWith(Idle, Uninitialized);
|
||||
|
||||
//#when-syntax
|
||||
when(Idle,
|
||||
matchEvent(SetTarget.class, Uninitialized.class,
|
||||
(setTarget, uninitialized) ->
|
||||
stay().using(new Todo(setTarget.getRef(), new LinkedList<>()))));
|
||||
//#when-syntax
|
||||
|
||||
//#transition-elided
|
||||
onTransition(
|
||||
matchState(Active, Idle, () -> {
|
||||
// reuse this matcher
|
||||
final UnitMatch<Data> m = UnitMatch.create(
|
||||
matchData(Todo.class,
|
||||
todo -> todo.getTarget().tell(new Batch(todo.getQueue()), getSelf())));
|
||||
m.match(stateData());
|
||||
}).
|
||||
state(Idle, Active, () -> {/* Do something here */}));
|
||||
//#transition-elided
|
||||
|
||||
when(Active, Duration.create(1, "second"),
|
||||
matchEvent(Arrays.asList(Flush.class, StateTimeout()), Todo.class,
|
||||
(event, todo) -> goTo(Idle).using(todo.copy(new LinkedList<>()))));
|
||||
|
||||
//#unhandled-elided
|
||||
whenUnhandled(
|
||||
matchEvent(Queue.class, Todo.class,
|
||||
(queue, todo) -> goTo(Active).using(todo.addElement(queue.getObj()))).
|
||||
anyEvent((event, state) -> {
|
||||
log().warning("received unhandled request {} in state {}/{}",
|
||||
event, stateName(), state);
|
||||
return stay();
|
||||
}));
|
||||
//#unhandled-elided
|
||||
|
||||
initialize();
|
||||
//#fsm-body
|
||||
}
|
||||
//#simple-fsm
|
||||
|
||||
static
|
||||
//#simple-state
|
||||
// states
|
||||
enum State {
|
||||
Idle, Active
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
// state data
|
||||
interface Data {
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
enum Uninitialized implements Data {
|
||||
Uninitialized
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
final class Todo implements Data {
|
||||
private final ActorRef target;
|
||||
private final List<Object> queue;
|
||||
|
||||
public Todo(ActorRef target, List<Object> queue) {
|
||||
this.target = target;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public ActorRef getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public List<Object> getQueue() {
|
||||
return queue;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Todo{" +
|
||||
"target=" + target +
|
||||
", queue=" + queue +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Todo addElement(Object element) {
|
||||
List<Object> nQueue = new LinkedList<>(queue);
|
||||
nQueue.add(element);
|
||||
return new Todo(this.target, nQueue);
|
||||
}
|
||||
|
||||
public Todo copy(List<Object> queue) {
|
||||
return new Todo(this.target, queue);
|
||||
}
|
||||
|
||||
public Todo copy(ActorRef target) {
|
||||
return new Todo(target, this.queue);
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
//#simple-state
|
||||
//#simple-fsm
|
||||
}
|
||||
//#simple-fsm
|
||||
78
akka-docs/src/test/java/jdocs/actor/fsm/BuncherTest.java
Normal file
78
akka-docs/src/test/java/jdocs/actor/fsm/BuncherTest.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor.fsm;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static jdocs.actor.fsm.Events.Batch;
|
||||
import static jdocs.actor.fsm.Events.Queue;
|
||||
import static jdocs.actor.fsm.Events.SetTarget;
|
||||
import static jdocs.actor.fsm.Events.Flush.Flush;
|
||||
|
||||
//#test-code
|
||||
public class BuncherTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("BuncherTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuncherActorBatchesCorrectly() {
|
||||
new TestKit(system) {{
|
||||
final ActorRef buncher =
|
||||
system.actorOf(Props.create(Buncher.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
buncher.tell(new SetTarget(probe), probe);
|
||||
buncher.tell(new Queue(42), probe);
|
||||
buncher.tell(new Queue(43), probe);
|
||||
LinkedList<Object> list1 = new LinkedList<>();
|
||||
list1.add(42);
|
||||
list1.add(43);
|
||||
expectMsgEquals(new Batch(list1));
|
||||
buncher.tell(new Queue(44), probe);
|
||||
buncher.tell(Flush, probe);
|
||||
buncher.tell(new Queue(45), probe);
|
||||
LinkedList<Object> list2 = new LinkedList<>();
|
||||
list2.add(44);
|
||||
expectMsgEquals(new Batch(list2));
|
||||
LinkedList<Object> list3 = new LinkedList<>();
|
||||
list3.add(45);
|
||||
expectMsgEquals(new Batch(list3));
|
||||
system.stop(buncher);
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuncherActorDoesntBatchUninitialized() {
|
||||
new TestKit(system) {{
|
||||
final ActorRef buncher =
|
||||
system.actorOf(Props.create(Buncher.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
buncher.tell(new Queue(42), probe);
|
||||
expectNoMsg();
|
||||
system.stop(buncher);
|
||||
}};
|
||||
}
|
||||
}
|
||||
//#test-code
|
||||
108
akka-docs/src/test/java/jdocs/actor/fsm/Events.java
Normal file
108
akka-docs/src/test/java/jdocs/actor/fsm/Events.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor.fsm;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import java.util.List;
|
||||
|
||||
public class Events {
|
||||
|
||||
static
|
||||
//#simple-events
|
||||
public final class SetTarget {
|
||||
private final ActorRef ref;
|
||||
|
||||
public SetTarget(ActorRef ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public ActorRef getRef() {
|
||||
return ref;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SetTarget{" +
|
||||
"ref=" + ref +
|
||||
'}';
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public final class Queue {
|
||||
private final Object obj;
|
||||
|
||||
public Queue(Object obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
public Object getObj() {
|
||||
return obj;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Queue{" +
|
||||
"obj=" + obj +
|
||||
'}';
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public final class Batch {
|
||||
private final List<Object> list;
|
||||
|
||||
public Batch(List<Object> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public List<Object> getList() {
|
||||
return list;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Batch batch = (Batch) o;
|
||||
|
||||
return list.equals(batch.list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append( "Batch{list=");
|
||||
list.stream().forEachOrdered(e -> { builder.append(e); builder.append(","); });
|
||||
int len = builder.length();
|
||||
builder.replace(len, len, "}");
|
||||
return builder.toString();
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public enum Flush {
|
||||
Flush
|
||||
}
|
||||
//#simple-events
|
||||
}
|
||||
179
akka-docs/src/test/java/jdocs/actor/fsm/FSMDocTest.java
Normal file
179
akka-docs/src/test/java/jdocs/actor/fsm/FSMDocTest.java
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.actor.fsm;
|
||||
|
||||
import akka.actor.*;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static jdocs.actor.fsm.FSMDocTest.StateType.*;
|
||||
import static jdocs.actor.fsm.FSMDocTest.Messages.*;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
public class FSMDocTest extends AbstractJavaTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FSMDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
public static enum StateType {
|
||||
SomeState,
|
||||
Processing,
|
||||
Idle,
|
||||
Active,
|
||||
Error
|
||||
}
|
||||
|
||||
public static enum Messages {
|
||||
WillDo,
|
||||
Tick
|
||||
}
|
||||
|
||||
public static enum Data {
|
||||
Foo,
|
||||
Bar
|
||||
};
|
||||
|
||||
public static interface X {};
|
||||
|
||||
public static class DummyFSM extends AbstractFSM<StateType, Integer> {
|
||||
Integer newData = 42;
|
||||
//#alt-transition-syntax
|
||||
public void handler(StateType from, StateType to) {
|
||||
// handle transition here
|
||||
}
|
||||
|
||||
//#alt-transition-syntax
|
||||
{
|
||||
//#modifier-syntax
|
||||
when(SomeState, matchAnyEvent((msg, data) -> {
|
||||
return goTo(Processing).using(newData).
|
||||
forMax(Duration.create(5, SECONDS)).replying(WillDo);
|
||||
}));
|
||||
//#modifier-syntax
|
||||
|
||||
//#NullFunction
|
||||
when(SomeState, AbstractFSM.NullFunction());
|
||||
//#NullFunction
|
||||
|
||||
//#transition-syntax
|
||||
onTransition(
|
||||
matchState(Active, Idle, () -> setTimer("timeout",
|
||||
Tick, Duration.create(1, SECONDS), true)).
|
||||
state(Active, null, () -> cancelTimer("timeout")).
|
||||
state(null, Idle, (f, t) -> log().info("entering Idle from " + f)));
|
||||
//#transition-syntax
|
||||
|
||||
//#alt-transition-syntax
|
||||
onTransition(this::handler);
|
||||
//#alt-transition-syntax
|
||||
|
||||
//#stop-syntax
|
||||
when(Error, matchEventEquals("stop", (event, data) -> {
|
||||
// do cleanup ...
|
||||
return stop();
|
||||
}));
|
||||
//#stop-syntax
|
||||
|
||||
//#termination-syntax
|
||||
onTermination(
|
||||
matchStop(Normal(),
|
||||
(state, data) -> {/* Do something here */}).
|
||||
stop(Shutdown(),
|
||||
(state, data) -> {/* Do something here */}).
|
||||
stop(Failure.class,
|
||||
(reason, state, data) -> {/* Do something here */}));
|
||||
//#termination-syntax
|
||||
|
||||
//#unhandled-syntax
|
||||
whenUnhandled(
|
||||
matchEvent(X.class, (x, data) -> {
|
||||
log().info("Received unhandled event: " + x);
|
||||
return stay();
|
||||
}).
|
||||
anyEvent((event, data) -> {
|
||||
log().warning("Received unknown event: " + event);
|
||||
return goTo(Error);
|
||||
}));
|
||||
}
|
||||
//#unhandled-syntax
|
||||
}
|
||||
|
||||
static
|
||||
//#logging-fsm
|
||||
public class MyFSM extends AbstractLoggingFSM<StateType, Data> {
|
||||
//#body-elided
|
||||
//#logging-fsm
|
||||
ActorRef target = null;
|
||||
//#logging-fsm
|
||||
@Override
|
||||
public int logDepth() { return 12; }
|
||||
{
|
||||
onTermination(
|
||||
matchStop(Failure.class, (reason, state, data) -> {
|
||||
String lastEvents = getLog().mkString("\n\t");
|
||||
log().warning("Failure in state " + state + " with data " + data + "\n" +
|
||||
"Events leading up to this point:\n\t" + lastEvents);
|
||||
//#logging-fsm
|
||||
target.tell(reason.cause(), getSelf());
|
||||
target.tell(state, getSelf());
|
||||
target.tell(data, getSelf());
|
||||
target.tell(lastEvents, getSelf());
|
||||
//#logging-fsm
|
||||
})
|
||||
);
|
||||
//...
|
||||
//#logging-fsm
|
||||
startWith(SomeState, Data.Foo);
|
||||
when(SomeState, matchEvent(ActorRef.class, Data.class, (ref, data) -> {
|
||||
target = ref;
|
||||
target.tell("going active", getSelf());
|
||||
return goTo(Active);
|
||||
}));
|
||||
when(Active, matchEventEquals("stop", (event, data) -> {
|
||||
target.tell("stopping", getSelf());
|
||||
return stop(new Failure("This is not the error you're looking for"));
|
||||
}));
|
||||
initialize();
|
||||
//#logging-fsm
|
||||
}
|
||||
//#body-elided
|
||||
}
|
||||
//#logging-fsm
|
||||
|
||||
@Test
|
||||
public void testLoggingFSM()
|
||||
{
|
||||
new TestKit(system) {{
|
||||
final ActorRef logger =
|
||||
system.actorOf(Props.create(MyFSM.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
logger.tell(probe, probe);
|
||||
expectMsgEquals("going active");
|
||||
logger.tell("stop", probe);
|
||||
expectMsgEquals("stopping");
|
||||
expectMsgEquals("This is not the error you're looking for");
|
||||
expectMsgEquals(Active);
|
||||
expectMsgEquals(Data.Foo);
|
||||
String msg = expectMsgClass(String.class);
|
||||
assertTrue(msg.startsWith("LogEntry(SomeState,Foo,Actor[akka://FSMDocTest/system/"));
|
||||
}};
|
||||
}
|
||||
}
|
||||
115
akka-docs/src/test/java/jdocs/agent/AgentDocTest.java
Normal file
115
akka-docs/src/test/java/jdocs/agent/AgentDocTest.java
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.agent;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
//#import-agent
|
||||
import scala.concurrent.ExecutionContext;
|
||||
import akka.agent.Agent;
|
||||
import akka.dispatch.ExecutionContexts;
|
||||
//#import-agent
|
||||
|
||||
//#import-function
|
||||
import akka.dispatch.Mapper;
|
||||
//#import-function
|
||||
|
||||
//#import-future
|
||||
import scala.concurrent.Future;
|
||||
//#import-future
|
||||
|
||||
public class AgentDocTest extends jdocs.AbstractJavaTest {
|
||||
|
||||
private static ExecutionContext ec = ExecutionContexts.global();
|
||||
|
||||
@Test
|
||||
public void createAndRead() throws Exception {
|
||||
//#create
|
||||
ExecutionContext ec = ExecutionContexts.global();
|
||||
Agent<Integer> agent = Agent.create(5, ec);
|
||||
//#create
|
||||
|
||||
//#read-get
|
||||
Integer result = agent.get();
|
||||
//#read-get
|
||||
|
||||
//#read-future
|
||||
Future<Integer> future = agent.future();
|
||||
//#read-future
|
||||
|
||||
assertEquals(result, new Integer(5));
|
||||
assertEquals(Await.result(future, Duration.create(5,"s")), new Integer(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendAndSendOffAndReadAwait() throws Exception {
|
||||
Agent<Integer> agent = Agent.create(5, ec);
|
||||
|
||||
//#send
|
||||
// send a value, enqueues this change
|
||||
// of the value of the Agent
|
||||
agent.send(7);
|
||||
|
||||
// send a Mapper, enqueues this change
|
||||
// to the value of the Agent
|
||||
agent.send(new Mapper<Integer, Integer>() {
|
||||
public Integer apply(Integer i) {
|
||||
return i * 2;
|
||||
}
|
||||
});
|
||||
//#send
|
||||
|
||||
Mapper<Integer, Integer> longRunningOrBlockingFunction = new Mapper<Integer, Integer>() {
|
||||
public Integer apply(Integer i) {
|
||||
return i * 1;
|
||||
}
|
||||
};
|
||||
|
||||
ExecutionContext theExecutionContextToExecuteItIn = ec;
|
||||
//#send-off
|
||||
// sendOff a function
|
||||
agent.sendOff(longRunningOrBlockingFunction,
|
||||
theExecutionContextToExecuteItIn);
|
||||
//#send-off
|
||||
|
||||
assertEquals(Await.result(agent.future(), Duration.create(5,"s")), new Integer(14));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alterAndAlterOff() throws Exception {
|
||||
Agent<Integer> agent = Agent.create(5, ec);
|
||||
|
||||
//#alter
|
||||
// alter a value
|
||||
Future<Integer> f1 = agent.alter(7);
|
||||
|
||||
// alter a function (Mapper)
|
||||
Future<Integer> f2 = agent.alter(new Mapper<Integer, Integer>() {
|
||||
public Integer apply(Integer i) {
|
||||
return i * 2;
|
||||
}
|
||||
});
|
||||
//#alter
|
||||
|
||||
Mapper<Integer, Integer> longRunningOrBlockingFunction = new Mapper<Integer, Integer>() {
|
||||
public Integer apply(Integer i) {
|
||||
return i * 1;
|
||||
}
|
||||
};
|
||||
|
||||
ExecutionContext theExecutionContextToExecuteItIn = ec;
|
||||
//#alter-off
|
||||
// alterOff a function (Mapper)
|
||||
Future<Integer> f3 = agent.alterOff(longRunningOrBlockingFunction,
|
||||
theExecutionContextToExecuteItIn);
|
||||
//#alter-off
|
||||
|
||||
assertEquals(Await.result(f3, Duration.create(5,"s")), new Integer(14));
|
||||
}
|
||||
}
|
||||
54
akka-docs/src/test/java/jdocs/camel/ActivationTestBase.java
Normal file
54
akka-docs/src/test/java/jdocs/camel/ActivationTestBase.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package jdocs.camel;
|
||||
//#CamelActivation
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.camel.Camel;
|
||||
import akka.camel.CamelExtension;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import akka.util.Timeout;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
//#CamelActivation
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ActivationTestBase extends AbstractJavaTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testActivation() {
|
||||
//#CamelActivation
|
||||
|
||||
// ..
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Props props = Props.create(MyConsumer.class);
|
||||
ActorRef producer = system.actorOf(props,"myproducer");
|
||||
Camel camel = CamelExtension.get(system);
|
||||
// get a future reference to the activation of the endpoint of the Consumer Actor
|
||||
Timeout timeout = new Timeout(Duration.create(10, SECONDS));
|
||||
Future<ActorRef> activationFuture = camel.activationFutureFor(producer,
|
||||
timeout, system.dispatcher());
|
||||
//#CamelActivation
|
||||
//#CamelDeactivation
|
||||
// ..
|
||||
system.stop(producer);
|
||||
// get a future reference to the deactivation of the endpoint of the Consumer Actor
|
||||
Future<ActorRef> deactivationFuture = camel.deactivationFutureFor(producer,
|
||||
timeout, system.dispatcher());
|
||||
//#CamelDeactivation
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
public static class MyConsumer extends UntypedConsumerActor {
|
||||
public String getEndpointUri() {
|
||||
return "direct:test";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
}
|
||||
}
|
||||
}
|
||||
34
akka-docs/src/test/java/jdocs/camel/CamelExtensionTest.java
Normal file
34
akka-docs/src/test/java/jdocs/camel/CamelExtensionTest.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package jdocs.camel;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.camel.Camel;
|
||||
import akka.camel.CamelExtension;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.apache.camel.CamelContext;
|
||||
import org.apache.camel.ProducerTemplate;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CamelExtensionTest extends AbstractJavaTest {
|
||||
@Test
|
||||
public void getCamelExtension() {
|
||||
//#CamelExtension
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Camel camel = CamelExtension.get(system);
|
||||
CamelContext camelContext = camel.context();
|
||||
ProducerTemplate producerTemplate = camel.template();
|
||||
//#CamelExtension
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
public void addActiveMQComponent() {
|
||||
//#CamelExtensionAddComponent
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Camel camel = CamelExtension.get(system);
|
||||
CamelContext camelContext = camel.context();
|
||||
// camelContext.addComponent("activemq", ActiveMQComponent.activeMQComponent(
|
||||
// "vm://localhost?broker.persistent=false"));
|
||||
//#CamelExtensionAddComponent
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
}
|
||||
24
akka-docs/src/test/java/jdocs/camel/Consumer1.java
Normal file
24
akka-docs/src/test/java/jdocs/camel/Consumer1.java
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package jdocs.camel;
|
||||
//#Consumer1
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class Consumer1 extends UntypedConsumerActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "file:data/input/actor";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
if (message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
String body = camelMessage.getBodyAs(String.class, getCamelContext());
|
||||
log.info("Received message: {}", body);
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
}
|
||||
//#Consumer1
|
||||
20
akka-docs/src/test/java/jdocs/camel/Consumer2.java
Normal file
20
akka-docs/src/test/java/jdocs/camel/Consumer2.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package jdocs.camel;
|
||||
//#Consumer2
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
|
||||
public class Consumer2 extends UntypedConsumerActor {
|
||||
public String getEndpointUri() {
|
||||
return "jetty:http://localhost:8877/camel/default";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
if (message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
String body = camelMessage.getBodyAs(String.class, getCamelContext());
|
||||
getSender().tell(String.format("Received message: %s",body), getSelf());
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
}
|
||||
//#Consumer2
|
||||
31
akka-docs/src/test/java/jdocs/camel/Consumer3.java
Normal file
31
akka-docs/src/test/java/jdocs/camel/Consumer3.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package jdocs.camel;
|
||||
//#Consumer3
|
||||
import akka.actor.Status;
|
||||
import akka.camel.Ack;
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
|
||||
public class Consumer3 extends UntypedConsumerActor{
|
||||
|
||||
@Override
|
||||
public boolean autoAck() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "jms:queue:test";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
if (message instanceof CamelMessage) {
|
||||
getSender().tell(Ack.getInstance(), getSelf());
|
||||
// on success
|
||||
// ..
|
||||
Exception someException = new Exception("e1");
|
||||
// on failure
|
||||
getSender().tell(new Status.Failure(someException), getSelf());
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
}
|
||||
//#Consumer3
|
||||
32
akka-docs/src/test/java/jdocs/camel/Consumer4.java
Normal file
32
akka-docs/src/test/java/jdocs/camel/Consumer4.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package jdocs.camel;
|
||||
//#Consumer4
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Consumer4 extends UntypedConsumerActor {
|
||||
private final static FiniteDuration timeout =
|
||||
Duration.create(500, TimeUnit.MILLISECONDS);
|
||||
|
||||
@Override
|
||||
public FiniteDuration replyTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "jetty:http://localhost:8877/camel/default";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
if (message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
String body = camelMessage.getBodyAs(String.class, getCamelContext());
|
||||
getSender().tell(String.format("Hello %s",body), getSelf());
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
}
|
||||
//#Consumer4
|
||||
18
akka-docs/src/test/java/jdocs/camel/CustomRouteBuilder.java
Normal file
18
akka-docs/src/test/java/jdocs/camel/CustomRouteBuilder.java
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package jdocs.camel;
|
||||
//#CustomRoute
|
||||
import akka.actor.ActorRef;
|
||||
import akka.camel.internal.component.CamelPath;
|
||||
import org.apache.camel.builder.RouteBuilder;
|
||||
|
||||
public class CustomRouteBuilder extends RouteBuilder{
|
||||
private String uri;
|
||||
|
||||
public CustomRouteBuilder(ActorRef responder) {
|
||||
uri = CamelPath.toUri(responder);
|
||||
}
|
||||
|
||||
public void configure() throws Exception {
|
||||
from("jetty:http://localhost:8877/camel/custom").to(uri);
|
||||
}
|
||||
}
|
||||
//#CustomRoute
|
||||
23
akka-docs/src/test/java/jdocs/camel/CustomRouteTestBase.java
Normal file
23
akka-docs/src/test/java/jdocs/camel/CustomRouteTestBase.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package jdocs.camel;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.camel.Camel;
|
||||
import akka.camel.CamelExtension;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
|
||||
public class CustomRouteTestBase {
|
||||
public void customRoute() throws Exception{
|
||||
//#CustomRoute
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
try {
|
||||
Camel camel = CamelExtension.get(system);
|
||||
ActorRef responder = system.actorOf(Props.create(Responder.class), "TestResponder");
|
||||
camel.context().addRoutes(new CustomRouteBuilder(responder));
|
||||
//#CustomRoute
|
||||
} finally {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package jdocs.camel;
|
||||
//#ErrorThrowingConsumer
|
||||
import akka.actor.Status;
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
import akka.dispatch.Mapper;
|
||||
import org.apache.camel.builder.Builder;
|
||||
import org.apache.camel.model.ProcessorDefinition;
|
||||
import org.apache.camel.model.RouteDefinition;
|
||||
import scala.Option;
|
||||
|
||||
public class ErrorThrowingConsumer extends UntypedConsumerActor{
|
||||
private String uri;
|
||||
|
||||
private static Mapper<RouteDefinition, ProcessorDefinition<?>> mapper =
|
||||
new Mapper<RouteDefinition, ProcessorDefinition<?>>() {
|
||||
public ProcessorDefinition<?> apply(RouteDefinition rd) {
|
||||
// Catch any exception and handle it by returning the exception message
|
||||
// as response
|
||||
return rd.onException(Exception.class).handled(true).
|
||||
transform(Builder.exceptionMessage()).end();
|
||||
}
|
||||
};
|
||||
|
||||
public ErrorThrowingConsumer(String uri){
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public void onReceive(Object message) throws Exception{
|
||||
if (message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
String body = camelMessage.getBodyAs(String.class, getCamelContext());
|
||||
throw new Exception(String.format("error: %s",body));
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mapper<RouteDefinition,
|
||||
ProcessorDefinition<?>> getRouteDefinitionHandler() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRestart(Throwable reason, Option<Object> message) {
|
||||
getSender().tell(new Status.Failure(reason), getSelf());
|
||||
}
|
||||
}
|
||||
//#ErrorThrowingConsumer
|
||||
10
akka-docs/src/test/java/jdocs/camel/FirstProducer.java
Normal file
10
akka-docs/src/test/java/jdocs/camel/FirstProducer.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package jdocs.camel;
|
||||
|
||||
//#Producer1
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
public class FirstProducer extends UntypedProducerActor {
|
||||
public String getEndpointUri() {
|
||||
return "http://localhost:8080/news";
|
||||
}
|
||||
}
|
||||
//#Producer1
|
||||
24
akka-docs/src/test/java/jdocs/camel/Forwarder.java
Normal file
24
akka-docs/src/test/java/jdocs/camel/Forwarder.java
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package jdocs.camel;
|
||||
//#RouteResponse
|
||||
import akka.actor.ActorRef;
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
|
||||
public class Forwarder extends UntypedProducerActor {
|
||||
private String uri;
|
||||
private ActorRef target;
|
||||
|
||||
public Forwarder(String uri, ActorRef target) {
|
||||
this.uri = uri;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteResponse(Object message) {
|
||||
target.forward(message, getContext());
|
||||
}
|
||||
}
|
||||
//#RouteResponse
|
||||
15
akka-docs/src/test/java/jdocs/camel/MyActor.java
Normal file
15
akka-docs/src/test/java/jdocs/camel/MyActor.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package jdocs.camel;
|
||||
//#ProducerTemplate
|
||||
import akka.actor.UntypedAbstractActor;
|
||||
import akka.camel.Camel;
|
||||
import akka.camel.CamelExtension;
|
||||
import org.apache.camel.ProducerTemplate;
|
||||
|
||||
public class MyActor extends UntypedAbstractActor {
|
||||
public void onReceive(Object message) {
|
||||
Camel camel = CamelExtension.get(getContext().getSystem());
|
||||
ProducerTemplate template = camel.template();
|
||||
template.sendBody("direct:news", message);
|
||||
}
|
||||
}
|
||||
//#ProducerTemplate
|
||||
31
akka-docs/src/test/java/jdocs/camel/MyEndpoint.java
Normal file
31
akka-docs/src/test/java/jdocs/camel/MyEndpoint.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package jdocs.camel;
|
||||
|
||||
//#Consumer-mina
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
|
||||
public class MyEndpoint extends UntypedConsumerActor{
|
||||
private String uri;
|
||||
|
||||
public String getEndpointUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof CamelMessage) {
|
||||
/* ... */
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
|
||||
// Extra constructor to change the default uri,
|
||||
// for instance to "jetty:http://localhost:8877/example"
|
||||
public MyEndpoint(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public MyEndpoint() {
|
||||
this.uri = "mina2:tcp://localhost:6200?textline=true";
|
||||
}
|
||||
}
|
||||
//#Consumer-mina
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package jdocs.camel;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
|
||||
public class OnRouteResponseTestBase {
|
||||
|
||||
public void onRouteResponse(){
|
||||
//#RouteResponse
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Props receiverProps = Props.create(ResponseReceiver.class);
|
||||
final ActorRef receiver = system.actorOf(receiverProps,"responseReceiver");
|
||||
ActorRef forwardResponse = system.actorOf(Props.create(
|
||||
Forwarder.class, "http://localhost:8080/news/akka", receiver));
|
||||
// the Forwarder sends out a request to the web page and forwards the response to
|
||||
// the ResponseReceiver
|
||||
forwardResponse.tell("some request", ActorRef.noSender());
|
||||
//#RouteResponse
|
||||
system.stop(receiver);
|
||||
system.stop(forwardResponse);
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
}
|
||||
20
akka-docs/src/test/java/jdocs/camel/OnewaySender.java
Normal file
20
akka-docs/src/test/java/jdocs/camel/OnewaySender.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package jdocs.camel;
|
||||
//#Oneway
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
|
||||
public class OnewaySender extends UntypedProducerActor{
|
||||
private String uri;
|
||||
|
||||
public OnewaySender(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
public String getEndpointUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOneway() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//#Oneway
|
||||
10
akka-docs/src/test/java/jdocs/camel/Orders.java
Normal file
10
akka-docs/src/test/java/jdocs/camel/Orders.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package jdocs.camel;
|
||||
//#Producer
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
|
||||
public class Orders extends UntypedProducerActor {
|
||||
public String getEndpointUri() {
|
||||
return "jms:queue:Orders";
|
||||
}
|
||||
}
|
||||
//#Producer
|
||||
10
akka-docs/src/test/java/jdocs/camel/Producer1.java
Normal file
10
akka-docs/src/test/java/jdocs/camel/Producer1.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package jdocs.camel;
|
||||
//#Producer1
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
|
||||
public class Producer1 extends UntypedProducerActor {
|
||||
public String getEndpointUri() {
|
||||
return "http://localhost:8080/news";
|
||||
}
|
||||
}
|
||||
//#Producer1
|
||||
51
akka-docs/src/test/java/jdocs/camel/ProducerTestBase.java
Normal file
51
akka-docs/src/test/java/jdocs/camel/ProducerTestBase.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package jdocs.camel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.pattern.PatternsCS;
|
||||
|
||||
public class ProducerTestBase {
|
||||
public void tellJmsProducer() {
|
||||
//#TellProducer
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Props props = Props.create(Orders.class);
|
||||
ActorRef producer = system.actorOf(props, "jmsproducer");
|
||||
producer.tell("<order amount=\"100\" currency=\"PLN\" itemId=\"12345\"/>",
|
||||
ActorRef.noSender());
|
||||
//#TellProducer
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void askProducer() {
|
||||
//#AskProducer
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Props props = Props.create(FirstProducer.class);
|
||||
ActorRef producer = system.actorOf(props,"myproducer");
|
||||
CompletionStage<Object> future = PatternsCS.ask(producer, "some request", 1000);
|
||||
//#AskProducer
|
||||
system.stop(producer);
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
public void correlate(){
|
||||
//#Correlate
|
||||
ActorSystem system = ActorSystem.create("some-system");
|
||||
Props props = Props.create(Orders.class);
|
||||
ActorRef producer = system.actorOf(props,"jmsproducer");
|
||||
Map<String,Object> headers = new HashMap<String, Object>();
|
||||
headers.put(CamelMessage.MessageExchangeId(),"123");
|
||||
producer.tell(new CamelMessage("<order amount=\"100\" currency=\"PLN\" " +
|
||||
"itemId=\"12345\"/>",headers), ActorRef.noSender());
|
||||
//#Correlate
|
||||
system.stop(producer);
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
}
|
||||
20
akka-docs/src/test/java/jdocs/camel/RequestBodyActor.java
Normal file
20
akka-docs/src/test/java/jdocs/camel/RequestBodyActor.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package jdocs.camel;
|
||||
//#RequestProducerTemplate
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.camel.Camel;
|
||||
import akka.camel.CamelExtension;
|
||||
import org.apache.camel.ProducerTemplate;
|
||||
|
||||
public class RequestBodyActor extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchAny(message -> {
|
||||
Camel camel = CamelExtension.get(getContext().getSystem());
|
||||
ProducerTemplate template = camel.template();
|
||||
getSender().tell(template.requestBody("direct:news", message), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#RequestProducerTemplate
|
||||
26
akka-docs/src/test/java/jdocs/camel/Responder.java
Normal file
26
akka-docs/src/test/java/jdocs/camel/Responder.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package jdocs.camel;
|
||||
//#CustomRoute
|
||||
import akka.actor.UntypedAbstractActor;
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.dispatch.Mapper;
|
||||
|
||||
public class Responder extends UntypedAbstractActor{
|
||||
|
||||
public void onReceive(Object message) {
|
||||
if (message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
getSender().tell(createResponse(camelMessage), getSelf());
|
||||
} else
|
||||
unhandled(message);
|
||||
}
|
||||
|
||||
private CamelMessage createResponse(CamelMessage msg) {
|
||||
return msg.mapBody(new Mapper<String,String>() {
|
||||
@Override
|
||||
public String apply(String body) {
|
||||
return String.format("received %s", body);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//#CustomRoute
|
||||
13
akka-docs/src/test/java/jdocs/camel/ResponseReceiver.java
Normal file
13
akka-docs/src/test/java/jdocs/camel/ResponseReceiver.java
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package jdocs.camel;
|
||||
//#RouteResponse
|
||||
import akka.actor.UntypedAbstractActor;
|
||||
import akka.camel.CamelMessage;
|
||||
|
||||
public class ResponseReceiver extends UntypedAbstractActor{
|
||||
public void onReceive(Object message) {
|
||||
if(message instanceof CamelMessage) {
|
||||
// do something with the forwarded response
|
||||
}
|
||||
}
|
||||
}
|
||||
//#RouteResponse
|
||||
37
akka-docs/src/test/java/jdocs/camel/Transformer.java
Normal file
37
akka-docs/src/test/java/jdocs/camel/Transformer.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package jdocs.camel;
|
||||
//#TransformOutgoingMessage
|
||||
import akka.camel.CamelMessage;
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
import akka.dispatch.Mapper;
|
||||
|
||||
public class Transformer extends UntypedProducerActor{
|
||||
private String uri;
|
||||
|
||||
public Transformer(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
private CamelMessage upperCase(CamelMessage msg) {
|
||||
return msg.mapBody(new Mapper<String,String>() {
|
||||
@Override
|
||||
public String apply(String body) {
|
||||
return body.toUpperCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransformOutgoingMessage(Object message) {
|
||||
if(message instanceof CamelMessage) {
|
||||
CamelMessage camelMessage = (CamelMessage) message;
|
||||
return upperCase(camelMessage);
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
//#TransformOutgoingMessage
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.circuitbreaker;
|
||||
|
||||
//#imports1
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.LoggingAdapter;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.pattern.CircuitBreaker;
|
||||
import akka.event.Logging;
|
||||
|
||||
import static akka.pattern.PatternsCS.pipe;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
//#imports1
|
||||
|
||||
//#circuit-breaker-initialization
|
||||
public class DangerousJavaActor extends AbstractActor {
|
||||
|
||||
private final CircuitBreaker breaker;
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
|
||||
|
||||
public DangerousJavaActor() {
|
||||
this.breaker = new CircuitBreaker(
|
||||
getContext().dispatcher(), getContext().system().scheduler(),
|
||||
5, Duration.create(10, "s"), Duration.create(1, "m"))
|
||||
.onOpen(this::notifyMeOnOpen);
|
||||
}
|
||||
|
||||
public void notifyMeOnOpen() {
|
||||
log.warning("My CircuitBreaker is now open, and will not close for one minute");
|
||||
}
|
||||
//#circuit-breaker-initialization
|
||||
|
||||
//#circuit-breaker-usage
|
||||
public String dangerousCall() {
|
||||
return "This really isn't that dangerous of a call after all";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder().
|
||||
match(String.class, "is my middle name"::equals, m -> pipe(
|
||||
breaker.callWithCircuitBreakerCS(() ->
|
||||
CompletableFuture.supplyAsync(this::dangerousCall)
|
||||
), getContext().dispatcher()
|
||||
).to(sender()))
|
||||
.match(String.class, "block for me"::equals, m -> {
|
||||
sender().tell(breaker
|
||||
.callWithSyncCircuitBreaker(this::dangerousCall), self());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#circuit-breaker-usage
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package jdocs.circuitbreaker;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.pattern.CircuitBreaker;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class EvenNoFailureJavaExample extends AbstractActor {
|
||||
//#even-no-as-failure
|
||||
private final CircuitBreaker breaker;
|
||||
|
||||
public EvenNoFailureJavaExample() {
|
||||
this.breaker = new CircuitBreaker(
|
||||
getContext().dispatcher(), getContext().system().scheduler(),
|
||||
5, Duration.create(10, "s"), Duration.create(1, "m"));
|
||||
}
|
||||
|
||||
public int luckyNumber() {
|
||||
BiFunction<Optional<Integer>, Optional<Throwable>, Boolean> evenNoAsFailure =
|
||||
(result, err) -> (result.isPresent() && result.get() % 2 == 0);
|
||||
|
||||
// this will return 8888 and increase failure count at the same time
|
||||
return this.breaker.callWithSyncCircuitBreaker(() -> 8888, evenNoAsFailure);
|
||||
}
|
||||
|
||||
//#even-no-as-failure
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.circuitbreaker;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ReceiveTimeout;
|
||||
import akka.actor.AbstractActor.Receive;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.pattern.CircuitBreaker;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
public class TellPatternJavaActor extends AbstractActor {
|
||||
|
||||
private final ActorRef target;
|
||||
private final CircuitBreaker breaker;
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
|
||||
|
||||
public TellPatternJavaActor(ActorRef targetActor) {
|
||||
this.target = targetActor;
|
||||
this.breaker = new CircuitBreaker(
|
||||
getContext().dispatcher(), getContext().system().scheduler(),
|
||||
5, Duration.create(10, "s"), Duration.create(1, "m"))
|
||||
.onOpen(new Runnable() {
|
||||
public void run() {
|
||||
notifyMeOnOpen();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyMeOnOpen() {
|
||||
log.warning("My CircuitBreaker is now open, and will not close for one minute");
|
||||
}
|
||||
|
||||
//#circuit-breaker-tell-pattern
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, payload -> "call".equals(payload) && breaker.isClosed(), payload ->
|
||||
target.tell("message", self())
|
||||
)
|
||||
.matchEquals("response", payload ->
|
||||
breaker.succeed()
|
||||
)
|
||||
.match(Throwable.class, t ->
|
||||
breaker.fail()
|
||||
)
|
||||
.match(ReceiveTimeout.class, t ->
|
||||
breaker.fail()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
//#circuit-breaker-tell-pattern
|
||||
|
||||
}
|
||||
42
akka-docs/src/test/java/jdocs/cluster/ClusterDocTest.java
Normal file
42
akka-docs/src/test/java/jdocs/cluster/ClusterDocTest.java
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.cluster;
|
||||
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.cluster.Cluster;
|
||||
|
||||
|
||||
public class ClusterDocTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("ClusterDocTest",
|
||||
ConfigFactory.parseString(scala.docs.cluster.ClusterDocSpec.config()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateLeave() {
|
||||
//#leave
|
||||
final Cluster cluster = Cluster.get(system);
|
||||
cluster.leave(cluster.selfAddress());
|
||||
//#leave
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
36
akka-docs/src/test/java/jdocs/cluster/FactorialBackend.java
Normal file
36
akka-docs/src/test/java/jdocs/cluster/FactorialBackend.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import static akka.pattern.PatternsCS.pipe;
|
||||
|
||||
//#backend
|
||||
public class FactorialBackend extends AbstractActor {
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Integer.class, n -> {
|
||||
|
||||
CompletableFuture<FactorialResult> result =
|
||||
CompletableFuture.supplyAsync(() -> factorial(n))
|
||||
.thenApply((factorial) -> new FactorialResult(n, factorial));
|
||||
|
||||
pipe(result, getContext().dispatcher()).to(getSender());
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
BigInteger factorial(int n) {
|
||||
BigInteger acc = BigInteger.ONE;
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
acc = acc.multiply(BigInteger.valueOf(i));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
//#backend
|
||||
|
||||
103
akka-docs/src/test/java/jdocs/cluster/FactorialFrontend.java
Normal file
103
akka-docs/src/test/java/jdocs/cluster/FactorialFrontend.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.metrics.AdaptiveLoadBalancingGroup;
|
||||
import akka.cluster.metrics.AdaptiveLoadBalancingPool;
|
||||
import akka.cluster.metrics.HeapMetricsSelector;
|
||||
import akka.cluster.metrics.SystemLoadAverageMetricsSelector;
|
||||
import akka.cluster.routing.ClusterRouterGroup;
|
||||
import akka.cluster.routing.ClusterRouterGroupSettings;
|
||||
import akka.cluster.routing.ClusterRouterPool;
|
||||
import akka.cluster.routing.ClusterRouterPoolSettings;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ReceiveTimeout;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.routing.FromConfig;
|
||||
|
||||
//#frontend
|
||||
public class FactorialFrontend extends AbstractActor {
|
||||
final int upToN;
|
||||
final boolean repeat;
|
||||
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
ActorRef backend = getContext().actorOf(FromConfig.getInstance().props(),
|
||||
"factorialBackendRouter");
|
||||
|
||||
public FactorialFrontend(int upToN, boolean repeat) {
|
||||
this.upToN = upToN;
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
sendJobs();
|
||||
getContext().setReceiveTimeout(Duration.create(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(FactorialResult.class, result -> {
|
||||
if (result.n == upToN) {
|
||||
log.debug("{}! = {}", result.n, result.factorial);
|
||||
if (repeat)
|
||||
sendJobs();
|
||||
else
|
||||
getContext().stop(getSelf());
|
||||
}
|
||||
})
|
||||
.match(ReceiveTimeout.class, x -> {
|
||||
log.info("Timeout");
|
||||
sendJobs();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void sendJobs() {
|
||||
log.info("Starting batch of factorials up to [{}]", upToN);
|
||||
for (int n = 1; n <= upToN; n++) {
|
||||
backend.tell(n, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#frontend
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class FactorialFrontend2 extends AbstractActor {
|
||||
//#router-lookup-in-code
|
||||
int totalInstances = 100;
|
||||
Iterable<String> routeesPaths = Arrays.asList("/user/factorialBackend", "");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "backend";
|
||||
ActorRef backend = getContext().actorOf(
|
||||
new ClusterRouterGroup(new AdaptiveLoadBalancingGroup(
|
||||
HeapMetricsSelector.getInstance(), Collections.<String> emptyList()),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "factorialBackendRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class FactorialFrontend3 extends AbstractActor {
|
||||
//#router-deploy-in-code
|
||||
int totalInstances = 100;
|
||||
int maxInstancesPerNode = 3;
|
||||
boolean allowLocalRoutees = false;
|
||||
String useRole = "backend";
|
||||
ActorRef backend = getContext().actorOf(
|
||||
new ClusterRouterPool(new AdaptiveLoadBalancingPool(
|
||||
SystemLoadAverageMetricsSelector.getInstance(), 0),
|
||||
new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
|
||||
allowLocalRoutees, useRole)).props(Props
|
||||
.create(FactorialBackend.class)), "factorialBackendRouter3");
|
||||
//#router-deploy-in-code
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.Cluster;
|
||||
|
||||
public class FactorialFrontendMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int upToN = 200;
|
||||
|
||||
final Config config = ConfigFactory.parseString(
|
||||
"akka.cluster.roles = [frontend]").withFallback(
|
||||
ConfigFactory.load("factorial"));
|
||||
|
||||
final ActorSystem system = ActorSystem.create("ClusterSystem", config);
|
||||
system.log().info(
|
||||
"Factorials will start when 2 backend members in the cluster.");
|
||||
//#registerOnUp
|
||||
Cluster.get(system).registerOnMemberUp(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
system.actorOf(Props.create(FactorialFrontend.class, upToN, true),
|
||||
"factorialFrontend");
|
||||
}
|
||||
});
|
||||
//#registerOnUp
|
||||
|
||||
//#registerOnRemoved
|
||||
Cluster.get(system).registerOnMemberRemoved(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// exit JVM when ActorSystem has been terminated
|
||||
final Runnable exit = new Runnable() {
|
||||
@Override public void run() {
|
||||
System.exit(0);
|
||||
}
|
||||
};
|
||||
system.registerOnTermination(exit);
|
||||
|
||||
// shut down ActorSystem
|
||||
system.terminate();
|
||||
|
||||
// In case ActorSystem shutdown takes longer than 10 seconds,
|
||||
// exit the JVM forcefully anyway.
|
||||
// We must spawn a separate thread to not block current thread,
|
||||
// since that would have blocked the shutdown of the ActorSystem.
|
||||
new Thread() {
|
||||
@Override public void run(){
|
||||
try {
|
||||
Await.ready(system.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
|
||||
} catch (Exception e) {
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
//#registerOnRemoved
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
15
akka-docs/src/test/java/jdocs/cluster/FactorialResult.java
Normal file
15
akka-docs/src/test/java/jdocs/cluster/FactorialResult.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FactorialResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final int n;
|
||||
public final BigInteger factorial;
|
||||
|
||||
FactorialResult(int n, BigInteger factorial) {
|
||||
this.n = n;
|
||||
this.factorial = factorial;
|
||||
}
|
||||
}
|
||||
69
akka-docs/src/test/java/jdocs/cluster/MetricsListener.java
Normal file
69
akka-docs/src/test/java/jdocs/cluster/MetricsListener.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
//#metrics-listener
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.metrics.ClusterMetricsChanged;
|
||||
import akka.cluster.metrics.NodeMetrics;
|
||||
import akka.cluster.metrics.StandardMetrics;
|
||||
import akka.cluster.metrics.StandardMetrics.HeapMemory;
|
||||
import akka.cluster.metrics.StandardMetrics.Cpu;
|
||||
import akka.cluster.metrics.ClusterMetricsExtension;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class MetricsListener extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
ClusterMetricsExtension extension = ClusterMetricsExtension.get(getContext().getSystem());
|
||||
|
||||
|
||||
// Subscribe unto ClusterMetricsEvent events.
|
||||
@Override
|
||||
public void preStart() {
|
||||
extension.subscribe(getSelf());
|
||||
}
|
||||
|
||||
// Unsubscribe from ClusterMetricsEvent events.
|
||||
@Override
|
||||
public void postStop() {
|
||||
extension.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(ClusterMetricsChanged.class, clusterMetrics -> {
|
||||
for (NodeMetrics nodeMetrics : clusterMetrics.getNodeMetrics()) {
|
||||
if (nodeMetrics.address().equals(cluster.selfAddress())) {
|
||||
logHeap(nodeMetrics);
|
||||
logCpu(nodeMetrics);
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(CurrentClusterState.class, message -> {
|
||||
// Ignore.
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void logHeap(NodeMetrics nodeMetrics) {
|
||||
HeapMemory heap = StandardMetrics.extractHeapMemory(nodeMetrics);
|
||||
if (heap != null) {
|
||||
log.info("Used heap: {} MB", ((double) heap.used()) / 1024 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
void logCpu(NodeMetrics nodeMetrics) {
|
||||
Cpu cpu = StandardMetrics.extractCpu(nodeMetrics);
|
||||
if (cpu != null && cpu.systemLoadAverage().isDefined()) {
|
||||
log.info("Load: {} ({} processors)", cpu.systemLoadAverage().get(),
|
||||
cpu.processors());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#metrics-listener
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.MemberRemoved;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class SimpleClusterListener extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes
|
||||
@Override
|
||||
public void preStart() {
|
||||
//#subscribe
|
||||
cluster.subscribe(getSelf(), ClusterEvent.initialStateAsEvents(),
|
||||
MemberEvent.class, UnreachableMember.class);
|
||||
//#subscribe
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(MemberUp.class, mUp -> {
|
||||
log.info("Member is Up: {}", mUp.member());
|
||||
})
|
||||
.match(UnreachableMember.class, mUnreachable -> {
|
||||
log.info("Member detected as unreachable: {}", mUnreachable.member());
|
||||
})
|
||||
.match(MemberRemoved.class, mRemoved -> {
|
||||
log.info("Member is Removed: {}", mRemoved.member());
|
||||
})
|
||||
.match(MemberEvent.class, message -> {
|
||||
// ignore
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.MemberRemoved;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class SimpleClusterListener2 extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes
|
||||
@Override
|
||||
public void preStart() {
|
||||
//#subscribe
|
||||
cluster.subscribe(getSelf(), MemberEvent.class, UnreachableMember.class);
|
||||
//#subscribe
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
log.info("Current members: {}", state.members());
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
log.info("Member is Up: {}", mUp.member());
|
||||
})
|
||||
.match(UnreachableMember.class, mUnreachable -> {
|
||||
log.info("Member detected as unreachable: {}", mUnreachable.member());
|
||||
})
|
||||
.match(MemberRemoved.class, mRemoved -> {
|
||||
log.info("Member is Removed: {}", mRemoved.member());
|
||||
})
|
||||
.match(MemberEvent.class, event -> {
|
||||
// ignore
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
55
akka-docs/src/test/java/jdocs/cluster/StatsAggregator.java
Normal file
55
akka-docs/src/test/java/jdocs/cluster/StatsAggregator.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jdocs.cluster.StatsMessages.JobFailed;
|
||||
import jdocs.cluster.StatsMessages.StatsResult;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ReceiveTimeout;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#aggregator
|
||||
public class StatsAggregator extends AbstractActor {
|
||||
|
||||
final int expectedResults;
|
||||
final ActorRef replyTo;
|
||||
final List<Integer> results = new ArrayList<Integer>();
|
||||
|
||||
public StatsAggregator(int expectedResults, ActorRef replyTo) {
|
||||
this.expectedResults = expectedResults;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
getContext().setReceiveTimeout(Duration.create(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Integer.class, wordCount -> {
|
||||
results.add(wordCount);
|
||||
if (results.size() == expectedResults) {
|
||||
int sum = 0;
|
||||
for (int c : results) {
|
||||
sum += c;
|
||||
}
|
||||
double meanWordLength = ((double) sum) / results.size();
|
||||
replyTo.tell(new StatsResult(meanWordLength), getSelf());
|
||||
getContext().stop(getSelf());
|
||||
}
|
||||
})
|
||||
.match(ReceiveTimeout.class, x -> {
|
||||
replyTo.tell(new JobFailed("Service unavailable, try again later"),
|
||||
getSelf());
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#aggregator
|
||||
55
akka-docs/src/test/java/jdocs/cluster/StatsMessages.java
Normal file
55
akka-docs/src/test/java/jdocs/cluster/StatsMessages.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
//#messages
|
||||
public interface StatsMessages {
|
||||
|
||||
public static class StatsJob implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public StatsJob(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StatsResult implements Serializable {
|
||||
private final double meanWordLength;
|
||||
|
||||
public StatsResult(double meanWordLength) {
|
||||
this.meanWordLength = meanWordLength;
|
||||
}
|
||||
|
||||
public double getMeanWordLength() {
|
||||
return meanWordLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "meanWordLength: " + meanWordLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JobFailed implements Serializable {
|
||||
private final String reason;
|
||||
|
||||
public JobFailed(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobFailed(" + reason + ")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#messages
|
||||
100
akka-docs/src/test/java/jdocs/cluster/StatsSampleClient.java
Normal file
100
akka-docs/src/test/java/jdocs/cluster/StatsSampleClient.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jdocs.cluster.StatsMessages.JobFailed;
|
||||
import jdocs.cluster.StatsMessages.StatsJob;
|
||||
import jdocs.cluster.StatsMessages.StatsResult;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import akka.actor.ActorSelection;
|
||||
import akka.actor.Address;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.cluster.ClusterEvent.ReachableMember;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.ReachabilityEvent;
|
||||
import akka.cluster.Member;
|
||||
import akka.cluster.MemberStatus;
|
||||
|
||||
public class StatsSampleClient extends AbstractActor {
|
||||
|
||||
final String servicePath;
|
||||
final Cancellable tickTask;
|
||||
final Set<Address> nodes = new HashSet<Address>();
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
public StatsSampleClient(String servicePath) {
|
||||
this.servicePath = servicePath;
|
||||
FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS);
|
||||
tickTask = getContext()
|
||||
.getSystem()
|
||||
.scheduler()
|
||||
.schedule(interval, interval, getSelf(), "tick",
|
||||
getContext().dispatcher(), null);
|
||||
}
|
||||
|
||||
//subscribe to cluster changes, MemberEvent
|
||||
@Override
|
||||
public void preStart() {
|
||||
cluster.subscribe(getSelf(), MemberEvent.class, ReachabilityEvent.class);
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
tickTask.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("tick", x -> !nodes.isEmpty(), x -> {
|
||||
// just pick any one
|
||||
List<Address> nodesList = new ArrayList<Address>(nodes);
|
||||
Address address = nodesList.get(ThreadLocalRandom.current().nextInt(
|
||||
nodesList.size()));
|
||||
ActorSelection service = getContext().actorSelection(address + servicePath);
|
||||
service.tell(new StatsJob("this is the text that will be analyzed"),
|
||||
getSelf());
|
||||
})
|
||||
.match(StatsResult.class, System.out::println)
|
||||
.match(JobFailed.class, System.out::println)
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
nodes.clear();
|
||||
for (Member member : state.getMembers()) {
|
||||
if (member.hasRole("compute") && member.status().equals(MemberStatus.up())) {
|
||||
nodes.add(member.address());
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
if (mUp.member().hasRole("compute"))
|
||||
nodes.add(mUp.member().address());
|
||||
})
|
||||
.match(MemberEvent.class, event -> {
|
||||
nodes.remove(event.member().address());
|
||||
})
|
||||
.match(UnreachableMember.class, unreachable -> {
|
||||
nodes.remove(unreachable.member().address());
|
||||
})
|
||||
.match(ReachableMember.class, reachable -> {
|
||||
if (reachable.member().hasRole("compute"))
|
||||
nodes.add(reachable.member().address());
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
|
||||
public class StatsSampleOneMasterClientMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// note that client is not a compute node, role not defined
|
||||
ActorSystem system = ActorSystem.create("ClusterSystem",
|
||||
ConfigFactory.load("stats2"));
|
||||
system.actorOf(Props.create(StatsSampleClient.class, "/user/statsServiceProxy"),
|
||||
"client");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.singleton.ClusterSingletonManager;
|
||||
import akka.cluster.singleton.ClusterSingletonManagerSettings;
|
||||
import akka.cluster.singleton.ClusterSingletonProxy;
|
||||
import akka.cluster.singleton.ClusterSingletonProxySettings;
|
||||
|
||||
public class StatsSampleOneMasterMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
startup(new String[] { "2551", "2552", "0" });
|
||||
StatsSampleOneMasterClientMain.main(new String[0]);
|
||||
} else {
|
||||
startup(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void startup(String[] ports) {
|
||||
for (String port : ports) {
|
||||
// Override the configuration of the port
|
||||
Config config = ConfigFactory
|
||||
.parseString("akka.remote.netty.tcp.port=" + port)
|
||||
.withFallback(
|
||||
ConfigFactory.parseString("akka.cluster.roles = [compute]"))
|
||||
.withFallback(ConfigFactory.load("stats2"));
|
||||
|
||||
ActorSystem system = ActorSystem.create("ClusterSystem", config);
|
||||
|
||||
//#create-singleton-manager
|
||||
ClusterSingletonManagerSettings settings = ClusterSingletonManagerSettings.create(system)
|
||||
.withRole("compute");
|
||||
system.actorOf(ClusterSingletonManager.props(
|
||||
Props.create(StatsService.class), PoisonPill.getInstance(), settings),
|
||||
"statsService");
|
||||
//#create-singleton-manager
|
||||
|
||||
//#singleton-proxy
|
||||
ClusterSingletonProxySettings proxySettings =
|
||||
ClusterSingletonProxySettings.create(system).withRole("compute");
|
||||
system.actorOf(ClusterSingletonProxy.props("/user/statsService",
|
||||
proxySettings), "statsServiceProxy");
|
||||
//#singleton-proxy
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
80
akka-docs/src/test/java/jdocs/cluster/StatsService.java
Normal file
80
akka-docs/src/test/java/jdocs/cluster/StatsService.java
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.cluster.routing.ClusterRouterGroup;
|
||||
import akka.cluster.routing.ClusterRouterGroupSettings;
|
||||
import akka.cluster.routing.ClusterRouterPool;
|
||||
import akka.cluster.routing.ClusterRouterPoolSettings;
|
||||
import akka.routing.ConsistentHashingGroup;
|
||||
import akka.routing.ConsistentHashingPool;
|
||||
import jdocs.cluster.StatsMessages.StatsJob;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
|
||||
import akka.routing.FromConfig;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
//#service
|
||||
public class StatsService extends AbstractActor {
|
||||
|
||||
// This router is used both with lookup and deploy of routees. If you
|
||||
// have a router with only lookup of routees you can use Props.empty()
|
||||
// instead of Props.create(StatsWorker.class).
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
FromConfig.getInstance().props(Props.create(StatsWorker.class)),
|
||||
"workerRouter");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(StatsJob.class, job -> !job.getText().isEmpty(), job -> {
|
||||
String[] words = job.getText().split(" ");
|
||||
ActorRef replyTo = getSender();
|
||||
|
||||
// create actor that collects replies from workers
|
||||
ActorRef aggregator = getContext().actorOf(
|
||||
Props.create(StatsAggregator.class, words.length, replyTo));
|
||||
|
||||
// send each word to a worker
|
||||
for (String word : words) {
|
||||
workerRouter.tell(new ConsistentHashableEnvelope(word, word),
|
||||
aggregator);
|
||||
}
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#service
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class StatsService2 extends AbstractActor {
|
||||
//#router-lookup-in-code
|
||||
int totalInstances = 100;
|
||||
Iterable<String> routeesPaths = Collections
|
||||
.singletonList("/user/statsWorker");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "compute";
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
new ClusterRouterGroup(new ConsistentHashingGroup(routeesPaths),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "workerRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class StatsService3 extends AbstractActor {
|
||||
//#router-deploy-in-code
|
||||
int totalInstances = 100;
|
||||
int maxInstancesPerNode = 3;
|
||||
boolean allowLocalRoutees = false;
|
||||
String useRole = "compute";
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
new ClusterRouterPool(new ConsistentHashingPool(0),
|
||||
new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
|
||||
allowLocalRoutees, useRole)).props(Props
|
||||
.create(StatsWorker.class)), "workerRouter3");
|
||||
//#router-deploy-in-code
|
||||
}
|
||||
|
||||
27
akka-docs/src/test/java/jdocs/cluster/StatsWorker.java
Normal file
27
akka-docs/src/test/java/jdocs/cluster/StatsWorker.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#worker
|
||||
public class StatsWorker extends AbstractActor {
|
||||
|
||||
Map<String, Integer> cache = new HashMap<String, Integer>();
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, word -> {
|
||||
Integer length = cache.get(word);
|
||||
if (length == null) {
|
||||
length = word.length();
|
||||
cache.put(word, length);
|
||||
}
|
||||
getSender().tell(length, getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#worker
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import static jdocs.cluster.TransformationMessages.BACKEND_REGISTRATION;
|
||||
import jdocs.cluster.TransformationMessages.TransformationJob;
|
||||
import jdocs.cluster.TransformationMessages.TransformationResult;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.Member;
|
||||
import akka.cluster.MemberStatus;
|
||||
|
||||
//#backend
|
||||
public class TransformationBackend extends AbstractActor {
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes, MemberUp
|
||||
@Override
|
||||
public void preStart() {
|
||||
cluster.subscribe(getSelf(), MemberUp.class);
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(TransformationJob.class, job -> {
|
||||
getSender().tell(new TransformationResult(job.getText().toUpperCase()),
|
||||
getSelf());
|
||||
})
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
for (Member member : state.getMembers()) {
|
||||
if (member.status().equals(MemberStatus.up())) {
|
||||
register(member);
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
register(mUp.member());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void register(Member member) {
|
||||
if (member.hasRole("frontend"))
|
||||
getContext().actorSelection(member.address() + "/user/frontend").tell(
|
||||
BACKEND_REGISTRATION, getSelf());
|
||||
}
|
||||
}
|
||||
//#backend
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import static jdocs.cluster.TransformationMessages.BACKEND_REGISTRATION;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdocs.cluster.TransformationMessages.JobFailed;
|
||||
import jdocs.cluster.TransformationMessages.TransformationJob;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Terminated;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#frontend
|
||||
public class TransformationFrontend extends AbstractActor {
|
||||
|
||||
List<ActorRef> backends = new ArrayList<ActorRef>();
|
||||
int jobCounter = 0;
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(TransformationJob.class, job -> backends.isEmpty(), job -> {
|
||||
getSender().tell(
|
||||
new JobFailed("Service unavailable, try again later", job),
|
||||
getSender());
|
||||
})
|
||||
.match(TransformationJob.class, job -> {
|
||||
jobCounter++;
|
||||
backends.get(jobCounter % backends.size())
|
||||
.forward(job, getContext());
|
||||
})
|
||||
.matchEquals(BACKEND_REGISTRATION, x -> {
|
||||
getContext().watch(getSender());
|
||||
backends.add(getSender());
|
||||
})
|
||||
.match(Terminated.class, terminated -> {
|
||||
backends.remove(terminated.getActor());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#frontend
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
//#messages
|
||||
public interface TransformationMessages {
|
||||
|
||||
public static class TransformationJob implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public TransformationJob(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TransformationResult implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public TransformationResult(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TransformationResult(" + text + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class JobFailed implements Serializable {
|
||||
private final String reason;
|
||||
private final TransformationJob job;
|
||||
|
||||
public JobFailed(String reason, TransformationJob job) {
|
||||
this.reason = reason;
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public TransformationJob getJob() {
|
||||
return job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobFailed(" + reason + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static final String BACKEND_REGISTRATION = "BackendRegistration";
|
||||
|
||||
}
|
||||
//#messages
|
||||
33
akka-docs/src/test/java/jdocs/config/ConfigDoc.java
Normal file
33
akka-docs/src/test/java/jdocs/config/ConfigDoc.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.config;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import com.typesafe.config.*;
|
||||
|
||||
public class ConfigDoc {
|
||||
public ActorSystem createConfiguredSystem() {
|
||||
//#java-custom-config
|
||||
// make a Config with just your special setting
|
||||
Config myConfig =
|
||||
ConfigFactory.parseString("something=somethingElse");
|
||||
// load the normal config stack (system props,
|
||||
// then application.conf, then reference.conf)
|
||||
Config regularConfig =
|
||||
ConfigFactory.load();
|
||||
// override regular stack with myConfig
|
||||
Config combined =
|
||||
myConfig.withFallback(regularConfig);
|
||||
// put the result in between the overrides
|
||||
// (system props) and defaults again
|
||||
Config complete =
|
||||
ConfigFactory.load(combined);
|
||||
// create ActorSystem
|
||||
ActorSystem system =
|
||||
ActorSystem.create("myname", complete);
|
||||
//#java-custom-config
|
||||
return system;
|
||||
}
|
||||
}
|
||||
|
||||
101
akka-docs/src/test/java/jdocs/ddata/DataBot.java
Normal file
101
akka-docs/src/test/java/jdocs/ddata/DataBot.java
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata;
|
||||
|
||||
//#data-bot
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import scala.concurrent.duration.Duration;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ddata.DistributedData;
|
||||
import akka.cluster.ddata.Key;
|
||||
import akka.cluster.ddata.ORSet;
|
||||
import akka.cluster.ddata.ORSetKey;
|
||||
import akka.cluster.ddata.Replicator;
|
||||
import akka.cluster.ddata.Replicator.Changed;
|
||||
import akka.cluster.ddata.Replicator.Subscribe;
|
||||
import akka.cluster.ddata.Replicator.Update;
|
||||
import akka.cluster.ddata.Replicator.UpdateResponse;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class DataBot extends AbstractActor {
|
||||
|
||||
private static final String TICK = "tick";
|
||||
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
private final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
private final Cluster node = Cluster.get(getContext().getSystem());
|
||||
|
||||
private final Cancellable tickTask = getContext().getSystem().scheduler().schedule(
|
||||
Duration.create(5, SECONDS), Duration.create(5, SECONDS), getSelf(), TICK,
|
||||
getContext().dispatcher(), getSelf());
|
||||
|
||||
private final Key<ORSet<String>> dataKey = ORSetKey.create("key");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, a -> a.equals(TICK), a -> receiveTick())
|
||||
.match(Changed.class, c -> c.key().equals(dataKey), c -> receiveChanged((Changed<ORSet<String>>) c))
|
||||
.match(UpdateResponse.class, r -> receiveUpdateResoponse())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private void receiveTick() {
|
||||
String s = String.valueOf((char) ThreadLocalRandom.current().nextInt(97, 123));
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
// add
|
||||
log.info("Adding: {}", s);
|
||||
Update<ORSet<String>> update = new Update<>(
|
||||
dataKey,
|
||||
ORSet.create(),
|
||||
Replicator.writeLocal(),
|
||||
curr -> curr.add(node, s));
|
||||
replicator.tell(update, getSelf());
|
||||
} else {
|
||||
// remove
|
||||
log.info("Removing: {}", s);
|
||||
Update<ORSet<String>> update = new Update<>(
|
||||
dataKey,
|
||||
ORSet.create(),
|
||||
Replicator.writeLocal(),
|
||||
curr -> curr.remove(node, s));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void receiveChanged(Changed<ORSet<String>> c) {
|
||||
ORSet<String> data = c.dataValue();
|
||||
log.info("Current elements: {}", data.getElements());
|
||||
}
|
||||
|
||||
private void receiveUpdateResoponse() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
Subscribe<ORSet<String>> subscribe = new Subscribe<>(dataKey, getSelf());
|
||||
replicator.tell(subscribe, ActorRef.noSender());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStop(){
|
||||
tickTask.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
//#data-bot
|
||||
422
akka-docs/src/test/java/jdocs/ddata/DistributedDataDocTest.java
Normal file
422
akka-docs/src/test/java/jdocs/ddata/DistributedDataDocTest.java
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import docs.ddata.DistributedDataDocSpec;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ddata.*;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
|
||||
import static akka.cluster.ddata.Replicator.*;
|
||||
|
||||
@SuppressWarnings({"unchecked", "unused"})
|
||||
public class DistributedDataDocTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("DistributedDataDocTest",
|
||||
ConfigFactory.parseString(DistributedDataDocSpec.config()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
static
|
||||
//#update
|
||||
class DemonstrateUpdate extends AbstractActor {
|
||||
final Cluster node = Cluster.get(getContext().getSystem());
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<GSet<String>> set1Key = GSetKey.create("set1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
final Key<Flag> activeFlagKey = FlagKey.create("active");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder b = receiveBuilder();
|
||||
|
||||
b.matchEquals("demonstrate update", msg -> {
|
||||
replicator.tell(new Replicator.Update<PNCounter>(counter1Key, PNCounter.create(),
|
||||
Replicator.writeLocal(), curr -> curr.increment(node, 1)), getSelf());
|
||||
|
||||
final WriteConsistency writeTo3 = new WriteTo(3, Duration.create(1, SECONDS));
|
||||
replicator.tell(new Replicator.Update<GSet<String>>(set1Key, GSet.create(),
|
||||
writeTo3, curr -> curr.add("hello")), getSelf());
|
||||
|
||||
final WriteConsistency writeMajority =
|
||||
new WriteMajority(Duration.create(5, SECONDS));
|
||||
replicator.tell(new Replicator.Update<ORSet<String>>(set2Key, ORSet.create(),
|
||||
writeMajority, curr -> curr.add(node, "hello")), getSelf());
|
||||
|
||||
final WriteConsistency writeAll = new WriteAll(Duration.create(5, SECONDS));
|
||||
replicator.tell(new Replicator.Update<Flag>(activeFlagKey, Flag.create(),
|
||||
writeAll, curr -> curr.switchOn()), getSelf());
|
||||
});
|
||||
//#update
|
||||
|
||||
//#update-response1
|
||||
b.match(UpdateSuccess.class, a -> a.key().equals(counter1Key), a -> {
|
||||
// ok
|
||||
});
|
||||
//#update-response1
|
||||
|
||||
//#update-response2
|
||||
b.match(UpdateSuccess.class, a -> a.key().equals(set1Key), a -> {
|
||||
// ok
|
||||
})
|
||||
.match(UpdateTimeout.class, a -> a.key().equals(set1Key), a -> {
|
||||
// write to 3 nodes failed within 1.second
|
||||
});
|
||||
//#update-response2
|
||||
|
||||
//#update
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
//#update
|
||||
|
||||
static
|
||||
//#update-request-context
|
||||
class DemonstrateUpdateWithRequestContext extends AbstractActor {
|
||||
final Cluster node = Cluster.get(getContext().getSystem());
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final WriteConsistency writeTwo = new WriteTo(2, Duration.create(3, SECONDS));
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, a -> a.equals("increment"), a -> {
|
||||
// incoming command to increase the counter
|
||||
Optional<Object> reqContext = Optional.of(getSender());
|
||||
Replicator.Update<PNCounter> upd = new Replicator.Update<PNCounter>(counter1Key,
|
||||
PNCounter.create(), writeTwo, reqContext, curr -> curr.increment(node, 1));
|
||||
replicator.tell(upd, getSelf());
|
||||
})
|
||||
|
||||
.match(UpdateSuccess.class, a -> a.key().equals(counter1Key), a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell("ack", getSelf());
|
||||
})
|
||||
|
||||
.match(UpdateTimeout.class, a -> a.key().equals(counter1Key), a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell("nack", getSelf());
|
||||
})
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#update-request-context
|
||||
|
||||
static
|
||||
//#get
|
||||
class DemonstrateGet extends AbstractActor {
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<GSet<String>> set1Key = GSetKey.create("set1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
final Key<Flag> activeFlagKey = FlagKey.create("active");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder b = receiveBuilder();
|
||||
|
||||
b.matchEquals("demonstrate get", msg -> {
|
||||
|
||||
replicator.tell(new Replicator.Get<PNCounter>(counter1Key,
|
||||
Replicator.readLocal()), getSelf());
|
||||
|
||||
final ReadConsistency readFrom3 = new ReadFrom(3, Duration.create(1, SECONDS));
|
||||
replicator.tell(new Replicator.Get<GSet<String>>(set1Key,
|
||||
readFrom3), getSelf());
|
||||
|
||||
final ReadConsistency readMajority = new ReadMajority(Duration.create(5, SECONDS));
|
||||
replicator.tell(new Replicator.Get<ORSet<String>>(set2Key,
|
||||
readMajority), getSelf());
|
||||
|
||||
final ReadConsistency readAll = new ReadAll(Duration.create(5, SECONDS));
|
||||
replicator.tell(new Replicator.Get<Flag>(activeFlagKey,
|
||||
readAll), getSelf());
|
||||
|
||||
});
|
||||
//#get
|
||||
|
||||
//#get-response1
|
||||
b.match(GetSuccess.class, a -> a.key().equals(counter1Key), a -> {
|
||||
GetSuccess<PNCounter> g = a;
|
||||
BigInteger value = g.dataValue().getValue();
|
||||
}).
|
||||
match(NotFound.class, a -> a.key().equals(counter1Key), a -> {
|
||||
// key counter1 does not exist
|
||||
});
|
||||
//#get-response1
|
||||
|
||||
//#get-response2
|
||||
b.match(GetSuccess.class, a -> a.key().equals(set1Key), a -> {
|
||||
GetSuccess<GSet<String>> g = a;
|
||||
Set<String> value = g.dataValue().getElements();
|
||||
}).
|
||||
match(GetFailure.class, a -> a.key().equals(set1Key), a -> {
|
||||
// read from 3 nodes failed within 1.second
|
||||
}).
|
||||
match(NotFound.class, a -> a.key().equals(set1Key), a -> {
|
||||
// key set1 does not exist
|
||||
});
|
||||
//#get-response2
|
||||
|
||||
//#get
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
//#get
|
||||
|
||||
static
|
||||
//#get-request-context
|
||||
class DemonstrateGetWithRequestContext extends AbstractActor {
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final ReadConsistency readTwo = new ReadFrom(2, Duration.create(3, SECONDS));
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, a -> a.equals("get-count"), a -> {
|
||||
// incoming request to retrieve current value of the counter
|
||||
Optional<Object> reqContext = Optional.of(getSender());
|
||||
replicator.tell(new Replicator.Get<PNCounter>(counter1Key,
|
||||
readTwo), getSelf());
|
||||
})
|
||||
|
||||
.match(GetSuccess.class, a -> a.key().equals(counter1Key), a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
GetSuccess<PNCounter> g = a;
|
||||
long value = g.dataValue().getValue().longValue();
|
||||
replyTo.tell(value, getSelf());
|
||||
})
|
||||
|
||||
.match(GetFailure.class, a -> a.key().equals(counter1Key), a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell(-1L, getSelf());
|
||||
})
|
||||
|
||||
.match(NotFound.class, a -> a.key().equals(counter1Key), a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell(0L, getSelf());
|
||||
})
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#get-request-context
|
||||
|
||||
static
|
||||
//#subscribe
|
||||
class DemonstrateSubscribe extends AbstractActor {
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
BigInteger currentValue = BigInteger.valueOf(0);
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Changed.class, a -> a.key().equals(counter1Key), a -> {
|
||||
Changed<PNCounter> g = a;
|
||||
currentValue = g.dataValue().getValue();
|
||||
})
|
||||
.match(String.class, a -> a.equals("get-count"), a -> {
|
||||
// incoming request to retrieve current value of the counter
|
||||
getSender().tell(currentValue, getSender());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
// subscribe to changes of the Counter1Key value
|
||||
replicator.tell(new Subscribe<PNCounter>(counter1Key, getSelf()), ActorRef.noSender());
|
||||
}
|
||||
|
||||
}
|
||||
//#subscribe
|
||||
|
||||
static
|
||||
//#delete
|
||||
class DemonstrateDelete extends AbstractActor {
|
||||
final ActorRef replicator =
|
||||
DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("demonstrate delete", msg -> {
|
||||
|
||||
replicator.tell(new Delete<PNCounter>(counter1Key,
|
||||
Replicator.writeLocal()), getSelf());
|
||||
|
||||
final WriteConsistency writeMajority =
|
||||
new WriteMajority(Duration.create(5, SECONDS));
|
||||
replicator.tell(new Delete<PNCounter>(counter1Key,
|
||||
writeMajority), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#delete
|
||||
|
||||
public void demonstratePNCounter() {
|
||||
//#pncounter
|
||||
final Cluster node = Cluster.get(system);
|
||||
final PNCounter c0 = PNCounter.create();
|
||||
final PNCounter c1 = c0.increment(node, 1);
|
||||
final PNCounter c2 = c1.increment(node, 7);
|
||||
final PNCounter c3 = c2.decrement(node, 2);
|
||||
System.out.println(c3.value()); // 6
|
||||
//#pncounter
|
||||
}
|
||||
|
||||
public void demonstratePNCounterMap() {
|
||||
//#pncountermap
|
||||
final Cluster node = Cluster.get(system);
|
||||
final PNCounterMap<String> m0 = PNCounterMap.create();
|
||||
final PNCounterMap<String> m1 = m0.increment(node, "a", 7);
|
||||
final PNCounterMap<String> m2 = m1.decrement(node, "a", 2);
|
||||
final PNCounterMap<String> m3 = m2.increment(node, "b", 1);
|
||||
System.out.println(m3.get("a")); // 5
|
||||
System.out.println(m3.getEntries());
|
||||
//#pncountermap
|
||||
}
|
||||
|
||||
public void demonstrateGSet() {
|
||||
//#gset
|
||||
final GSet<String> s0 = GSet.create();
|
||||
final GSet<String> s1 = s0.add("a");
|
||||
final GSet<String> s2 = s1.add("b").add("c");
|
||||
if (s2.contains("a"))
|
||||
System.out.println(s2.getElements()); // a, b, c
|
||||
//#gset
|
||||
}
|
||||
|
||||
public void demonstrateORSet() {
|
||||
//#orset
|
||||
final Cluster node = Cluster.get(system);
|
||||
final ORSet<String> s0 = ORSet.create();
|
||||
final ORSet<String> s1 = s0.add(node, "a");
|
||||
final ORSet<String> s2 = s1.add(node, "b");
|
||||
final ORSet<String> s3 = s2.remove(node, "a");
|
||||
System.out.println(s3.getElements()); // b
|
||||
//#orset
|
||||
}
|
||||
|
||||
public void demonstrateORMultiMap() {
|
||||
//#ormultimap
|
||||
final Cluster node = Cluster.get(system);
|
||||
final ORMultiMap<String, Integer> m0 = ORMultiMap.create();
|
||||
final ORMultiMap<String, Integer> m1 = m0.put(node, "a",
|
||||
new HashSet<>(Arrays.asList(1, 2, 3)));
|
||||
final ORMultiMap<String, Integer> m2 = m1.addBinding(node, "a", 4);
|
||||
final ORMultiMap<String, Integer> m3 = m2.removeBinding(node, "a", 2);
|
||||
final ORMultiMap<String, Integer> m4 = m3.addBinding(node, "b", 1);
|
||||
System.out.println(m4.getEntries());
|
||||
//#ormultimap
|
||||
}
|
||||
|
||||
public void demonstrateFlag() {
|
||||
//#flag
|
||||
final Flag f0 = Flag.create();
|
||||
final Flag f1 = f0.switchOn();
|
||||
System.out.println(f1.enabled());
|
||||
//#flag
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateLWWRegister() {
|
||||
//#lwwregister
|
||||
final Cluster node = Cluster.get(system);
|
||||
final LWWRegister<String> r1 = LWWRegister.create(node, "Hello");
|
||||
final LWWRegister<String> r2 = r1.withValue(node, "Hi");
|
||||
System.out.println(r1.value() + " by " + r1.updatedBy() + " at " + r1.timestamp());
|
||||
//#lwwregister
|
||||
assertEquals("Hi", r2.value());
|
||||
}
|
||||
|
||||
static
|
||||
//#lwwregister-custom-clock
|
||||
class Record {
|
||||
public final int version;
|
||||
public final String name;
|
||||
public final String address;
|
||||
|
||||
public Record(int version, String name, String address) {
|
||||
this.version = version;
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
//#lwwregister-custom-clock
|
||||
|
||||
public void demonstrateLWWRegisterWithCustomClock() {
|
||||
//#lwwregister-custom-clock
|
||||
|
||||
final Cluster node = Cluster.get(system);
|
||||
final LWWRegister.Clock<Record> recordClock = new LWWRegister.Clock<Record>() {
|
||||
@Override
|
||||
public long apply(long currentTimestamp, Record value) {
|
||||
return value.version;
|
||||
}
|
||||
};
|
||||
|
||||
final Record record1 = new Record(1, "Alice", "Union Square");
|
||||
final LWWRegister<Record> r1 = LWWRegister.create(node, record1);
|
||||
|
||||
final Record record2 = new Record(2, "Alice", "Madison Square");
|
||||
final LWWRegister<Record> r2 = LWWRegister.create(node, record2);
|
||||
|
||||
final LWWRegister<Record> r3 = r1.merge(r2);
|
||||
System.out.println(r3.value());
|
||||
//#lwwregister-custom-clock
|
||||
|
||||
assertEquals("Madison Square", r3.value().address);
|
||||
}
|
||||
|
||||
}
|
||||
273
akka-docs/src/test/java/jdocs/ddata/ShoppingCart.java
Normal file
273
akka-docs/src/test/java/jdocs/ddata/ShoppingCart.java
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
package jdocs.ddata;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ddata.DistributedData;
|
||||
import akka.cluster.ddata.Key;
|
||||
import akka.cluster.ddata.LWWMap;
|
||||
import akka.cluster.ddata.LWWMapKey;
|
||||
import akka.cluster.ddata.Replicator;
|
||||
import akka.cluster.ddata.Replicator.GetFailure;
|
||||
import akka.cluster.ddata.Replicator.GetResponse;
|
||||
import akka.cluster.ddata.Replicator.GetSuccess;
|
||||
import akka.cluster.ddata.Replicator.NotFound;
|
||||
import akka.cluster.ddata.Replicator.ReadConsistency;
|
||||
import akka.cluster.ddata.Replicator.ReadMajority;
|
||||
import akka.cluster.ddata.Replicator.Update;
|
||||
import akka.cluster.ddata.Replicator.UpdateFailure;
|
||||
import akka.cluster.ddata.Replicator.UpdateSuccess;
|
||||
import akka.cluster.ddata.Replicator.UpdateTimeout;
|
||||
import akka.cluster.ddata.Replicator.WriteConsistency;
|
||||
import akka.cluster.ddata.Replicator.WriteMajority;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ShoppingCart extends AbstractActor {
|
||||
|
||||
//#read-write-majority
|
||||
private final WriteConsistency writeMajority =
|
||||
new WriteMajority(Duration.create(3, SECONDS));
|
||||
private final static ReadConsistency readMajority =
|
||||
new ReadMajority(Duration.create(3, SECONDS));
|
||||
//#read-write-majority
|
||||
|
||||
public static final String GET_CART = "getCart";
|
||||
|
||||
public static class AddItem {
|
||||
public final LineItem item;
|
||||
|
||||
public AddItem(LineItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RemoveItem {
|
||||
public final String productId;
|
||||
|
||||
public RemoveItem(String productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Cart {
|
||||
public final Set<LineItem> items;
|
||||
|
||||
public Cart(Set<LineItem> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LineItem implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final String productId;
|
||||
public final String title;
|
||||
public final int quantity;
|
||||
|
||||
public LineItem(String productId, String title, int quantity) {
|
||||
this.productId = productId;
|
||||
this.title = title;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((productId == null) ? 0 : productId.hashCode());
|
||||
result = prime * result + quantity;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
LineItem other = (LineItem) obj;
|
||||
if (productId == null) {
|
||||
if (other.productId != null)
|
||||
return false;
|
||||
} else if (!productId.equals(other.productId))
|
||||
return false;
|
||||
if (quantity != other.quantity)
|
||||
return false;
|
||||
if (title == null) {
|
||||
if (other.title != null)
|
||||
return false;
|
||||
} else if (!title.equals(other.title))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LineItem [productId=" + productId + ", title=" + title + ", quantity=" + quantity + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Props props(String userId) {
|
||||
return Props.create(ShoppingCart.class, userId);
|
||||
}
|
||||
|
||||
private final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
private final Cluster node = Cluster.get(getContext().getSystem());
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String userId;
|
||||
private final Key<LWWMap<String, LineItem>> dataKey;
|
||||
|
||||
public ShoppingCart(String userId) {
|
||||
this.userId = userId;
|
||||
this.dataKey = LWWMapKey.create("cart-" + userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return matchGetCart()
|
||||
.orElse(matchAddItem())
|
||||
.orElse(matchRemoveItem())
|
||||
.orElse(matchOther());
|
||||
}
|
||||
|
||||
//#get-cart
|
||||
private Receive matchGetCart() {
|
||||
return receiveBuilder()
|
||||
.matchEquals(GET_CART, s -> receiveGetCart())
|
||||
.match(GetSuccess.class, this::isResponseToGetCart,
|
||||
g -> receiveGetSuccess((GetSuccess<LWWMap<String, LineItem>>) g))
|
||||
.match(NotFound.class, this::isResponseToGetCart,
|
||||
n -> receiveNotFound((NotFound<LWWMap<String, LineItem>>) n))
|
||||
.match(GetFailure.class, this::isResponseToGetCart,
|
||||
f -> receiveGetFailure((GetFailure<LWWMap<String, LineItem>>) f))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void receiveGetCart() {
|
||||
Optional<Object> ctx = Optional.of(getSender());
|
||||
replicator.tell(new Replicator.Get<LWWMap<String, LineItem>>(dataKey, readMajority, ctx),
|
||||
getSelf());
|
||||
}
|
||||
|
||||
private boolean isResponseToGetCart(GetResponse<?> response) {
|
||||
return response.key().equals(dataKey) &&
|
||||
(response.getRequest().orElse(null) instanceof ActorRef);
|
||||
}
|
||||
|
||||
private void receiveGetSuccess(GetSuccess<LWWMap<String, LineItem>> g) {
|
||||
Set<LineItem> items = new HashSet<>(g.dataValue().getEntries().values());
|
||||
ActorRef replyTo = (ActorRef) g.getRequest().get();
|
||||
replyTo.tell(new Cart(items), getSelf());
|
||||
}
|
||||
|
||||
private void receiveNotFound(NotFound<LWWMap<String, LineItem>> n) {
|
||||
ActorRef replyTo = (ActorRef) n.getRequest().get();
|
||||
replyTo.tell(new Cart(new HashSet<>()), getSelf());
|
||||
}
|
||||
|
||||
private void receiveGetFailure(GetFailure<LWWMap<String, LineItem>> f) {
|
||||
// ReadMajority failure, try again with local read
|
||||
Optional<Object> ctx = Optional.of(getSender());
|
||||
replicator.tell(new Replicator.Get<LWWMap<String, LineItem>>(dataKey, Replicator.readLocal(),
|
||||
ctx), getSelf());
|
||||
}
|
||||
//#get-cart
|
||||
|
||||
//#add-item
|
||||
private Receive matchAddItem() {
|
||||
return receiveBuilder()
|
||||
.match(AddItem.class, this::receiveAddItem)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void receiveAddItem(AddItem add) {
|
||||
Update<LWWMap<String, LineItem>> update = new Update<>(dataKey, LWWMap.create(), writeMajority,
|
||||
cart -> updateCart(cart, add.item));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
|
||||
//#add-item
|
||||
|
||||
private LWWMap<String, LineItem> updateCart(LWWMap<String, LineItem> data, LineItem item) {
|
||||
if (data.contains(item.productId)) {
|
||||
LineItem existingItem = data.get(item.productId).get();
|
||||
int newQuantity = existingItem.quantity + item.quantity;
|
||||
LineItem newItem = new LineItem(item.productId, item.title, newQuantity);
|
||||
return data.put(node, item.productId, newItem);
|
||||
} else {
|
||||
return data.put(node, item.productId, item);
|
||||
}
|
||||
}
|
||||
|
||||
private Receive matchRemoveItem() {
|
||||
return receiveBuilder()
|
||||
.match(RemoveItem.class, this::receiveRemoveItem)
|
||||
.match(GetSuccess.class, this::isResponseToRemoveItem,
|
||||
g -> receiveRemoveItemGetSuccess((GetSuccess<LWWMap<String, LineItem>>) g))
|
||||
.match(GetFailure.class, this::isResponseToRemoveItem,
|
||||
f -> receiveRemoveItemGetFailure((GetFailure<LWWMap<String, LineItem>>) f))
|
||||
.match(NotFound.class, this::isResponseToRemoveItem, n -> {/* nothing to remove */})
|
||||
.build();
|
||||
}
|
||||
|
||||
//#remove-item
|
||||
private void receiveRemoveItem(RemoveItem rm) {
|
||||
// Try to fetch latest from a majority of nodes first, since ORMap
|
||||
// remove must have seen the item to be able to remove it.
|
||||
Optional<Object> ctx = Optional.of(rm);
|
||||
replicator.tell(new Replicator.Get<LWWMap<String, LineItem>>(dataKey, readMajority, ctx),
|
||||
getSelf());
|
||||
}
|
||||
|
||||
private void receiveRemoveItemGetSuccess(GetSuccess<LWWMap<String, LineItem>> g) {
|
||||
RemoveItem rm = (RemoveItem) g.getRequest().get();
|
||||
removeItem(rm.productId);
|
||||
}
|
||||
|
||||
|
||||
private void receiveRemoveItemGetFailure(GetFailure<LWWMap<String, LineItem>> f) {
|
||||
// ReadMajority failed, fall back to best effort local value
|
||||
RemoveItem rm = (RemoveItem) f.getRequest().get();
|
||||
removeItem(rm.productId);
|
||||
}
|
||||
|
||||
private void removeItem(String productId) {
|
||||
Update<LWWMap<String, LineItem>> update = new Update<>(dataKey, LWWMap.create(), writeMajority,
|
||||
cart -> cart.remove(node, productId));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
|
||||
private boolean isResponseToRemoveItem(GetResponse<?> response) {
|
||||
return response.key().equals(dataKey) &&
|
||||
(response.getRequest().orElse(null) instanceof RemoveItem);
|
||||
}
|
||||
//#remove-item
|
||||
|
||||
private Receive matchOther() {
|
||||
return receiveBuilder()
|
||||
.match(UpdateSuccess.class, u -> {
|
||||
// ok
|
||||
})
|
||||
.match(UpdateTimeout.class, t -> {
|
||||
// will eventually be replicated
|
||||
})
|
||||
.match(UpdateFailure.class, f -> {
|
||||
throw new IllegalStateException("Unexpected failure: " + f);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
48
akka-docs/src/test/java/jdocs/ddata/TwoPhaseSet.java
Normal file
48
akka-docs/src/test/java/jdocs/ddata/TwoPhaseSet.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import akka.cluster.ddata.AbstractReplicatedData;
|
||||
import akka.cluster.ddata.GSet;
|
||||
|
||||
//#twophaseset
|
||||
public class TwoPhaseSet extends AbstractReplicatedData<TwoPhaseSet> {
|
||||
|
||||
public final GSet<String> adds;
|
||||
public final GSet<String> removals;
|
||||
|
||||
public TwoPhaseSet(GSet<String> adds, GSet<String> removals) {
|
||||
this.adds = adds;
|
||||
this.removals = removals;
|
||||
}
|
||||
|
||||
public static TwoPhaseSet create() {
|
||||
return new TwoPhaseSet(GSet.create(), GSet.create());
|
||||
}
|
||||
|
||||
public TwoPhaseSet add(String element) {
|
||||
return new TwoPhaseSet(adds.add(element), removals);
|
||||
}
|
||||
|
||||
public TwoPhaseSet remove(String element) {
|
||||
return new TwoPhaseSet(adds, removals.add(element));
|
||||
}
|
||||
|
||||
public Set<String> getElements() {
|
||||
Set<String> result = new HashSet<>(adds.getElements());
|
||||
result.removeAll(removals.getElements());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoPhaseSet mergeData(TwoPhaseSet that) {
|
||||
return new TwoPhaseSet(this.adds.merge(that.adds),
|
||||
this.removals.merge(that.removals));
|
||||
}
|
||||
}
|
||||
//#twophaseset
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
//#serializer
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages.TwoPhaseSet.Builder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import akka.actor.ExtendedActorSystem;
|
||||
import akka.cluster.ddata.GSet;
|
||||
import akka.cluster.ddata.protobuf.AbstractSerializationSupport;
|
||||
|
||||
public class TwoPhaseSetSerializer extends AbstractSerializationSupport {
|
||||
|
||||
private final ExtendedActorSystem system;
|
||||
|
||||
public TwoPhaseSetSerializer(ExtendedActorSystem system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedActorSystem system() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeManifest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int identifier() {
|
||||
return 99998;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return twoPhaseSetToProto((TwoPhaseSet) obj).toByteArray();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(bytes);
|
||||
}
|
||||
|
||||
protected TwoPhaseSetMessages.TwoPhaseSet twoPhaseSetToProto(TwoPhaseSet twoPhaseSet) {
|
||||
Builder b = TwoPhaseSetMessages.TwoPhaseSet.newBuilder();
|
||||
ArrayList<String> adds = new ArrayList<>(twoPhaseSet.adds.getElements());
|
||||
if (!adds.isEmpty()) {
|
||||
Collections.sort(adds);
|
||||
b.addAllAdds(adds);
|
||||
}
|
||||
ArrayList<String> removals = new ArrayList<>(twoPhaseSet.removals.getElements());
|
||||
if (!removals.isEmpty()) {
|
||||
Collections.sort(removals);
|
||||
b.addAllRemovals(removals);
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
protected TwoPhaseSet twoPhaseSetFromBinary(byte[] bytes) {
|
||||
try {
|
||||
TwoPhaseSetMessages.TwoPhaseSet msg =
|
||||
TwoPhaseSetMessages.TwoPhaseSet.parseFrom(bytes);
|
||||
GSet<String> adds = GSet.create();
|
||||
for (String elem : msg.getAddsList()) {
|
||||
adds = adds.add(elem);
|
||||
}
|
||||
GSet<String> removals = GSet.create();
|
||||
for (String elem : msg.getRemovalsList()) {
|
||||
removals = removals.add(elem);
|
||||
}
|
||||
// GSet will accumulate deltas when adding elements,
|
||||
// but those are not of interest in the result of the deserialization
|
||||
return new TwoPhaseSet(adds.resetDelta(), removals.resetDelta());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#serializer
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
//#serializer
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages.TwoPhaseSet2.Builder;
|
||||
|
||||
import akka.actor.ExtendedActorSystem;
|
||||
import akka.cluster.ddata.GSet;
|
||||
import akka.cluster.ddata.protobuf.AbstractSerializationSupport;
|
||||
import akka.cluster.ddata.protobuf.ReplicatedDataSerializer;
|
||||
|
||||
public class TwoPhaseSetSerializer2 extends AbstractSerializationSupport {
|
||||
|
||||
private final ExtendedActorSystem system;
|
||||
private final ReplicatedDataSerializer replicatedDataSerializer;
|
||||
|
||||
public TwoPhaseSetSerializer2(ExtendedActorSystem system) {
|
||||
this.system = system;
|
||||
this.replicatedDataSerializer = new ReplicatedDataSerializer(system);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedActorSystem system() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeManifest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int identifier() {
|
||||
return 99998;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return twoPhaseSetToProto((TwoPhaseSet) obj).toByteArray();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(bytes);
|
||||
}
|
||||
|
||||
protected TwoPhaseSetMessages.TwoPhaseSet2 twoPhaseSetToProto(TwoPhaseSet twoPhaseSet) {
|
||||
Builder b = TwoPhaseSetMessages.TwoPhaseSet2.newBuilder();
|
||||
if (!twoPhaseSet.adds.isEmpty())
|
||||
b.setAdds(otherMessageToProto(twoPhaseSet.adds).toByteString());
|
||||
if (!twoPhaseSet.removals.isEmpty())
|
||||
b.setRemovals(otherMessageToProto(twoPhaseSet.removals).toByteString());
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected TwoPhaseSet twoPhaseSetFromBinary(byte[] bytes) {
|
||||
try {
|
||||
TwoPhaseSetMessages.TwoPhaseSet2 msg =
|
||||
TwoPhaseSetMessages.TwoPhaseSet2.parseFrom(bytes);
|
||||
|
||||
GSet<String> adds = GSet.create();
|
||||
if (msg.hasAdds())
|
||||
adds = (GSet<String>) otherMessageFromBinary(msg.getAdds().toByteArray());
|
||||
|
||||
GSet<String> removals = GSet.create();
|
||||
if (msg.hasRemovals())
|
||||
adds = (GSet<String>) otherMessageFromBinary(msg.getRemovals().toByteArray());
|
||||
|
||||
return new TwoPhaseSet(adds, removals);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#serializer
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
|
||||
import akka.actor.ExtendedActorSystem;
|
||||
|
||||
public class TwoPhaseSetSerializerWithCompression extends TwoPhaseSetSerializer {
|
||||
public TwoPhaseSetSerializerWithCompression(ExtendedActorSystem system) {
|
||||
super(system);
|
||||
}
|
||||
|
||||
//#compression
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return compress(twoPhaseSetToProto((TwoPhaseSet) obj));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(decompress(bytes));
|
||||
}
|
||||
//#compression
|
||||
}
|
||||
|
||||
258
akka-docs/src/test/java/jdocs/dispatcher/DispatcherDocTest.java
Normal file
258
akka-docs/src/test/java/jdocs/dispatcher/DispatcherDocTest.java
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.dispatcher;
|
||||
|
||||
import akka.dispatch.ControlMessage;
|
||||
import akka.dispatch.RequiresMessageQueue;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import docs.dispatcher.DispatcherDocSpec;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import jdocs.actor.MyBoundedActor;
|
||||
import jdocs.actor.MyActor;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
|
||||
//#imports
|
||||
import akka.actor.*;
|
||||
//#imports
|
||||
//#imports-prio
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
//#imports-prio
|
||||
|
||||
//#imports-prio-mailbox
|
||||
import akka.dispatch.PriorityGenerator;
|
||||
import akka.dispatch.UnboundedStablePriorityMailbox;
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import com.typesafe.config.Config;
|
||||
|
||||
//#imports-prio-mailbox
|
||||
|
||||
//#imports-required-mailbox
|
||||
|
||||
//#imports-required-mailbox
|
||||
|
||||
public class DispatcherDocTest extends AbstractJavaTest {
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("DispatcherDocTest", ConfigFactory.parseString(
|
||||
DispatcherDocSpec.javaConfig()).withFallback(ConfigFactory.parseString(
|
||||
DispatcherDocSpec.config())).withFallback(AkkaSpec.testConf()));
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void defineDispatcherInConfig() {
|
||||
//#defining-dispatcher-in-config
|
||||
ActorRef myActor =
|
||||
system.actorOf(Props.create(MyActor.class),
|
||||
"myactor");
|
||||
//#defining-dispatcher-in-config
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void defineDispatcherInCode() {
|
||||
//#defining-dispatcher-in-code
|
||||
ActorRef myActor =
|
||||
system.actorOf(Props.create(MyActor.class).withDispatcher("my-dispatcher"),
|
||||
"myactor3");
|
||||
//#defining-dispatcher-in-code
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void defineFixedPoolSizeDispatcher() {
|
||||
//#defining-fixed-pool-size-dispatcher
|
||||
ActorRef myActor = system.actorOf(Props.create(MyActor.class)
|
||||
.withDispatcher("blocking-io-dispatcher"));
|
||||
//#defining-fixed-pool-size-dispatcher
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void definePinnedDispatcher() {
|
||||
//#defining-pinned-dispatcher
|
||||
ActorRef myActor = system.actorOf(Props.create(MyActor.class)
|
||||
.withDispatcher("my-pinned-dispatcher"));
|
||||
//#defining-pinned-dispatcher
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void compileLookup() {
|
||||
//#lookup
|
||||
// this is scala.concurrent.ExecutionContext
|
||||
// for use with Futures, Scheduler, etc.
|
||||
final ExecutionContext ex = system.dispatchers().lookup("my-dispatcher");
|
||||
//#lookup
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void defineMailboxInConfig() {
|
||||
//#defining-mailbox-in-config
|
||||
ActorRef myActor =
|
||||
system.actorOf(Props.create(MyActor.class),
|
||||
"priomailboxactor");
|
||||
//#defining-mailbox-in-config
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void defineMailboxInCode() {
|
||||
//#defining-mailbox-in-code
|
||||
ActorRef myActor =
|
||||
system.actorOf(Props.create(MyActor.class)
|
||||
.withMailbox("prio-mailbox"));
|
||||
//#defining-mailbox-in-code
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void usingARequiredMailbox() {
|
||||
ActorRef myActor =
|
||||
system.actorOf(Props.create(MyBoundedActor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void priorityDispatcher() throws Exception {
|
||||
TestKit probe = new TestKit(system);
|
||||
//#prio-dispatcher
|
||||
|
||||
class Demo extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
{
|
||||
for (Object msg : new Object[] { "lowpriority", "lowpriority",
|
||||
"highpriority", "pigdog", "pigdog2", "pigdog3", "highpriority",
|
||||
PoisonPill.getInstance() }) {
|
||||
getSelf().tell(msg, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder().matchAny(message -> {
|
||||
log.info(message.toString());
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
|
||||
// We create a new Actor that just prints out what it processes
|
||||
ActorRef myActor = system.actorOf(Props.create(Demo.class, this)
|
||||
.withDispatcher("prio-dispatcher"));
|
||||
|
||||
/*
|
||||
Logs:
|
||||
'highpriority
|
||||
'highpriority
|
||||
'pigdog
|
||||
'pigdog2
|
||||
'pigdog3
|
||||
'lowpriority
|
||||
'lowpriority
|
||||
*/
|
||||
//#prio-dispatcher
|
||||
|
||||
probe.watch(myActor);
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlAwareDispatcher() throws Exception {
|
||||
TestKit probe = new TestKit(system);
|
||||
//#control-aware-dispatcher
|
||||
|
||||
class Demo extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
{
|
||||
for (Object msg : new Object[] { "foo", "bar", new MyControlMessage(),
|
||||
PoisonPill.getInstance() }) {
|
||||
getSelf().tell(msg, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder().matchAny(message -> {
|
||||
log.info(message.toString());
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
|
||||
// We create a new Actor that just prints out what it processes
|
||||
ActorRef myActor = system.actorOf(Props.create(Demo.class, this)
|
||||
.withDispatcher("control-aware-dispatcher"));
|
||||
|
||||
/*
|
||||
Logs:
|
||||
'MyControlMessage
|
||||
'foo
|
||||
'bar
|
||||
*/
|
||||
//#control-aware-dispatcher
|
||||
|
||||
probe.watch(myActor);
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
}
|
||||
|
||||
static
|
||||
//#prio-mailbox
|
||||
public class MyPrioMailbox extends UnboundedStablePriorityMailbox {
|
||||
// needed for reflective instantiation
|
||||
public MyPrioMailbox(ActorSystem.Settings settings, Config config) {
|
||||
// Create a new PriorityGenerator, lower prio means more important
|
||||
super(new PriorityGenerator() {
|
||||
@Override
|
||||
public int gen(Object message) {
|
||||
if (message.equals("highpriority"))
|
||||
return 0; // 'highpriority messages should be treated first if possible
|
||||
else if (message.equals("lowpriority"))
|
||||
return 2; // 'lowpriority messages should be treated last if possible
|
||||
else if (message.equals(PoisonPill.getInstance()))
|
||||
return 3; // PoisonPill when no other left
|
||||
else
|
||||
return 1; // By default they go between high and low prio
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//#prio-mailbox
|
||||
|
||||
static
|
||||
//#control-aware-mailbox-messages
|
||||
public class MyControlMessage implements ControlMessage {}
|
||||
//#control-aware-mailbox-messages
|
||||
|
||||
@Test
|
||||
public void requiredMailboxDispatcher() throws Exception {
|
||||
ActorRef myActor = system.actorOf(Props.create(MyActor.class)
|
||||
.withDispatcher("custom-dispatcher"));
|
||||
}
|
||||
|
||||
static
|
||||
//#require-mailbox-on-actor
|
||||
public class MySpecialActor extends AbstractActor implements
|
||||
RequiresMessageQueue<MyUnboundedMessageQueueSemantics> {
|
||||
//#require-mailbox-on-actor
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return AbstractActor.emptyBehavior();
|
||||
}
|
||||
//#require-mailbox-on-actor
|
||||
// ...
|
||||
}
|
||||
//#require-mailbox-on-actor
|
||||
|
||||
@Test
|
||||
public void requiredMailboxActor() throws Exception {
|
||||
ActorRef myActor = system.actorOf(Props.create(MySpecialActor.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.dispatcher;
|
||||
|
||||
//#mailbox-implementation-example
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.Envelope;
|
||||
import akka.dispatch.MailboxType;
|
||||
import akka.dispatch.MessageQueue;
|
||||
import akka.dispatch.ProducesMessageQueue;
|
||||
import com.typesafe.config.Config;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.Queue;
|
||||
import scala.Option;
|
||||
|
||||
public class MyUnboundedMailbox implements MailboxType,
|
||||
ProducesMessageQueue<MyUnboundedMailbox.MyMessageQueue> {
|
||||
|
||||
// This is the MessageQueue implementation
|
||||
public static class MyMessageQueue implements MessageQueue,
|
||||
MyUnboundedMessageQueueSemantics {
|
||||
private final Queue<Envelope> queue =
|
||||
new ConcurrentLinkedQueue<Envelope>();
|
||||
|
||||
// these must be implemented; queue used as example
|
||||
public void enqueue(ActorRef receiver, Envelope handle) {
|
||||
queue.offer(handle);
|
||||
}
|
||||
public Envelope dequeue() { return queue.poll(); }
|
||||
public int numberOfMessages() { return queue.size(); }
|
||||
public boolean hasMessages() { return !queue.isEmpty(); }
|
||||
public void cleanUp(ActorRef owner, MessageQueue deadLetters) {
|
||||
for (Envelope handle: queue) {
|
||||
deadLetters.enqueue(owner, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This constructor signature must exist, it will be called by Akka
|
||||
public MyUnboundedMailbox(ActorSystem.Settings settings, Config config) {
|
||||
// put your initialization code here
|
||||
}
|
||||
|
||||
// The create method is called to create the MessageQueue
|
||||
public MessageQueue create(Option<ActorRef> owner, Option<ActorSystem> system) {
|
||||
return new MyMessageQueue();
|
||||
}
|
||||
}
|
||||
//#mailbox-implementation-example
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.dispatcher;
|
||||
|
||||
//#mailbox-implementation-example
|
||||
// Marker interface used for mailbox requirements mapping
|
||||
public interface MyUnboundedMessageQueueSemantics {
|
||||
}
|
||||
//#mailbox-implementation-example
|
||||
27
akka-docs/src/test/java/jdocs/duration/Java.java
Normal file
27
akka-docs/src/test/java/jdocs/duration/Java.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (C) 2013-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.duration;
|
||||
|
||||
//#import
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.Deadline;
|
||||
//#import
|
||||
|
||||
class Java {
|
||||
public void demo() {
|
||||
//#dsl
|
||||
final Duration fivesec = Duration.create(5, "seconds");
|
||||
final Duration threemillis = Duration.create("3 millis");
|
||||
final Duration diff = fivesec.minus(threemillis);
|
||||
assert diff.lt(fivesec);
|
||||
assert Duration.Zero().lt(Duration.Inf());
|
||||
//#dsl
|
||||
//#deadline
|
||||
final Deadline deadline = Duration.create(10, "seconds").fromNow();
|
||||
final Duration rest = deadline.timeLeft();
|
||||
//#deadline
|
||||
rest.toString();
|
||||
}
|
||||
}
|
||||
325
akka-docs/src/test/java/jdocs/event/EventBusDocTest.java
Normal file
325
akka-docs/src/test/java/jdocs/event/EventBusDocTest.java
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.event;
|
||||
|
||||
import akka.event.japi.EventBus;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import akka.util.Subclassification;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
//#lookup-bus
|
||||
import akka.event.japi.LookupEventBus;
|
||||
|
||||
//#lookup-bus
|
||||
|
||||
//#subchannel-bus
|
||||
import akka.event.japi.SubchannelEventBus;
|
||||
|
||||
//#subchannel-bus
|
||||
|
||||
//#scanning-bus
|
||||
import akka.event.japi.ScanningEventBus;
|
||||
|
||||
//#scanning-bus
|
||||
|
||||
//#actor-bus
|
||||
import akka.event.japi.ManagedActorEventBus;
|
||||
|
||||
//#actor-bus
|
||||
|
||||
public class EventBusDocTest extends AbstractJavaTest {
|
||||
|
||||
public static class Event {}
|
||||
public static class Subscriber {}
|
||||
public static class Classifier {}
|
||||
|
||||
static public interface EventBusApi extends EventBus<Event, Subscriber, Classifier> {
|
||||
|
||||
@Override
|
||||
//#event-bus-api
|
||||
/**
|
||||
* Attempts to register the subscriber to the specified Classifier
|
||||
* @return true if successful and false if not (because it was already
|
||||
* subscribed to that Classifier, or otherwise)
|
||||
*/
|
||||
public boolean subscribe(Subscriber subscriber, Classifier to);
|
||||
|
||||
//#event-bus-api
|
||||
|
||||
@Override
|
||||
//#event-bus-api
|
||||
/**
|
||||
* Attempts to deregister the subscriber from the specified Classifier
|
||||
* @return true if successful and false if not (because it wasn't subscribed
|
||||
* to that Classifier, or otherwise)
|
||||
*/
|
||||
public boolean unsubscribe(Subscriber subscriber, Classifier from);
|
||||
|
||||
//#event-bus-api
|
||||
|
||||
@Override
|
||||
//#event-bus-api
|
||||
/**
|
||||
* Attempts to deregister the subscriber from all Classifiers it may be subscribed to
|
||||
*/
|
||||
public void unsubscribe(Subscriber subscriber);
|
||||
|
||||
//#event-bus-api
|
||||
|
||||
@Override
|
||||
//#event-bus-api
|
||||
/**
|
||||
* Publishes the specified Event to this bus
|
||||
*/
|
||||
public void publish(Event event);
|
||||
|
||||
//#event-bus-api
|
||||
|
||||
}
|
||||
|
||||
static
|
||||
//#lookup-bus
|
||||
public class MsgEnvelope {
|
||||
public final String topic;
|
||||
public final Object payload;
|
||||
|
||||
public MsgEnvelope(String topic, Object payload) {
|
||||
this.topic = topic;
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
//#lookup-bus
|
||||
static
|
||||
//#lookup-bus
|
||||
/**
|
||||
* Publishes the payload of the MsgEnvelope when the topic of the
|
||||
* MsgEnvelope equals the String specified when subscribing.
|
||||
*/
|
||||
public class LookupBusImpl extends LookupEventBus<MsgEnvelope, ActorRef, String> {
|
||||
|
||||
// is used for extracting the classifier from the incoming events
|
||||
@Override public String classify(MsgEnvelope event) {
|
||||
return event.topic;
|
||||
}
|
||||
|
||||
// will be invoked for each event for all subscribers which registered themselves
|
||||
// for the event’s classifier
|
||||
@Override public void publish(MsgEnvelope event, ActorRef subscriber) {
|
||||
subscriber.tell(event.payload, ActorRef.noSender());
|
||||
}
|
||||
|
||||
// must define a full order over the subscribers, expressed as expected from
|
||||
// `java.lang.Comparable.compare`
|
||||
@Override public int compareSubscribers(ActorRef a, ActorRef b) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
// determines the initial size of the index data structure
|
||||
// used internally (i.e. the expected number of different classifiers)
|
||||
@Override public int mapSize() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
}
|
||||
//#lookup-bus
|
||||
|
||||
static
|
||||
//#subchannel-bus
|
||||
public class StartsWithSubclassification implements Subclassification<String> {
|
||||
@Override public boolean isEqual(String x, String y) {
|
||||
return x.equals(y);
|
||||
}
|
||||
|
||||
@Override public boolean isSubclass(String x, String y) {
|
||||
return x.startsWith(y);
|
||||
}
|
||||
}
|
||||
|
||||
//#subchannel-bus
|
||||
|
||||
static
|
||||
//#subchannel-bus
|
||||
/**
|
||||
* Publishes the payload of the MsgEnvelope when the topic of the
|
||||
* MsgEnvelope starts with the String specified when subscribing.
|
||||
*/
|
||||
public class SubchannelBusImpl extends SubchannelEventBus<MsgEnvelope, ActorRef, String> {
|
||||
|
||||
// Subclassification is an object providing `isEqual` and `isSubclass`
|
||||
// to be consumed by the other methods of this classifier
|
||||
@Override public Subclassification<String> subclassification() {
|
||||
return new StartsWithSubclassification();
|
||||
}
|
||||
|
||||
// is used for extracting the classifier from the incoming events
|
||||
@Override public String classify(MsgEnvelope event) {
|
||||
return event.topic;
|
||||
}
|
||||
|
||||
// will be invoked for each event for all subscribers which registered themselves
|
||||
// for the event’s classifier
|
||||
@Override public void publish(MsgEnvelope event, ActorRef subscriber) {
|
||||
subscriber.tell(event.payload, ActorRef.noSender());
|
||||
}
|
||||
|
||||
}
|
||||
//#subchannel-bus
|
||||
|
||||
static
|
||||
//#scanning-bus
|
||||
/**
|
||||
* Publishes String messages with length less than or equal to the length
|
||||
* specified when subscribing.
|
||||
*/
|
||||
public class ScanningBusImpl extends ScanningEventBus<String, ActorRef, Integer> {
|
||||
|
||||
// is needed for determining matching classifiers and storing them in an
|
||||
// ordered collection
|
||||
@Override public int compareClassifiers(Integer a, Integer b) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
// is needed for storing subscribers in an ordered collection
|
||||
@Override public int compareSubscribers(ActorRef a, ActorRef b) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
// determines whether a given classifier shall match a given event; it is invoked
|
||||
// for each subscription for all received events, hence the name of the classifier
|
||||
@Override public boolean matches(Integer classifier, String event) {
|
||||
return event.length() <= classifier;
|
||||
}
|
||||
|
||||
// will be invoked for each event for all subscribers which registered themselves
|
||||
// for the event’s classifier
|
||||
@Override public void publish(String event, ActorRef subscriber) {
|
||||
subscriber.tell(event, ActorRef.noSender());
|
||||
}
|
||||
|
||||
}
|
||||
//#scanning-bus
|
||||
|
||||
static
|
||||
//#actor-bus
|
||||
public class Notification {
|
||||
public final ActorRef ref;
|
||||
public final int id;
|
||||
|
||||
public Notification(ActorRef ref, int id) {
|
||||
this.ref = ref;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
//#actor-bus
|
||||
|
||||
static
|
||||
//#actor-bus
|
||||
public class ActorBusImpl extends ManagedActorEventBus<Notification> {
|
||||
|
||||
// the ActorSystem will be used for book-keeping operations, such as subscribers terminating
|
||||
public ActorBusImpl(ActorSystem system) {
|
||||
super(system);
|
||||
}
|
||||
|
||||
// is used for extracting the classifier from the incoming events
|
||||
@Override public ActorRef classify(Notification event) {
|
||||
return event.ref;
|
||||
}
|
||||
|
||||
// determines the initial size of the index data structure
|
||||
// used internally (i.e. the expected number of different classifiers)
|
||||
@Override public int mapSize() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
}
|
||||
//#actor-bus
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("EventBusDocTest");
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
@Test
|
||||
public void demonstrateLookupClassification() {
|
||||
new TestKit(system) {{
|
||||
//#lookup-bus-test
|
||||
LookupBusImpl lookupBus = new LookupBusImpl();
|
||||
lookupBus.subscribe(getTestActor(), "greetings");
|
||||
lookupBus.publish(new MsgEnvelope("time", System.currentTimeMillis()));
|
||||
lookupBus.publish(new MsgEnvelope("greetings", "hello"));
|
||||
expectMsgEquals("hello");
|
||||
//#lookup-bus-test
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateSubchannelClassification() {
|
||||
new TestKit(system) {{
|
||||
//#subchannel-bus-test
|
||||
SubchannelBusImpl subchannelBus = new SubchannelBusImpl();
|
||||
subchannelBus.subscribe(getTestActor(), "abc");
|
||||
subchannelBus.publish(new MsgEnvelope("xyzabc", "x"));
|
||||
subchannelBus.publish(new MsgEnvelope("bcdef", "b"));
|
||||
subchannelBus.publish(new MsgEnvelope("abc", "c"));
|
||||
expectMsgEquals("c");
|
||||
subchannelBus.publish(new MsgEnvelope("abcdef", "d"));
|
||||
expectMsgEquals("d");
|
||||
//#subchannel-bus-test
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateScanningClassification() {
|
||||
new TestKit(system) {{
|
||||
//#scanning-bus-test
|
||||
ScanningBusImpl scanningBus = new ScanningBusImpl();
|
||||
scanningBus.subscribe(getTestActor(), 3);
|
||||
scanningBus.publish("xyzabc");
|
||||
scanningBus.publish("ab");
|
||||
expectMsgEquals("ab");
|
||||
scanningBus.publish("abc");
|
||||
expectMsgEquals("abc");
|
||||
//#scanning-bus-test
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateManagedActorClassification() {
|
||||
//#actor-bus-test
|
||||
ActorRef observer1 = new TestKit(system).getRef();
|
||||
ActorRef observer2 = new TestKit(system).getRef();
|
||||
TestKit probe1 = new TestKit(system);
|
||||
TestKit probe2 = new TestKit(system);
|
||||
ActorRef subscriber1 = probe1.getRef();
|
||||
ActorRef subscriber2 = probe2.getRef();
|
||||
ActorBusImpl actorBus = new ActorBusImpl(system);
|
||||
actorBus.subscribe(subscriber1, observer1);
|
||||
actorBus.subscribe(subscriber2, observer1);
|
||||
actorBus.subscribe(subscriber2, observer2);
|
||||
Notification n1 = new Notification(observer1, 100);
|
||||
actorBus.publish(n1);
|
||||
probe1.expectMsgEquals(n1);
|
||||
probe2.expectMsgEquals(n1);
|
||||
Notification n2 = new Notification(observer2, 101);
|
||||
actorBus.publish(n2);
|
||||
probe2.expectMsgEquals(n2);
|
||||
probe1.expectNoMsg(FiniteDuration.create(500, TimeUnit.MILLISECONDS));
|
||||
//#actor-bus-test
|
||||
}
|
||||
|
||||
}
|
||||
245
akka-docs/src/test/java/jdocs/event/LoggingDocTest.java
Normal file
245
akka-docs/src/test/java/jdocs/event/LoggingDocTest.java
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.event;
|
||||
|
||||
//#imports
|
||||
import akka.actor.*;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
//#imports
|
||||
|
||||
//#imports-listener
|
||||
import akka.event.Logging.InitializeLogger;
|
||||
import akka.event.Logging.Error;
|
||||
import akka.event.Logging.Warning;
|
||||
import akka.event.Logging.Info;
|
||||
import akka.event.Logging.Debug;
|
||||
|
||||
//#imports-listener
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.Test;
|
||||
import java.util.Optional;
|
||||
|
||||
//#imports-mdc
|
||||
import akka.event.DiagnosticLoggingAdapter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
//#imports-mdc
|
||||
|
||||
//#imports-deadletter
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
//#imports-deadletter
|
||||
|
||||
public class LoggingDocTest extends AbstractJavaTest {
|
||||
|
||||
@Test
|
||||
public void useLoggingActor() {
|
||||
ActorSystem system = ActorSystem.create("MySystem");
|
||||
ActorRef myActor = system.actorOf(Props.create(MyActor.class, this));
|
||||
myActor.tell("test", ActorRef.noSender());
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useLoggingActorWithMDC() {
|
||||
ActorSystem system = ActorSystem.create("MyDiagnosticSystem");
|
||||
ActorRef mdcActor = system.actorOf(Props.create(MdcActor.class, this));
|
||||
mdcActor.tell("some request", ActorRef.noSender());
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subscribeToDeadLetters() {
|
||||
//#deadletters
|
||||
final ActorSystem system = ActorSystem.create("DeadLetters");
|
||||
final ActorRef actor = system.actorOf(Props.create(DeadLetterActor.class));
|
||||
system.eventStream().subscribe(actor, DeadLetter.class);
|
||||
//#deadletters
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
//#superclass-subscription-eventstream
|
||||
interface AllKindsOfMusic { }
|
||||
|
||||
class Jazz implements AllKindsOfMusic {
|
||||
final public String artist;
|
||||
public Jazz(String artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
}
|
||||
class Electronic implements AllKindsOfMusic {
|
||||
final public String artist;
|
||||
public Electronic(String artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
}
|
||||
|
||||
static class Listener extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Jazz.class, msg ->
|
||||
System.out.printf("%s is listening to: %s%n", getSelf().path().name(), msg)
|
||||
)
|
||||
.match(Electronic.class, msg ->
|
||||
System.out.printf("%s is listening to: %s%n", getSelf().path().name(), msg)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#superclass-subscription-eventstream
|
||||
|
||||
@Test
|
||||
public void subscribeBySubclassification() {
|
||||
final ActorSystem system = ActorSystem.create("DeadLetters");
|
||||
//#superclass-subscription-eventstream
|
||||
final ActorRef actor = system.actorOf(Props.create(DeadLetterActor.class));
|
||||
system.eventStream().subscribe(actor, DeadLetter.class);
|
||||
|
||||
final ActorRef jazzListener = system.actorOf(Props.create(Listener.class));
|
||||
final ActorRef musicListener = system.actorOf(Props.create(Listener.class));
|
||||
system.eventStream().subscribe(jazzListener, Jazz.class);
|
||||
system.eventStream().subscribe(musicListener, AllKindsOfMusic.class);
|
||||
|
||||
// only musicListener gets this message, since it listens to *all* kinds of music:
|
||||
system.eventStream().publish(new Electronic("Parov Stelar"));
|
||||
|
||||
// jazzListener and musicListener will be notified about Jazz:
|
||||
system.eventStream().publish(new Jazz("Sonny Rollins"));
|
||||
|
||||
//#superclass-subscription-eventstream
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subscribeToSuppressedDeadLetters() {
|
||||
final ActorSystem system = ActorSystem.create("SuppressedDeadLetters");
|
||||
final ActorRef actor = system.actorOf(Props.create(DeadLetterActor.class));
|
||||
|
||||
//#suppressed-deadletters
|
||||
system.eventStream().subscribe(actor, SuppressedDeadLetter.class);
|
||||
//#suppressed-deadletters
|
||||
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
@Test
|
||||
public void subscribeToAllDeadLetters() {
|
||||
final ActorSystem system = ActorSystem.create("AllDeadLetters");
|
||||
final ActorRef actor = system.actorOf(Props.create(DeadLetterActor.class));
|
||||
|
||||
//#all-deadletters
|
||||
system.eventStream().subscribe(actor, AllDeadLetters.class);
|
||||
//#all-deadletters
|
||||
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateMultipleArgs() {
|
||||
final ActorSystem system = ActorSystem.create("multiArg");
|
||||
//#array
|
||||
final Object[] args = new Object[] { "The", "brown", "fox", "jumps", 42 };
|
||||
system.log().debug("five parameters: {}, {}, {}, {}, {}", args);
|
||||
//#array
|
||||
TestKit.shutdownActorSystem(system);
|
||||
}
|
||||
|
||||
//#my-actor
|
||||
class MyActor extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
log.debug("Starting");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRestart(Throwable reason, Optional<Object> message) {
|
||||
log.error(reason, "Restarting due to [{}] when processing [{}]",
|
||||
reason.getMessage(), message.isPresent() ? message.get() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("test", msg ->
|
||||
log.info("Received test")
|
||||
)
|
||||
.matchAny(msg ->
|
||||
log.warning("Received unknown message: {}", msg)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
//#my-actor
|
||||
|
||||
//#mdc-actor
|
||||
class MdcActor extends AbstractActor {
|
||||
|
||||
final DiagnosticLoggingAdapter log = Logging.getLogger(this);
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchAny(msg -> {
|
||||
Map<String, Object> mdc;
|
||||
mdc = new HashMap<String, Object>();
|
||||
mdc.put("requestId", 1234);
|
||||
mdc.put("visitorId", 5678);
|
||||
log.setMDC(mdc);
|
||||
|
||||
log.info("Starting new request");
|
||||
|
||||
log.clearMDC();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
//#mdc-actor
|
||||
|
||||
//#my-event-listener
|
||||
class MyEventListener extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(InitializeLogger.class, msg -> {
|
||||
getSender().tell(Logging.loggerInitialized(), getSelf());
|
||||
})
|
||||
.match(Error.class, msg -> {
|
||||
// ...
|
||||
})
|
||||
.match(Warning.class, msg -> {
|
||||
// ...
|
||||
})
|
||||
.match(Info.class, msg -> {
|
||||
// ...
|
||||
})
|
||||
.match(Debug.class, msg -> {
|
||||
// ...
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#my-event-listener
|
||||
|
||||
static
|
||||
//#deadletter-actor
|
||||
public class DeadLetterActor extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(DeadLetter.class, msg -> {
|
||||
System.out.println(msg);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#deadletter-actor
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.extension;
|
||||
|
||||
//#imports
|
||||
import akka.actor.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
//#imports
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExtensionDocTest extends AbstractJavaTest {
|
||||
|
||||
static
|
||||
//#extension
|
||||
public class CountExtensionImpl implements Extension {
|
||||
//Since this Extension is a shared instance
|
||||
// per ActorSystem we need to be threadsafe
|
||||
private final AtomicLong counter = new AtomicLong(0);
|
||||
|
||||
//This is the operation this Extension provides
|
||||
public long increment() {
|
||||
return counter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
//#extension
|
||||
|
||||
static
|
||||
//#extensionid
|
||||
public class CountExtension extends AbstractExtensionId<CountExtensionImpl>
|
||||
implements ExtensionIdProvider {
|
||||
//This will be the identifier of our CountExtension
|
||||
public final static CountExtension CountExtensionProvider = new CountExtension();
|
||||
|
||||
private CountExtension() {}
|
||||
|
||||
//The lookup method is required by ExtensionIdProvider,
|
||||
// so we return ourselves here, this allows us
|
||||
// to configure our extension to be loaded when
|
||||
// the ActorSystem starts up
|
||||
public CountExtension lookup() {
|
||||
return CountExtension.CountExtensionProvider; //The public static final
|
||||
}
|
||||
|
||||
//This method will be called by Akka
|
||||
// to instantiate our Extension
|
||||
public CountExtensionImpl createExtension(ExtendedActorSystem system) {
|
||||
return new CountExtensionImpl();
|
||||
}
|
||||
}
|
||||
|
||||
//#extensionid
|
||||
|
||||
static
|
||||
//#extension-usage-actor
|
||||
public class MyActor extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchAny(msg -> {
|
||||
// typically you would use static import of the
|
||||
// CountExtension.CountExtensionProvider field
|
||||
CountExtension.CountExtensionProvider.get(getContext().getSystem()).increment();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
//#extension-usage-actor
|
||||
|
||||
@Test
|
||||
public void demonstrateHowToCreateAndUseAnAkkaExtensionInJava() {
|
||||
final ActorSystem system = null;
|
||||
try {
|
||||
//#extension-usage
|
||||
// typically you would use static import of the
|
||||
// CountExtension.CountExtensionProvider field
|
||||
CountExtension.CountExtensionProvider.get(system).increment();
|
||||
//#extension-usage
|
||||
} catch (Exception e) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.extension;
|
||||
|
||||
//#imports
|
||||
import akka.actor.Extension;
|
||||
import akka.actor.AbstractExtensionId;
|
||||
import akka.actor.ExtensionIdProvider;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.ExtendedActorSystem;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import com.typesafe.config.Config;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
//#imports
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.actor.AbstractActor;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SettingsExtensionDocTest extends AbstractJavaTest {
|
||||
|
||||
static
|
||||
//#extension
|
||||
public class SettingsImpl implements Extension {
|
||||
|
||||
public final String DB_URI;
|
||||
public final Duration CIRCUIT_BREAKER_TIMEOUT;
|
||||
|
||||
public SettingsImpl(Config config) {
|
||||
DB_URI = config.getString("myapp.db.uri");
|
||||
CIRCUIT_BREAKER_TIMEOUT =
|
||||
Duration.create(config.getDuration("myapp.circuit-breaker.timeout",
|
||||
TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#extension
|
||||
|
||||
static
|
||||
//#extensionid
|
||||
public class Settings extends AbstractExtensionId<SettingsImpl>
|
||||
implements ExtensionIdProvider {
|
||||
public final static Settings SettingsProvider = new Settings();
|
||||
|
||||
private Settings() {}
|
||||
|
||||
public Settings lookup() {
|
||||
return Settings.SettingsProvider;
|
||||
}
|
||||
|
||||
public SettingsImpl createExtension(ExtendedActorSystem system) {
|
||||
return new SettingsImpl(system.settings().config());
|
||||
}
|
||||
}
|
||||
|
||||
//#extensionid
|
||||
|
||||
static
|
||||
//#extension-usage-actor
|
||||
public class MyActor extends AbstractActor {
|
||||
// typically you would use static import of the Settings.SettingsProvider field
|
||||
final SettingsImpl settings =
|
||||
Settings.SettingsProvider.get(getContext().getSystem());
|
||||
Connection connection =
|
||||
connect(settings.DB_URI, settings.CIRCUIT_BREAKER_TIMEOUT);
|
||||
|
||||
//#extension-usage-actor
|
||||
|
||||
public Connection connect(String dbUri, Duration circuitBreakerTimeout) {
|
||||
return new Connection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return AbstractActor.emptyBehavior();
|
||||
}
|
||||
//#extension-usage-actor
|
||||
}
|
||||
//#extension-usage-actor
|
||||
|
||||
public static class Connection {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateHowToCreateAndUseAnAkkaExtensionInJava() {
|
||||
final ActorSystem system = null;
|
||||
try {
|
||||
//#extension-usage
|
||||
// typically you would use static import of the Settings.SettingsProvider field
|
||||
String dbUri = Settings.SettingsProvider.get(system).DB_URI;
|
||||
//#extension-usage
|
||||
} catch (Exception e) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
671
akka-docs/src/test/java/jdocs/future/FutureDocTest.java
Normal file
671
akka-docs/src/test/java/jdocs/future/FutureDocTest.java
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.future;
|
||||
|
||||
//#imports1
|
||||
import akka.dispatch.*;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Promise;
|
||||
import akka.util.Timeout;
|
||||
//#imports1
|
||||
|
||||
//#imports2
|
||||
import scala.concurrent.duration.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 static akka.pattern.Patterns.after;
|
||||
import java.util.Arrays;
|
||||
//#imports7
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import scala.compat.java8.FutureConverters;
|
||||
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.actor.Status.Failure;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.pattern.Patterns;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.core.StringContains.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class FutureDocTest extends AbstractJavaTest {
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("FutureDocTest", AkkaSpec.testConf());
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
public final static class PrintResult<T> extends OnSuccess<T> {
|
||||
@Override public final void onSuccess(T t) {
|
||||
// print t
|
||||
}
|
||||
}
|
||||
|
||||
public final static class Demo {
|
||||
//#print-result
|
||||
public final static class PrintResult<T> extends OnSuccess<T> {
|
||||
@Override public final void onSuccess(T t) {
|
||||
System.out.println(t);
|
||||
}
|
||||
}
|
||||
//#print-result
|
||||
}
|
||||
@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<String> 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(Props.create(MyActor.class));
|
||||
String msg = "hello";
|
||||
//#ask-blocking
|
||||
Timeout timeout = new Timeout(Duration.create(5, "seconds"));
|
||||
Future<Object> future = Patterns.ask(actor, msg, timeout);
|
||||
String result = (String) Await.result(future, timeout.duration());
|
||||
//#ask-blocking
|
||||
//#pipe-to
|
||||
akka.pattern.Patterns.pipe(future, system.dispatcher()).to(actor);
|
||||
//#pipe-to
|
||||
assertEquals("HELLO", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useFutureEval() throws Exception {
|
||||
//#future-eval
|
||||
Future<String> f = future(new Callable<String>() {
|
||||
public String call() {
|
||||
return "Hello" + "World";
|
||||
}
|
||||
}, system.dispatcher());
|
||||
|
||||
f.onSuccess(new PrintResult<String>(), system.dispatcher());
|
||||
//#future-eval
|
||||
String result = (String) Await.result(f, Duration.create(5, SECONDS));
|
||||
assertEquals("HelloWorld", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useMap() throws Exception {
|
||||
//#map
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
Future<String> f1 = future(new Callable<String>() {
|
||||
public String call() {
|
||||
return "Hello" + "World";
|
||||
}
|
||||
}, ec);
|
||||
|
||||
Future<Integer> f2 = f1.map(new Mapper<String, Integer>() {
|
||||
public Integer apply(String s) {
|
||||
return s.length();
|
||||
}
|
||||
}, ec);
|
||||
|
||||
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());
|
||||
//#map
|
||||
int result = Await.result(f2, Duration.create(5, SECONDS));
|
||||
assertEquals(10, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useFlatMap() throws Exception {
|
||||
//#flat-map
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
Future<String> f1 = future(new Callable<String>() {
|
||||
public String call() {
|
||||
return "Hello" + "World";
|
||||
}
|
||||
}, ec);
|
||||
|
||||
Future<Integer> f2 = f1.flatMap(new Mapper<String, Future<Integer>>() {
|
||||
public Future<Integer> apply(final String s) {
|
||||
return future(new Callable<Integer>() {
|
||||
public Integer call() {
|
||||
return s.length();
|
||||
}
|
||||
}, ec);
|
||||
}
|
||||
}, ec);
|
||||
|
||||
f2.onSuccess(new PrintResult<Integer>(), system.dispatcher());
|
||||
//#flat-map
|
||||
int result = Await.result(f2, Duration.create(5, SECONDS));
|
||||
assertEquals(10, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useSequence() throws Exception {
|
||||
List<Future<Integer>> source = new ArrayList<Future<Integer>>();
|
||||
source.add(Futures.successful(1));
|
||||
source.add(Futures.successful(2));
|
||||
|
||||
//#sequence
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
//Some source generating a sequence of Future<Integer>:s
|
||||
Iterable<Future<Integer>> listOfFutureInts = source;
|
||||
|
||||
// now we have a Future[Iterable[Integer]]
|
||||
Future<Iterable<Integer>> futureListOfInts = sequence(listOfFutureInts, ec);
|
||||
|
||||
// Find the sum of the odd numbers
|
||||
Future<Long> futureSum = futureListOfInts.map(
|
||||
new Mapper<Iterable<Integer>, Long>() {
|
||||
public Long apply(Iterable<Integer> ints) {
|
||||
long sum = 0;
|
||||
for (Integer i : ints)
|
||||
sum += i;
|
||||
return sum;
|
||||
}
|
||||
}, ec);
|
||||
|
||||
futureSum.onSuccess(new PrintResult<Long>(), system.dispatcher());
|
||||
//#sequence
|
||||
long result = Await.result(futureSum, Duration.create(5, SECONDS));
|
||||
assertEquals(3L, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useTraverse() throws Exception {
|
||||
//#traverse
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
//Just a sequence of Strings
|
||||
Iterable<String> listStrings = Arrays.asList("a", "b", "c");
|
||||
|
||||
Future<Iterable<String>> futureResult = traverse(listStrings,
|
||||
new Function<String, Future<String>>() {
|
||||
public Future<String> apply(final String r) {
|
||||
return future(new Callable<String>() {
|
||||
public String call() {
|
||||
return r.toUpperCase();
|
||||
}
|
||||
}, ec);
|
||||
}
|
||||
}, ec);
|
||||
|
||||
//Returns the sequence of strings as upper case
|
||||
futureResult.onSuccess(new PrintResult<Iterable<String>>(), system.dispatcher());
|
||||
//#traverse
|
||||
Iterable<String> result = Await.result(futureResult, Duration.create(5, SECONDS));
|
||||
assertEquals(Arrays.asList("A", "B", "C"), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useFold() throws Exception {
|
||||
List<Future<String>> source = new ArrayList<Future<String>>();
|
||||
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<Future<String>> futures = source;
|
||||
|
||||
//Start value is the empty string
|
||||
Future<String> resultFuture = fold("", futures,
|
||||
new Function2<String, String, String>() {
|
||||
public String apply(String r, String t) {
|
||||
return r + t; //Just concatenate
|
||||
}
|
||||
}, ec);
|
||||
|
||||
resultFuture.onSuccess(new PrintResult<String>(), system.dispatcher());
|
||||
//#fold
|
||||
String result = Await.result(resultFuture, Duration.create(5, SECONDS));
|
||||
assertEquals("ab", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useReduce() throws Exception {
|
||||
List<Future<String>> source = new ArrayList<Future<String>>();
|
||||
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<Future<String>> futures = source;
|
||||
|
||||
Future<Object> resultFuture = reduce(futures,
|
||||
new Function2<Object, String, Object>() {
|
||||
public Object apply(Object r, String t) {
|
||||
return r + t; //Just concatenate
|
||||
}
|
||||
}, ec);
|
||||
|
||||
resultFuture.onSuccess(new PrintResult<Object>(), system.dispatcher());
|
||||
//#reduce
|
||||
Object result = Await.result(resultFuture, Duration.create(5, SECONDS));
|
||||
|
||||
assertEquals("ab", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useSuccessfulAndFailedAndPromise() throws Exception {
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
//#successful
|
||||
Future<String> future = Futures.successful("Yay!");
|
||||
//#successful
|
||||
//#failed
|
||||
Future<String> otherFuture = Futures.failed(
|
||||
new IllegalArgumentException("Bang!"));
|
||||
//#failed
|
||||
//#promise
|
||||
Promise<String> promise = Futures.promise();
|
||||
Future<String> theFuture = promise.future();
|
||||
promise.success("hello");
|
||||
//#promise
|
||||
Object result = Await.result(future, Duration.create(5, SECONDS));
|
||||
assertEquals("Yay!", result);
|
||||
Throwable result2 = Await.result(otherFuture.failed(),
|
||||
Duration.create(5, SECONDS));
|
||||
assertEquals("Bang!", result2.getMessage());
|
||||
String out = Await.result(theFuture, Duration.create(5, SECONDS));
|
||||
assertEquals("hello", out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useFilter() throws Exception {
|
||||
//#filter
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
Future<Integer> future1 = Futures.successful(4);
|
||||
Future<Integer> successfulFilter = future1.filter(Filter.filterOf(
|
||||
new Function<Integer, Boolean>() {
|
||||
public Boolean apply(Integer i) {
|
||||
return i % 2 == 0;
|
||||
}
|
||||
}), ec);
|
||||
|
||||
Future<Integer> failedFilter = future1.filter(Filter.filterOf(
|
||||
new Function<Integer, Boolean>() {
|
||||
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<String> future1 = Futures.successful("value").andThen(
|
||||
new OnComplete<String>() {
|
||||
public void onComplete(Throwable failure, String result) {
|
||||
if (failure != null)
|
||||
sendToIssueTracker(failure);
|
||||
}
|
||||
}, ec).andThen(new OnComplete<String>() {
|
||||
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<Integer> future = future(new Callable<Integer>() {
|
||||
public Integer call() {
|
||||
return 1 / 0;
|
||||
}
|
||||
}, ec).recover(new Recover<Integer>() {
|
||||
public Integer recover(Throwable problem) throws Throwable {
|
||||
if (problem instanceof ArithmeticException)
|
||||
return 0;
|
||||
else
|
||||
throw problem;
|
||||
}
|
||||
}, ec);
|
||||
|
||||
future.onSuccess(new PrintResult<Integer>(), system.dispatcher());
|
||||
//#recover
|
||||
int result = Await.result(future, Duration.create(5, SECONDS));
|
||||
assertEquals(result, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useTryRecover() throws Exception {
|
||||
//#try-recover
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
Future<Integer> future = future(new Callable<Integer>() {
|
||||
public Integer call() {
|
||||
return 1 / 0;
|
||||
}
|
||||
}, ec).recoverWith(new Recover<Future<Integer>>() {
|
||||
public Future<Integer> recover(Throwable problem) throws Throwable {
|
||||
if (problem instanceof ArithmeticException) {
|
||||
return future(new Callable<Integer>() {
|
||||
public Integer call() {
|
||||
return 0;
|
||||
}
|
||||
}, ec);
|
||||
} else
|
||||
throw problem;
|
||||
}
|
||||
}, ec);
|
||||
|
||||
future.onSuccess(new PrintResult<Integer>(), system.dispatcher());
|
||||
//#try-recover
|
||||
int result = Await.result(future, Duration.create(5, SECONDS));
|
||||
assertEquals(result, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useOnSuccessOnFailureAndOnComplete() throws Exception {
|
||||
{
|
||||
Future<String> future = Futures.successful("foo");
|
||||
|
||||
//#onSuccess
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
future.onSuccess(new OnSuccess<String>() {
|
||||
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<String> 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<String> future = Futures.successful("foo");
|
||||
//#onComplete
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
future.onComplete(new OnComplete<String>() {
|
||||
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<String> future1 = Futures.successful("foo");
|
||||
Future<String> future2 = Futures.successful("bar");
|
||||
Future<String> future3 = future1.zip(future2).map(
|
||||
new Mapper<scala.Tuple2<String, String>, String>() {
|
||||
public String apply(scala.Tuple2<String, String> zipped) {
|
||||
return zipped._1() + " " + zipped._2();
|
||||
}
|
||||
}, ec);
|
||||
|
||||
future3.onSuccess(new PrintResult<String>(), system.dispatcher());
|
||||
//#zip
|
||||
String result = Await.result(future3, Duration.create(5, SECONDS));
|
||||
assertEquals("foo bar", result);
|
||||
}
|
||||
|
||||
{
|
||||
//#fallback-to
|
||||
Future<String> future1 = Futures.failed(new IllegalStateException("OHNOES1"));
|
||||
Future<String> future2 = Futures.failed(new IllegalStateException("OHNOES2"));
|
||||
Future<String> future3 = Futures.successful("bar");
|
||||
// Will have "bar" in this case
|
||||
Future<String> future4 = future1.fallbackTo(future2).fallbackTo(future3);
|
||||
future4.onSuccess(new PrintResult<String>(), system.dispatcher());
|
||||
//#fallback-to
|
||||
String result = Await.result(future4, Duration.create(5, SECONDS));
|
||||
assertEquals("bar", result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void useAfter() throws Exception {
|
||||
//#after
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
Future<String> failExc = Futures.failed(new IllegalStateException("OHNOES1"));
|
||||
Future<String> delayed = Patterns.after(Duration.create(200, "millis"),
|
||||
system.scheduler(), ec, failExc);
|
||||
Future<String> future = future(new Callable<String>() {
|
||||
public String call() throws InterruptedException {
|
||||
Thread.sleep(1000);
|
||||
return "foo";
|
||||
}
|
||||
}, ec);
|
||||
Future<String> result = Futures.firstCompletedOf(
|
||||
Arrays.<Future<String>>asList(future, delayed), ec);
|
||||
//#after
|
||||
Await.result(result, Duration.create(2, SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thenApplyCompletionThread() throws Exception {
|
||||
//#apply-completion-thread
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
Future<String> scalaFuture = Futures.future(() -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
countDownLatch.await(); // do not complete yet
|
||||
return "hello";
|
||||
}, ec);
|
||||
|
||||
CompletionStage<String> fromScalaFuture = FutureConverters.toJava(scalaFuture)
|
||||
.thenApply(s -> { // 1
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
})
|
||||
.thenApply(s -> { // 2
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
})
|
||||
.thenApply(s -> { // 3
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
});
|
||||
|
||||
countDownLatch.countDown(); // complete scalaFuture
|
||||
//#apply-completion-thread
|
||||
|
||||
fromScalaFuture.toCompletableFuture().get(2, SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thenApplyMainThread() throws Exception {
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
//#apply-main-thread
|
||||
Future<String> scalaFuture = Futures.future(() -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return "hello";
|
||||
}, ec);
|
||||
|
||||
CompletionStage<String> completedStage = FutureConverters.toJava(scalaFuture)
|
||||
.thenApply(s -> { // 1
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
});
|
||||
|
||||
completedStage.toCompletableFuture().get(2, SECONDS); // complete current CompletionStage
|
||||
final String currentThread = Thread.currentThread().getName();
|
||||
|
||||
CompletionStage<String> stage2 = completedStage
|
||||
.thenApply(s -> { // 2
|
||||
assertThat(Thread.currentThread().getName(), is(currentThread));
|
||||
return s;
|
||||
})
|
||||
.thenApply(s -> { // 3
|
||||
assertThat(Thread.currentThread().getName(), is(currentThread));
|
||||
return s;
|
||||
});
|
||||
//#apply-main-thread
|
||||
|
||||
stage2.toCompletableFuture().get(2, SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thenApplyAsyncDefault() throws Exception {
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
Future<String> scalaFuture = Futures.future(() -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return "hello";
|
||||
}, ec);
|
||||
|
||||
//#apply-async-default
|
||||
CompletionStage<String> fromScalaFuture = FutureConverters.toJava(scalaFuture)
|
||||
.thenApplyAsync(s -> { // 1
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
})
|
||||
.thenApplyAsync(s -> { // 2
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
})
|
||||
.thenApplyAsync(s -> { // 3
|
||||
assertThat(Thread.currentThread().getName(), containsString("ForkJoinPool.commonPool"));
|
||||
return s;
|
||||
});
|
||||
//#apply-async-default
|
||||
|
||||
fromScalaFuture.toCompletableFuture().get(2, SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thenApplyAsyncExecutor() throws Exception {
|
||||
final ExecutionContext ec = system.dispatcher();
|
||||
|
||||
Future<String> scalaFuture = Futures.future(() -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return "hello";
|
||||
}, ec);
|
||||
|
||||
//#apply-async-executor
|
||||
final Executor ex = system.dispatcher();
|
||||
|
||||
CompletionStage<String> fromScalaFuture = FutureConverters.toJava(scalaFuture)
|
||||
.thenApplyAsync(s -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return s;
|
||||
}, ex)
|
||||
.thenApplyAsync(s -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return s;
|
||||
}, ex)
|
||||
.thenApplyAsync(s -> {
|
||||
assertThat(Thread.currentThread().getName(), containsString("akka.actor.default-dispatcher"));
|
||||
return s;
|
||||
}, ex);
|
||||
//#apply-async-executor
|
||||
|
||||
fromScalaFuture.toCompletableFuture().get(2, SECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class MyActor extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, msg -> {
|
||||
getSender().tell(msg.toUpperCase(), getSelf());
|
||||
})
|
||||
.match(Integer.class, i -> {
|
||||
if (i < 0) {
|
||||
getSender().tell(new Failure(new ArithmeticException("Negative values not supported")), getSelf());
|
||||
} else {
|
||||
getSender().tell(i, getSelf());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
akka-docs/src/test/java/jdocs/io/IODocTest.java
Normal file
86
akka-docs/src/test/java/jdocs/io/IODocTest.java
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.AbstractActor;
|
||||
//#imports
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.io.Inet;
|
||||
import akka.io.Tcp;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.io.TcpSO;
|
||||
import akka.util.ByteString;
|
||||
//#imports
|
||||
|
||||
public class IODocTest {
|
||||
|
||||
static public class Demo extends AbstractActor {
|
||||
ActorRef connectionActor = null;
|
||||
ActorRef listener = getSelf();
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("connect", msg -> {
|
||||
//#manager
|
||||
final ActorRef tcp = Tcp.get(system).manager();
|
||||
//#manager
|
||||
//#connect
|
||||
final InetSocketAddress remoteAddr = new InetSocketAddress("127.0.0.1",
|
||||
12345);
|
||||
tcp.tell(TcpMessage.connect(remoteAddr), getSelf());
|
||||
//#connect
|
||||
//#connect-with-options
|
||||
final InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1",
|
||||
1234);
|
||||
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
|
||||
options.add(TcpSO.keepAlive(true));
|
||||
tcp.tell(TcpMessage.connect(remoteAddr, localAddr, options, null, false), getSelf());
|
||||
//#connect-with-options
|
||||
})
|
||||
//#connected
|
||||
.match(Tcp.Connected.class, conn -> {
|
||||
connectionActor = getSender();
|
||||
connectionActor.tell(TcpMessage.register(listener), getSelf());
|
||||
})
|
||||
//#connected
|
||||
//#received
|
||||
.match(Tcp.Received.class, recv -> {
|
||||
final ByteString data = recv.data();
|
||||
// and do something with the received data ...
|
||||
})
|
||||
.match(Tcp.CommandFailed.class, failed -> {
|
||||
final Tcp.Command command = failed.cmd();
|
||||
// react to failed connect, bind, write, etc.
|
||||
})
|
||||
.match(Tcp.ConnectionClosed.class, closed -> {
|
||||
if (closed.isAborted()) {
|
||||
// handle close reasons like this
|
||||
}
|
||||
})
|
||||
//#received
|
||||
.matchEquals("bind", msg -> {
|
||||
final ActorRef handler = getSelf();
|
||||
//#bind
|
||||
final ActorRef tcp = Tcp.get(system).manager();
|
||||
final InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1",
|
||||
1234);
|
||||
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
|
||||
options.add(TcpSO.reuseAddress(true));
|
||||
tcp.tell(TcpMessage.bind(handler, localAddr, 10, options, false), getSelf());
|
||||
//#bind
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
// This is currently only a compilation test, nothing is run
|
||||
}
|
||||
97
akka-docs/src/test/java/jdocs/io/JavaReadBackPressure.java
Normal file
97
akka-docs/src/test/java/jdocs/io/JavaReadBackPressure.java
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package jdocs.io;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.io.Inet;
|
||||
import akka.io.Tcp;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.util.ByteString;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
public class JavaReadBackPressure {
|
||||
|
||||
static public class Listener extends AbstractActor {
|
||||
ActorRef tcp;
|
||||
ActorRef listener;
|
||||
|
||||
@Override
|
||||
//#pull-accepting
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Tcp.Bound.class, x -> {
|
||||
listener = getSender();
|
||||
// Accept connections one by one
|
||||
listener.tell(TcpMessage.resumeAccepting(1), getSelf());
|
||||
})
|
||||
.match(Tcp.Connected.class, x -> {
|
||||
ActorRef handler = getContext().actorOf(Props.create(PullEcho.class, getSender()));
|
||||
getSender().tell(TcpMessage.register(handler), getSelf());
|
||||
// Resume accepting connections
|
||||
listener.tell(TcpMessage.resumeAccepting(1), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#pull-accepting
|
||||
|
||||
@Override
|
||||
public void preStart() throws Exception {
|
||||
//#pull-mode-bind
|
||||
tcp = Tcp.get(getContext().getSystem()).manager();
|
||||
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
|
||||
tcp.tell(
|
||||
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100, options, true),
|
||||
getSelf()
|
||||
);
|
||||
//#pull-mode-bind
|
||||
}
|
||||
|
||||
private void demonstrateConnect() {
|
||||
//#pull-mode-connect
|
||||
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
|
||||
tcp.tell(
|
||||
TcpMessage.connect(new InetSocketAddress("localhost", 3000), null, options, null, true),
|
||||
getSelf()
|
||||
);
|
||||
//#pull-mode-connect
|
||||
}
|
||||
}
|
||||
|
||||
static public class Ack implements Tcp.Event {
|
||||
}
|
||||
|
||||
static public class PullEcho extends AbstractActor {
|
||||
final ActorRef connection;
|
||||
|
||||
public PullEcho(ActorRef connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
//#pull-reading-echo
|
||||
@Override
|
||||
public void preStart() throws Exception {
|
||||
connection.tell(TcpMessage.resumeReading(), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Tcp.Received.class, message -> {
|
||||
ByteString data = message.data();
|
||||
connection.tell(TcpMessage.write(data, new Ack()), getSelf());
|
||||
})
|
||||
.match(Ack.class, message -> {
|
||||
connection.tell(TcpMessage.resumeReading(), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#pull-reading-echo
|
||||
}
|
||||
|
||||
}
|
||||
128
akka-docs/src/test/java/jdocs/io/JavaUdpMulticast.java
Normal file
128
akka-docs/src/test/java/jdocs/io/JavaUdpMulticast.java
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io;
|
||||
|
||||
//#imports
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.io.Inet;
|
||||
import akka.io.Udp;
|
||||
import akka.io.UdpMessage;
|
||||
import akka.util.ByteString;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.net.DatagramSocket;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
//#imports
|
||||
|
||||
public class JavaUdpMulticast {
|
||||
//#inet6-protocol-family
|
||||
public static class Inet6ProtocolFamily extends Inet.DatagramChannelCreator {
|
||||
@Override
|
||||
public DatagramChannel create() throws Exception {
|
||||
return DatagramChannel.open(StandardProtocolFamily.INET6);
|
||||
}
|
||||
}
|
||||
//#inet6-protocol-family
|
||||
|
||||
//#multicast-group
|
||||
public static class MulticastGroup extends Inet.AbstractSocketOptionV2 {
|
||||
private String address;
|
||||
private String interf;
|
||||
|
||||
public MulticastGroup(String address, String interf) {
|
||||
this.address = address;
|
||||
this.interf = interf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterBind(DatagramSocket s) {
|
||||
try {
|
||||
InetAddress group = InetAddress.getByName(address);
|
||||
NetworkInterface networkInterface = NetworkInterface.getByName(interf);
|
||||
s.getChannel().join(group, networkInterface);
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Unable to join multicast group.");
|
||||
}
|
||||
}
|
||||
}
|
||||
//#multicast-group
|
||||
|
||||
public static class Listener extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
ActorRef sink;
|
||||
|
||||
public Listener(String iface, String group, Integer port, ActorRef sink) {
|
||||
this.sink = sink;
|
||||
|
||||
//#bind
|
||||
List<Inet.SocketOption> options = new ArrayList<>();
|
||||
options.add(new Inet6ProtocolFamily());
|
||||
options.add(new MulticastGroup(group, iface));
|
||||
|
||||
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
|
||||
// listen for datagrams on this address
|
||||
InetSocketAddress endpoint = new InetSocketAddress(port);
|
||||
mgr.tell(UdpMessage.bind(getSelf(), endpoint, options), getSelf());
|
||||
//#bind
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Udp.Bound.class, bound -> {
|
||||
log.info("Bound to {}", bound.localAddress());
|
||||
sink.tell(bound, getSelf());
|
||||
})
|
||||
.match(Udp.Received.class, received -> {
|
||||
final String txt = received.data().decodeString("utf-8");
|
||||
log.info("Received '{}' from {}", txt, received.sender());
|
||||
sink.tell(txt, getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Sender extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
String iface;
|
||||
String group;
|
||||
Integer port;
|
||||
String message;
|
||||
|
||||
public Sender(String iface, String group, Integer port, String msg) {
|
||||
this.iface = iface;
|
||||
this.group = group;
|
||||
this.port = port;
|
||||
this.message = msg;
|
||||
|
||||
List<Inet.SocketOption> options = new ArrayList<>();
|
||||
options.add(new Inet6ProtocolFamily());
|
||||
|
||||
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
|
||||
mgr.tell(UdpMessage.simpleSender(options), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Udp.SimpleSenderReady.class, x -> {
|
||||
InetSocketAddress remote = new InetSocketAddress(group + "%" + iface, port);
|
||||
log.info("Sending message to " + remote);
|
||||
getSender().tell(UdpMessage.send(ByteString.fromString(message), remote), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
100
akka-docs/src/test/java/jdocs/io/JavaUdpMulticastTest.java
Normal file
100
akka-docs/src/test/java/jdocs/io/JavaUdpMulticastTest.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.io.Udp;
|
||||
import akka.testkit.SocketUtil;
|
||||
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public class JavaUdpMulticastTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("JavaUdpMulticastTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUdpMulticast() throws Exception {
|
||||
new TestKit(system) {{
|
||||
List<NetworkInterface> ipv6Ifaces = new ArrayList<>();
|
||||
for (Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements(); ) {
|
||||
NetworkInterface interf = interfaces.nextElement();
|
||||
if (interf.isUp() && interf.supportsMulticast()) {
|
||||
for (Enumeration<InetAddress> addresses = interf.getInetAddresses(); addresses.hasMoreElements(); ) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
if (address instanceof Inet6Address) {
|
||||
ipv6Ifaces.add(interf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipv6Ifaces.isEmpty()) {
|
||||
system.log().info("JavaUdpMulticastTest skipped since no ipv6 interface supporting multicast could be found");
|
||||
} else {
|
||||
// lots of problems with choosing the wrong interface for this test depending
|
||||
// on the platform (awsdl0 can't be used on OSX, docker[0-9] can't be used in a docker machine etc.)
|
||||
// therefore: try hard to find an interface that _does_ work, and only fail if there was any potentially
|
||||
// working interfaces but all failed
|
||||
for (Iterator<NetworkInterface> interfaceIterator = ipv6Ifaces.iterator(); interfaceIterator.hasNext(); ) {
|
||||
NetworkInterface ipv6Iface = interfaceIterator.next();
|
||||
// host assigned link local multicast address http://tools.ietf.org/html/rfc3307#section-4.3.2
|
||||
// generate a random 32 bit multicast address with the high order bit set
|
||||
final String randomAddress = Long.toHexString(((long) Math.abs(new Random().nextInt())) | (1L << 31)).toUpperCase();
|
||||
final StringBuilder groupBuilder = new StringBuilder("FF02:");
|
||||
for (int i = 0; i < 2; i += 1) {
|
||||
groupBuilder.append(":");
|
||||
groupBuilder.append(randomAddress.subSequence(i * 4, i * 4 + 4));
|
||||
}
|
||||
final String group = groupBuilder.toString();
|
||||
final Integer port = SocketUtil.temporaryUdpIpv6Port(ipv6Iface);
|
||||
final String msg = "ohi";
|
||||
final ActorRef sink = getRef();
|
||||
final String iface = ipv6Iface.getName();
|
||||
|
||||
final ActorRef listener = system.actorOf(Props.create(JavaUdpMulticast.Listener.class, iface, group, port, sink));
|
||||
|
||||
try {
|
||||
expectMsgClass(Udp.Bound.class);
|
||||
final ActorRef sender = system.actorOf(Props.create(JavaUdpMulticast.Sender.class, iface, group, port, msg));
|
||||
expectMsgEquals(msg);
|
||||
// success with one interface is enough
|
||||
break;
|
||||
|
||||
} catch (AssertionError ex) {
|
||||
if (!interfaceIterator.hasNext()) throw ex;
|
||||
else {
|
||||
system.log().info("Failed to run test on interface {}", ipv6Iface.getDisplayName());
|
||||
}
|
||||
} finally {
|
||||
// unbind
|
||||
system.stop(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
}
|
||||
85
akka-docs/src/test/java/jdocs/io/UdpConnectedDocTest.java
Normal file
85
akka-docs/src/test/java/jdocs/io/UdpConnectedDocTest.java
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io;
|
||||
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.AbstractActor;
|
||||
//#imports
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.io.Inet;
|
||||
import akka.io.UdpConnected;
|
||||
import akka.io.UdpConnectedMessage;
|
||||
import akka.io.UdpSO;
|
||||
import akka.util.ByteString;
|
||||
//#imports
|
||||
|
||||
public class UdpConnectedDocTest {
|
||||
|
||||
static public class Demo extends AbstractActor {
|
||||
ActorRef connectionActor = null;
|
||||
ActorRef handler = getSelf();
|
||||
ActorSystem system = getContext().getSystem();
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder builder = receiveBuilder();
|
||||
builder.matchEquals("connect", message -> {
|
||||
//#manager
|
||||
final ActorRef udp = UdpConnected.get(system).manager();
|
||||
//#manager
|
||||
//#connect
|
||||
final InetSocketAddress remoteAddr =
|
||||
new InetSocketAddress("127.0.0.1", 12345);
|
||||
udp.tell(UdpConnectedMessage.connect(handler, remoteAddr), getSelf());
|
||||
//#connect
|
||||
//#connect-with-options
|
||||
final InetSocketAddress localAddr =
|
||||
new InetSocketAddress("127.0.0.1", 1234);
|
||||
final List<Inet.SocketOption> options =
|
||||
new ArrayList<Inet.SocketOption>();
|
||||
options.add(UdpSO.broadcast(true));
|
||||
udp.tell(UdpConnectedMessage.connect(handler, remoteAddr, localAddr, options), getSelf());
|
||||
//#connect-with-options
|
||||
});
|
||||
//#connected
|
||||
builder.match(UdpConnected.Connected.class, conn -> {
|
||||
connectionActor = getSender(); // Save the worker ref for later use
|
||||
});
|
||||
//#connected
|
||||
//#received
|
||||
builder
|
||||
.match(UdpConnected.Received.class, recv -> {
|
||||
final ByteString data = recv.data();
|
||||
// and do something with the received data ...
|
||||
})
|
||||
.match(UdpConnected.CommandFailed.class, failed -> {
|
||||
final UdpConnected.Command command = failed.cmd();
|
||||
// react to failed connect, etc.
|
||||
})
|
||||
.match(UdpConnected.Disconnected.class, x -> {
|
||||
// do something on disconnect
|
||||
});
|
||||
//#received
|
||||
builder.matchEquals("send", x -> {
|
||||
ByteString data = ByteString.empty();
|
||||
//#send
|
||||
connectionActor.tell(UdpConnectedMessage.send(data), getSelf());
|
||||
//#send
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateConnect() {
|
||||
}
|
||||
|
||||
}
|
||||
164
akka-docs/src/test/java/jdocs/io/UdpDocTest.java
Normal file
164
akka-docs/src/test/java/jdocs/io/UdpDocTest.java
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io;
|
||||
|
||||
//#imports
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.io.Udp;
|
||||
import akka.io.UdpConnected;
|
||||
import akka.io.UdpConnectedMessage;
|
||||
import akka.io.UdpMessage;
|
||||
import akka.util.ByteString;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
//#imports
|
||||
|
||||
public class UdpDocTest {
|
||||
|
||||
//#sender
|
||||
public static class SimpleSender extends AbstractActor {
|
||||
final InetSocketAddress remote;
|
||||
|
||||
public SimpleSender(InetSocketAddress remote) {
|
||||
this.remote = remote;
|
||||
|
||||
// request creation of a SimpleSender
|
||||
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
|
||||
mgr.tell(UdpMessage.simpleSender(), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Udp.SimpleSenderReady.class, message -> {
|
||||
getContext().become(ready(getSender()));
|
||||
//#sender
|
||||
getSender().tell(UdpMessage.send(ByteString.fromString("hello"), remote), getSelf());
|
||||
//#sender
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private Receive ready(final ActorRef send) {
|
||||
return receiveBuilder()
|
||||
.match(String.class, message -> {
|
||||
send.tell(UdpMessage.send(ByteString.fromString(message), remote), getSelf());
|
||||
//#sender
|
||||
if (message.equals("world")) {
|
||||
send.tell(PoisonPill.getInstance(), getSelf());
|
||||
}
|
||||
//#sender
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#sender
|
||||
|
||||
//#listener
|
||||
public static class Listener extends AbstractActor {
|
||||
final ActorRef nextActor;
|
||||
|
||||
public Listener(ActorRef nextActor) {
|
||||
this.nextActor = nextActor;
|
||||
|
||||
// request creation of a bound listen socket
|
||||
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
|
||||
mgr.tell(
|
||||
UdpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0)),
|
||||
getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Udp.Bound.class, bound -> {
|
||||
//#listener
|
||||
nextActor.tell(bound.localAddress(), getSender());
|
||||
//#listener
|
||||
getContext().become(ready(getSender()));
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private Receive ready(final ActorRef socket) {
|
||||
return receiveBuilder()
|
||||
.match(Udp.Received.class, r -> {
|
||||
// echo server example: send back the data
|
||||
socket.tell(UdpMessage.send(r.data(), r.sender()), getSelf());
|
||||
// or do some processing and forward it on
|
||||
final Object processed = // parse data etc., e.g. using PipelineStage
|
||||
// #listener
|
||||
r.data().utf8String();
|
||||
//#listener
|
||||
nextActor.tell(processed, getSelf());
|
||||
})
|
||||
.matchEquals(UdpMessage.unbind(), message -> {
|
||||
socket.tell(message, getSelf());
|
||||
})
|
||||
.match(Udp.Unbound.class, message -> {
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#listener
|
||||
|
||||
//#connected
|
||||
public static class Connected extends AbstractActor {
|
||||
final InetSocketAddress remote;
|
||||
|
||||
public Connected(InetSocketAddress remote) {
|
||||
this.remote = remote;
|
||||
|
||||
// create a restricted a.k.a. “connected” socket
|
||||
final ActorRef mgr = UdpConnected.get(getContext().getSystem()).getManager();
|
||||
mgr.tell(UdpConnectedMessage.connect(getSelf(), remote), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(UdpConnected.Connected.class, message -> {
|
||||
getContext().become(ready(getSender()));
|
||||
//#connected
|
||||
getSender()
|
||||
.tell(UdpConnectedMessage.send(ByteString.fromString("hello")),
|
||||
getSelf());
|
||||
//#connected
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private Receive ready(final ActorRef connection) {
|
||||
return receiveBuilder()
|
||||
.match(UdpConnected.Received.class, r -> {
|
||||
// process data, send it on, etc.
|
||||
// #connected
|
||||
if (r.data().utf8String().equals("hello")) {
|
||||
connection.tell(
|
||||
UdpConnectedMessage.send(ByteString.fromString("world")),
|
||||
getSelf());
|
||||
}
|
||||
// #connected
|
||||
})
|
||||
.match(String.class, str -> {
|
||||
connection
|
||||
.tell(UdpConnectedMessage.send(ByteString.fromString(str)),
|
||||
getSelf());
|
||||
})
|
||||
.matchEquals(UdpConnectedMessage.disconnect(), message -> {
|
||||
connection.tell(message, getSelf());
|
||||
})
|
||||
.match(UdpConnected.Disconnected.class, x -> {
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#connected
|
||||
|
||||
}
|
||||
243
akka-docs/src/test/java/jdocs/io/japi/EchoHandler.java
Normal file
243
akka-docs/src/test/java/jdocs/io/japi/EchoHandler.java
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.io.Tcp.CommandFailed;
|
||||
import akka.io.Tcp.ConnectionClosed;
|
||||
import akka.io.Tcp.Event;
|
||||
import akka.io.Tcp.Received;
|
||||
import akka.io.Tcp.Write;
|
||||
import akka.io.Tcp.WritingResumed;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.util.ByteString;
|
||||
|
||||
//#echo-handler
|
||||
public class EchoHandler extends AbstractActor {
|
||||
|
||||
final LoggingAdapter log = Logging
|
||||
.getLogger(getContext().getSystem(), getSelf());
|
||||
|
||||
final ActorRef connection;
|
||||
final InetSocketAddress remote;
|
||||
|
||||
public static final long MAX_STORED = 100000000;
|
||||
public static final long HIGH_WATERMARK = MAX_STORED * 5 / 10;
|
||||
public static final long LOW_WATERMARK = MAX_STORED * 2 / 10;
|
||||
|
||||
private long transferred;
|
||||
private int storageOffset = 0;
|
||||
private long stored = 0;
|
||||
private Queue<ByteString> storage = new LinkedList<ByteString>();
|
||||
|
||||
private boolean suspended = false;
|
||||
|
||||
private static class Ack implements Event {
|
||||
public final int ack;
|
||||
public Ack(int ack) {
|
||||
this.ack = ack;
|
||||
}
|
||||
}
|
||||
|
||||
public EchoHandler(ActorRef connection, InetSocketAddress remote) {
|
||||
this.connection = connection;
|
||||
this.remote = remote;
|
||||
|
||||
writing = writing();
|
||||
|
||||
// sign death pact: this actor stops when the connection is closed
|
||||
getContext().watch(connection);
|
||||
|
||||
// start out in optimistic write-through mode
|
||||
getContext().become(writing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return writing;
|
||||
}
|
||||
|
||||
private final Receive writing;
|
||||
|
||||
private Receive writing() {
|
||||
return receiveBuilder()
|
||||
.match(Received.class, msg -> {
|
||||
final ByteString data = msg.data();
|
||||
connection.tell(TcpMessage.write(data, new Ack(currentOffset())), getSelf());
|
||||
buffer(data);
|
||||
|
||||
})
|
||||
.match(Integer.class, msg -> {
|
||||
acknowledge(msg);
|
||||
})
|
||||
.match(CommandFailed.class, msg -> {
|
||||
final Write w = (Write) msg.cmd();
|
||||
connection.tell(TcpMessage.resumeWriting(), getSelf());
|
||||
getContext().become(buffering((Ack) w.ack()));
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
if (msg.isPeerClosed()) {
|
||||
if (storage.isEmpty()) {
|
||||
getContext().stop(getSelf());
|
||||
} else {
|
||||
getContext().become(closing());
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
//#buffering
|
||||
|
||||
final static class BufferingState {
|
||||
int toAck = 10;
|
||||
boolean peerClosed = false;
|
||||
}
|
||||
|
||||
protected Receive buffering(final Ack nack) {
|
||||
final BufferingState state = new BufferingState();
|
||||
|
||||
return receiveBuilder()
|
||||
.match(Received.class, msg -> {
|
||||
buffer(msg.data());
|
||||
|
||||
})
|
||||
.match(WritingResumed.class, msg -> {
|
||||
writeFirst();
|
||||
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
if (msg.isPeerClosed())
|
||||
state.peerClosed = true;
|
||||
else
|
||||
getContext().stop(getSelf());
|
||||
|
||||
})
|
||||
.match(Integer.class, ack -> {
|
||||
acknowledge(ack);
|
||||
|
||||
if (ack >= nack.ack) {
|
||||
// otherwise it was the ack of the last successful write
|
||||
|
||||
if (storage.isEmpty()) {
|
||||
if (state.peerClosed)
|
||||
getContext().stop(getSelf());
|
||||
else
|
||||
getContext().become(writing);
|
||||
|
||||
} else {
|
||||
if (state.toAck > 0) {
|
||||
// stay in ACK-based mode for a short while
|
||||
writeFirst();
|
||||
--state.toAck;
|
||||
} else {
|
||||
// then return to NACK-based again
|
||||
writeAll();
|
||||
if (state.peerClosed)
|
||||
getContext().become(closing());
|
||||
else
|
||||
getContext().become(writing);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#buffering
|
||||
|
||||
//#closing
|
||||
protected Receive closing() {
|
||||
return receiveBuilder()
|
||||
.match(CommandFailed.class, msg -> {
|
||||
// the command can only have been a Write
|
||||
connection.tell(TcpMessage.resumeWriting(), getSelf());
|
||||
getContext().become(closeResend(), false);
|
||||
})
|
||||
.match(Integer.class, msg -> {
|
||||
acknowledge(msg);
|
||||
if (storage.isEmpty())
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
protected Receive closeResend() {
|
||||
return receiveBuilder()
|
||||
.match(WritingResumed.class, msg -> {
|
||||
writeAll();
|
||||
getContext().unbecome();
|
||||
})
|
||||
.match(Integer.class, msg -> {
|
||||
acknowledge(msg);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
//#closing
|
||||
|
||||
//#storage-omitted
|
||||
|
||||
@Override
|
||||
public void postStop() {
|
||||
log.info("transferred {} bytes from/to [{}]", transferred, remote);
|
||||
}
|
||||
|
||||
//#helpers
|
||||
protected void buffer(ByteString data) {
|
||||
storage.add(data);
|
||||
stored += data.size();
|
||||
|
||||
if (stored > MAX_STORED) {
|
||||
log.warning("drop connection to [{}] (buffer overrun)", remote);
|
||||
getContext().stop(getSelf());
|
||||
|
||||
} else if (stored > HIGH_WATERMARK) {
|
||||
log.debug("suspending reading at {}", currentOffset());
|
||||
connection.tell(TcpMessage.suspendReading(), getSelf());
|
||||
suspended = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void acknowledge(int ack) {
|
||||
assert ack == storageOffset;
|
||||
assert !storage.isEmpty();
|
||||
|
||||
final ByteString acked = storage.remove();
|
||||
stored -= acked.size();
|
||||
transferred += acked.size();
|
||||
storageOffset += 1;
|
||||
|
||||
if (suspended && stored < LOW_WATERMARK) {
|
||||
log.debug("resuming reading");
|
||||
connection.tell(TcpMessage.resumeReading(), getSelf());
|
||||
suspended = false;
|
||||
}
|
||||
}
|
||||
//#helpers
|
||||
|
||||
protected int currentOffset() {
|
||||
return storageOffset + storage.size();
|
||||
}
|
||||
|
||||
protected void writeAll() {
|
||||
int i = 0;
|
||||
for (ByteString data : storage) {
|
||||
connection.tell(TcpMessage.write(data, new Ack(storageOffset + i++)), getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeFirst() {
|
||||
connection.tell(TcpMessage.write(storage.peek(), new Ack(storageOffset)), getSelf());
|
||||
}
|
||||
|
||||
//#storage-omitted
|
||||
}
|
||||
//#echo-handler
|
||||
81
akka-docs/src/test/java/jdocs/io/japi/EchoManager.java
Normal file
81
akka-docs/src/test/java/jdocs/io/japi/EchoManager.java
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.SupervisorStrategy;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.io.Tcp;
|
||||
import akka.io.Tcp.Bind;
|
||||
import akka.io.Tcp.Bound;
|
||||
import akka.io.Tcp.Connected;
|
||||
import akka.io.TcpMessage;
|
||||
|
||||
public class EchoManager extends AbstractActor {
|
||||
|
||||
final LoggingAdapter log = Logging
|
||||
.getLogger(getContext().getSystem(), getSelf());
|
||||
|
||||
final Class<?> handlerClass;
|
||||
|
||||
public EchoManager(Class<?> handlerClass) {
|
||||
this.handlerClass = handlerClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return SupervisorStrategy.stoppingStrategy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() throws Exception {
|
||||
//#manager
|
||||
final ActorRef tcpManager = Tcp.get(getContext().getSystem()).manager();
|
||||
//#manager
|
||||
tcpManager.tell(
|
||||
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100),
|
||||
getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRestart(Throwable arg0) throws Exception {
|
||||
// do not restart
|
||||
getContext().stop(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Bound.class, msg -> {
|
||||
log.info("listening on [{}]", msg.localAddress());
|
||||
})
|
||||
.match(Tcp.CommandFailed.class, failed -> {
|
||||
if (failed.cmd() instanceof Bind) {
|
||||
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).localAddress());
|
||||
getContext().stop(getSelf());
|
||||
} else {
|
||||
log.warning("unknown command failed [{}]", failed.cmd());
|
||||
}
|
||||
})
|
||||
.match(Connected.class, conn -> {
|
||||
log.info("received connection from [{}]", conn.remoteAddress());
|
||||
final ActorRef connection = getSender();
|
||||
final ActorRef handler = getContext().actorOf(
|
||||
Props.create(handlerClass, connection, conn.remoteAddress()));
|
||||
//#echo-manager
|
||||
connection.tell(TcpMessage.register(handler,
|
||||
true, // <-- keepOpenOnPeerClosed flag
|
||||
true), getSelf());
|
||||
//#echo-manager
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
35
akka-docs/src/test/java/jdocs/io/japi/EchoServer.java
Normal file
35
akka-docs/src/test/java/jdocs/io/japi/EchoServer.java
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
public class EchoServer {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
final Config config = ConfigFactory.parseString("akka.loglevel=DEBUG");
|
||||
final ActorSystem system = ActorSystem.create("EchoServer", config);
|
||||
try {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final ActorRef watcher = system.actorOf(Props.create(Watcher.class, latch), "watcher");
|
||||
final ActorRef nackServer = system.actorOf(Props.create(EchoManager.class, EchoHandler.class), "nack");
|
||||
final ActorRef ackServer = system.actorOf(Props.create(EchoManager.class, SimpleEchoHandler.class), "ack");
|
||||
watcher.tell(nackServer, ActorRef.noSender());
|
||||
watcher.tell(ackServer, ActorRef.noSender());
|
||||
latch.await(10, TimeUnit.MINUTES);
|
||||
} finally {
|
||||
system.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
186
akka-docs/src/test/java/jdocs/io/japi/IODocTest.java
Normal file
186
akka-docs/src/test/java/jdocs/io/japi/IODocTest.java
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
//#imports
|
||||
import java.net.InetSocketAddress;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.io.Tcp;
|
||||
import akka.io.Tcp.Bound;
|
||||
import akka.io.Tcp.CommandFailed;
|
||||
import akka.io.Tcp.Connected;
|
||||
import akka.io.Tcp.ConnectionClosed;
|
||||
import akka.io.Tcp.Received;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.util.ByteString;
|
||||
//#imports
|
||||
|
||||
import akka.testkit.AkkaSpec;
|
||||
|
||||
public class IODocTest extends AbstractJavaTest {
|
||||
|
||||
static
|
||||
//#server
|
||||
public class Server extends AbstractActor {
|
||||
|
||||
final ActorRef manager;
|
||||
|
||||
public Server(ActorRef manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public static Props props(ActorRef manager) {
|
||||
return Props.create(Server.class, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() throws Exception {
|
||||
final ActorRef tcp = Tcp.get(getContext().getSystem()).manager();
|
||||
tcp.tell(TcpMessage.bind(getSelf(),
|
||||
new InetSocketAddress("localhost", 0), 100), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Bound.class, msg -> {
|
||||
manager.tell(msg, getSelf());
|
||||
|
||||
})
|
||||
.match(CommandFailed.class, msg -> {
|
||||
getContext().stop(getSelf());
|
||||
|
||||
})
|
||||
.match(Connected.class, conn -> {
|
||||
manager.tell(conn, getSelf());
|
||||
final ActorRef handler = getContext().actorOf(
|
||||
Props.create(SimplisticHandler.class));
|
||||
getSender().tell(TcpMessage.register(handler), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#server
|
||||
|
||||
static
|
||||
//#simplistic-handler
|
||||
public class SimplisticHandler extends AbstractActor {
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Received.class, msg -> {
|
||||
final ByteString data = msg.data();
|
||||
System.out.println(data);
|
||||
getSender().tell(TcpMessage.write(data), getSelf());
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#simplistic-handler
|
||||
|
||||
static
|
||||
//#client
|
||||
public class Client extends AbstractActor {
|
||||
|
||||
final InetSocketAddress remote;
|
||||
final ActorRef listener;
|
||||
|
||||
public static Props props(InetSocketAddress remote, ActorRef listener) {
|
||||
return Props.create(Client.class, remote, listener);
|
||||
}
|
||||
|
||||
public Client(InetSocketAddress remote, ActorRef listener) {
|
||||
this.remote = remote;
|
||||
this.listener = listener;
|
||||
|
||||
final ActorRef tcp = Tcp.get(getContext().getSystem()).manager();
|
||||
tcp.tell(TcpMessage.connect(remote), getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(CommandFailed.class, msg -> {
|
||||
listener.tell("failed", getSelf());
|
||||
getContext().stop(getSelf());
|
||||
|
||||
})
|
||||
.match(Connected.class, msg -> {
|
||||
listener.tell(msg, getSelf());
|
||||
getSender().tell(TcpMessage.register(getSelf()), getSelf());
|
||||
getContext().become(connected(getSender()));
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private Receive connected(final ActorRef connection) {
|
||||
return receiveBuilder()
|
||||
.match(ByteString.class, msg -> {
|
||||
connection.tell(TcpMessage.write((ByteString) msg), getSelf());
|
||||
})
|
||||
.match(CommandFailed.class, msg -> {
|
||||
// OS kernel socket buffer was full
|
||||
})
|
||||
.match(Received.class, msg -> {
|
||||
listener.tell(msg.data(), getSelf());
|
||||
})
|
||||
.matchEquals("close", msg -> {
|
||||
connection.tell(TcpMessage.close(), getSelf());
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#client
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("IODocTest", AkkaSpec.testConf());
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
@Test
|
||||
public void testConnection() {
|
||||
new TestKit(system) {
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
final ActorRef server = system.actorOf(Server.props(getRef()), "server1");
|
||||
final InetSocketAddress listen = expectMsgClass(Bound.class).localAddress();
|
||||
final ActorRef client = system.actorOf(Client.props(listen, getRef()), "client1");
|
||||
|
||||
final Connected c1 = expectMsgClass(Connected.class);
|
||||
final Connected c2 = expectMsgClass(Connected.class);
|
||||
assert c1.localAddress().equals(c2.remoteAddress());
|
||||
assert c2.localAddress().equals(c1.remoteAddress());
|
||||
|
||||
client.tell(ByteString.fromString("hello"), getRef());
|
||||
final ByteString reply = expectMsgClass(ByteString.class);
|
||||
assert reply.utf8String().equals("hello");
|
||||
|
||||
watch(client);
|
||||
client.tell("close", getRef());
|
||||
expectTerminated(client);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
45
akka-docs/src/test/java/jdocs/io/japi/Message.java
Normal file
45
akka-docs/src/test/java/jdocs/io/japi/Message.java
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Copyright (C) 2013-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
//#message
|
||||
public class Message {
|
||||
|
||||
static public class Person {
|
||||
private final String first;
|
||||
private final String last;
|
||||
|
||||
public Person(String first, String last) {
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public String getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public String getLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Person[] persons;
|
||||
private final double[] happinessCurve;
|
||||
|
||||
public Message(Person[] persons, double[] happinessCurve) {
|
||||
this.persons = persons;
|
||||
this.happinessCurve = happinessCurve;
|
||||
}
|
||||
|
||||
public Person[] getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
public double[] getHappinessCurve() {
|
||||
return happinessCurve;
|
||||
}
|
||||
}
|
||||
//#message
|
||||
134
akka-docs/src/test/java/jdocs/io/japi/SimpleEchoHandler.java
Normal file
134
akka-docs/src/test/java/jdocs/io/japi/SimpleEchoHandler.java
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.io.japi;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.io.Tcp.ConnectionClosed;
|
||||
import akka.io.Tcp.Event;
|
||||
import akka.io.Tcp.Received;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.util.ByteString;
|
||||
|
||||
//#simple-echo-handler
|
||||
public class SimpleEchoHandler extends AbstractActor {
|
||||
|
||||
final LoggingAdapter log = Logging
|
||||
.getLogger(getContext().getSystem(), getSelf());
|
||||
|
||||
final ActorRef connection;
|
||||
final InetSocketAddress remote;
|
||||
|
||||
public static final long maxStored = 100000000;
|
||||
public static final long highWatermark = maxStored * 5 / 10;
|
||||
public static final long lowWatermark = maxStored * 2 / 10;
|
||||
|
||||
public SimpleEchoHandler(ActorRef connection, InetSocketAddress remote) {
|
||||
this.connection = connection;
|
||||
this.remote = remote;
|
||||
|
||||
// sign death pact: this actor stops when the connection is closed
|
||||
getContext().watch(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Received.class, msg -> {
|
||||
final ByteString data = msg.data();
|
||||
buffer(data);
|
||||
connection.tell(TcpMessage.write(data, ACK), getSelf());
|
||||
// now switch behavior to “waiting for acknowledgement”
|
||||
getContext().become(buffering(), false);
|
||||
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private final Receive buffering() {
|
||||
return receiveBuilder()
|
||||
.match(Received.class, msg -> {
|
||||
buffer(msg.data());
|
||||
|
||||
})
|
||||
.match(Event.class, msg -> msg == ACK, msg -> {
|
||||
acknowledge();
|
||||
|
||||
})
|
||||
.match(ConnectionClosed.class, msg -> {
|
||||
if (msg.isPeerClosed()) {
|
||||
closing = true;
|
||||
} else {
|
||||
// could also be ErrorClosed, in which case we just give up
|
||||
getContext().stop(getSelf());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
//#storage-omitted
|
||||
public void postStop() {
|
||||
log.info("transferred {} bytes from/to [{}]", transferred, remote);
|
||||
}
|
||||
|
||||
private long transferred;
|
||||
private long stored = 0;
|
||||
private Queue<ByteString> storage = new LinkedList<ByteString>();
|
||||
|
||||
private boolean suspended = false;
|
||||
private boolean closing = false;
|
||||
|
||||
private final Event ACK = new Event() {};
|
||||
|
||||
//#simple-helpers
|
||||
protected void buffer(ByteString data) {
|
||||
storage.add(data);
|
||||
stored += data.size();
|
||||
|
||||
if (stored > maxStored) {
|
||||
log.warning("drop connection to [{}] (buffer overrun)", remote);
|
||||
getContext().stop(getSelf());
|
||||
|
||||
} else if (stored > highWatermark) {
|
||||
log.debug("suspending reading");
|
||||
connection.tell(TcpMessage.suspendReading(), getSelf());
|
||||
suspended = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void acknowledge() {
|
||||
final ByteString acked = storage.remove();
|
||||
stored -= acked.size();
|
||||
transferred += acked.size();
|
||||
|
||||
if (suspended && stored < lowWatermark) {
|
||||
log.debug("resuming reading");
|
||||
connection.tell(TcpMessage.resumeReading(), getSelf());
|
||||
suspended = false;
|
||||
}
|
||||
|
||||
if (storage.isEmpty()) {
|
||||
if (closing) {
|
||||
getContext().stop(getSelf());
|
||||
} else {
|
||||
getContext().unbecome();
|
||||
}
|
||||
} else {
|
||||
connection.tell(TcpMessage.write(storage.peek(), ACK), getSelf());
|
||||
}
|
||||
}
|
||||
//#simple-helpers
|
||||
//#storage-omitted
|
||||
}
|
||||
//#simple-echo-handler
|
||||
37
akka-docs/src/test/java/jdocs/io/japi/Watcher.java
Normal file
37
akka-docs/src/test/java/jdocs/io/japi/Watcher.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package jdocs.io.japi;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Terminated;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
public class Watcher extends AbstractActor {
|
||||
|
||||
static public class Watch {
|
||||
final ActorRef target;
|
||||
public Watch(ActorRef target) {
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
final CountDownLatch latch;
|
||||
|
||||
public Watcher(CountDownLatch latch) {
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Watch.class, msg -> {
|
||||
getContext().watch(msg.target);
|
||||
})
|
||||
.match(Terminated.class, msg -> {
|
||||
latch.countDown();
|
||||
if (latch.getCount() == 0) getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.pattern;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.pattern.Backoff;
|
||||
import akka.pattern.BackoffSupervisor;
|
||||
import akka.testkit.TestActors.EchoActor;
|
||||
//#backoff-imports
|
||||
import scala.concurrent.duration.Duration;
|
||||
//#backoff-imports
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BackoffSupervisorDocTest {
|
||||
|
||||
void exampleStop (ActorSystem system) {
|
||||
//#backoff-stop
|
||||
final Props childProps = Props.create(EchoActor.class);
|
||||
|
||||
final Props supervisorProps = BackoffSupervisor.props(
|
||||
Backoff.onStop(
|
||||
childProps,
|
||||
"myEcho",
|
||||
Duration.create(3, TimeUnit.SECONDS),
|
||||
Duration.create(30, TimeUnit.SECONDS),
|
||||
0.2)); // adds 20% "noise" to vary the intervals slightly
|
||||
|
||||
system.actorOf(supervisorProps, "echoSupervisor");
|
||||
//#backoff-stop
|
||||
}
|
||||
|
||||
void exampleFailure (ActorSystem system) {
|
||||
//#backoff-fail
|
||||
final Props childProps = Props.create(EchoActor.class);
|
||||
|
||||
final Props supervisorProps = BackoffSupervisor.props(
|
||||
Backoff.onFailure(
|
||||
childProps,
|
||||
"myEcho",
|
||||
Duration.create(3, TimeUnit.SECONDS),
|
||||
Duration.create(30, TimeUnit.SECONDS),
|
||||
0.2)); // adds 20% "noise" to vary the intervals slightly
|
||||
|
||||
system.actorOf(supervisorProps, "echoSupervisor");
|
||||
//#backoff-fail
|
||||
}
|
||||
}
|
||||
173
akka-docs/src/test/java/jdocs/pattern/SchedulerPatternTest.java
Normal file
173
akka-docs/src/test/java/jdocs/pattern/SchedulerPatternTest.java
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.pattern;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.testkit.*;
|
||||
import akka.testkit.TestEvent.Mute;
|
||||
import akka.testkit.TestEvent.UnMute;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import akka.testkit.javadsl.TestKit;
|
||||
import org.junit.*;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SchedulerPatternTest extends AbstractJavaTest {
|
||||
|
||||
@ClassRule
|
||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||
new AkkaJUnitActorSystemResource("SchedulerPatternTest", AkkaSpec.testConf());
|
||||
|
||||
private final ActorSystem system = actorSystemResource.getSystem();
|
||||
|
||||
static
|
||||
//#schedule-constructor
|
||||
public class ScheduleInConstructor extends AbstractActor {
|
||||
|
||||
private final Cancellable tick = getContext().getSystem().scheduler().schedule(
|
||||
Duration.create(500, TimeUnit.MILLISECONDS),
|
||||
Duration.create(1, TimeUnit.SECONDS),
|
||||
getSelf(), "tick", getContext().dispatcher(), null);
|
||||
//#schedule-constructor
|
||||
// this variable and constructor is declared here to not show up in the docs
|
||||
final ActorRef target;
|
||||
public ScheduleInConstructor(ActorRef target) {
|
||||
this.target = target;
|
||||
}
|
||||
//#schedule-constructor
|
||||
|
||||
@Override
|
||||
public void postStop() {
|
||||
tick.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("tick", message -> {
|
||||
// do something useful here
|
||||
//#schedule-constructor
|
||||
target.tell(message, getSelf());
|
||||
//#schedule-constructor
|
||||
})
|
||||
.matchEquals("restart", message -> {
|
||||
throw new ArithmeticException();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#schedule-constructor
|
||||
|
||||
static
|
||||
//#schedule-receive
|
||||
public class ScheduleInReceive extends AbstractActor {
|
||||
//#schedule-receive
|
||||
// this variable and constructor is declared here to not show up in the docs
|
||||
final ActorRef target;
|
||||
public ScheduleInReceive(ActorRef target) {
|
||||
this.target = target;
|
||||
}
|
||||
//#schedule-receive
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
getContext().getSystem().scheduler().scheduleOnce(
|
||||
Duration.create(500, TimeUnit.MILLISECONDS),
|
||||
getSelf(), "tick", getContext().dispatcher(), null);
|
||||
}
|
||||
|
||||
// override postRestart so we don't call preStart and schedule a new message
|
||||
@Override
|
||||
public void postRestart(Throwable reason) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("tick", message -> {
|
||||
// send another periodic tick after the specified delay
|
||||
getContext().getSystem().scheduler().scheduleOnce(
|
||||
Duration.create(1, TimeUnit.SECONDS),
|
||||
getSelf(), "tick", getContext().dispatcher(), null);
|
||||
// do something useful here
|
||||
//#schedule-receive
|
||||
target.tell(message, getSelf());
|
||||
//#schedule-receive
|
||||
})
|
||||
.matchEquals("restart", message -> {
|
||||
throw new ArithmeticException();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#schedule-receive
|
||||
|
||||
@Test
|
||||
@Ignore // no way to tag this as timing sensitive
|
||||
public void scheduleInConstructor() {
|
||||
new TestSchedule(system) {{
|
||||
final TestKit probe = new TestKit(system);
|
||||
final Props props = Props.create(ScheduleInConstructor.class, probe.getRef());
|
||||
testSchedule(probe, props, duration("3000 millis"), duration("2000 millis"));
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // no way to tag this as timing sensitive
|
||||
public void scheduleInReceive() {
|
||||
new TestSchedule(system) {{
|
||||
final TestKit probe = new TestKit(system);
|
||||
final Props props = Props.create(ScheduleInReceive.class, probe.getRef());
|
||||
testSchedule(probe, props, duration("3000 millis"), duration("2500 millis"));
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doNothing() {
|
||||
// actorSystemResource.after is not called when all tests are ignored
|
||||
}
|
||||
|
||||
public static class TestSchedule extends TestKit {
|
||||
private ActorSystem system;
|
||||
|
||||
public TestSchedule(ActorSystem system) {
|
||||
super(system);
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
public void testSchedule(final TestKit probe, Props props,
|
||||
FiniteDuration startDuration,
|
||||
FiniteDuration afterRestartDuration) {
|
||||
Iterable<akka.testkit.EventFilter> filter =
|
||||
Arrays.asList(new akka.testkit.EventFilter[]{
|
||||
(akka.testkit.EventFilter) new ErrorFilter(ArithmeticException.class)});
|
||||
try {
|
||||
system.eventStream().publish(new Mute(filter));
|
||||
|
||||
final ActorRef actor = system.actorOf(props);
|
||||
within(startDuration, () -> {
|
||||
probe.expectMsgEquals("tick");
|
||||
probe.expectMsgEquals("tick");
|
||||
probe.expectMsgEquals("tick");
|
||||
return null;
|
||||
});
|
||||
|
||||
actor.tell("restart", getRef());
|
||||
within(afterRestartDuration, () -> {
|
||||
probe.expectMsgEquals("tick");
|
||||
probe.expectMsgEquals("tick");
|
||||
return null;
|
||||
});
|
||||
|
||||
system.stop(actor);
|
||||
}
|
||||
finally {
|
||||
system.eventStream().publish(new UnMute(filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
akka-docs/src/test/java/jdocs/pattern/SupervisedAsk.java
Normal file
106
akka-docs/src/test/java/jdocs/pattern/SupervisedAsk.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package jdocs.pattern;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import akka.actor.ActorKilledException;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorRefFactory;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.actor.OneForOneStrategy;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.Scheduler;
|
||||
import akka.actor.Status;
|
||||
import akka.actor.SupervisorStrategy;
|
||||
import akka.actor.Terminated;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.pattern.PatternsCS;
|
||||
import akka.util.Timeout;
|
||||
|
||||
public class SupervisedAsk {
|
||||
|
||||
private static class AskParam {
|
||||
Props props;
|
||||
Object message;
|
||||
Timeout timeout;
|
||||
|
||||
AskParam(Props props, Object message, Timeout timeout) {
|
||||
this.props = props;
|
||||
this.message = message;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AskTimeout {
|
||||
}
|
||||
|
||||
public static class AskSupervisorCreator extends AbstractActor {
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(AskParam.class, message -> {
|
||||
ActorRef supervisor = getContext().actorOf(
|
||||
Props.create(AskSupervisor.class));
|
||||
supervisor.forward(message, getContext());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AskSupervisor extends AbstractActor {
|
||||
private ActorRef targetActor;
|
||||
private ActorRef caller;
|
||||
private AskParam askParam;
|
||||
private Cancellable timeoutMessage;
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return new OneForOneStrategy(0, Duration.Zero(), cause -> {
|
||||
caller.tell(new Status.Failure(cause), getSelf());
|
||||
return SupervisorStrategy.stop();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(AskParam.class, message -> {
|
||||
askParam = message;
|
||||
caller = getSender();
|
||||
targetActor = getContext().actorOf(askParam.props);
|
||||
getContext().watch(targetActor);
|
||||
targetActor.forward(askParam.message, getContext());
|
||||
Scheduler scheduler = getContext().getSystem().scheduler();
|
||||
timeoutMessage = scheduler.scheduleOnce(askParam.timeout.duration(),
|
||||
getSelf(), new AskTimeout(), getContext().dispatcher(), null);
|
||||
})
|
||||
.match(Terminated.class, message -> {
|
||||
Throwable ex = new ActorKilledException("Target actor terminated.");
|
||||
caller.tell(new Status.Failure(ex), getSelf());
|
||||
timeoutMessage.cancel();
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.match(AskTimeout.class, message -> {
|
||||
Throwable ex = new TimeoutException("Target actor timed out after "
|
||||
+ askParam.timeout.toString());
|
||||
caller.tell(new Status.Failure(ex), getSelf());
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletionStage<Object> askOf(ActorRef supervisorCreator, Props props,
|
||||
Object message, Timeout timeout) {
|
||||
AskParam param = new AskParam(props, message, timeout);
|
||||
return PatternsCS.ask(supervisorCreator, param, timeout);
|
||||
}
|
||||
|
||||
synchronized public static ActorRef createSupervisorCreator(
|
||||
ActorRefFactory factory) {
|
||||
return factory.actorOf(Props.create(AskSupervisorCreator.class));
|
||||
}
|
||||
}
|
||||
30
akka-docs/src/test/java/jdocs/pattern/SupervisedAskSpec.java
Normal file
30
akka-docs/src/test/java/jdocs/pattern/SupervisedAskSpec.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package jdocs.pattern;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorRefFactory;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.util.Timeout;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
public class SupervisedAskSpec {
|
||||
|
||||
public Object execute(Class<? extends AbstractActor> someActor,
|
||||
Object message, Timeout timeout, ActorRefFactory actorSystem)
|
||||
throws Exception {
|
||||
// example usage
|
||||
try {
|
||||
ActorRef supervisorCreator = SupervisedAsk
|
||||
.createSupervisorCreator(actorSystem);
|
||||
CompletionStage<Object> finished = SupervisedAsk.askOf(supervisorCreator,
|
||||
Props.create(someActor), message, timeout);
|
||||
FiniteDuration d = timeout.duration();
|
||||
return finished.toCompletableFuture().get(d.length(), d.unit());
|
||||
} catch (Exception e) {
|
||||
// exception propagated by supervision
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue