rename akka-docs dir to docs (#62)

This commit is contained in:
PJ Fanning 2022-12-02 10:49:40 +01:00 committed by GitHub
parent 13dce0ec69
commit 708da8caec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1029 changed files with 2033 additions and 2039 deletions

View file

@ -0,0 +1,10 @@
/*
* Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs;
import org.scalatestplus.junit.JUnitSuite;
/** Base class for all runnable example tests written in Java */
public abstract class AbstractJavaTest extends JUnitSuite {}

View file

@ -0,0 +1,899 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import org.apache.pekko.actor.*;
import org.apache.pekko.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 org.apache.pekko.actor.CoordinatedShutdown;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
import org.apache.pekko.testkit.TestActors;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
// #import-props
import org.apache.pekko.actor.Props;
// #import-props
// #import-actorRef
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
// #import-actorRef
// #import-identify
import org.apache.pekko.actor.ActorIdentity;
import org.apache.pekko.actor.ActorSelection;
import org.apache.pekko.actor.Identify;
// #import-identify
// #import-ask
import static org.apache.pekko.pattern.Patterns.ask;
import static org.apache.pekko.pattern.Patterns.pipe;
import java.util.concurrent.CompletableFuture;
// #import-ask
// #import-gracefulStop
import static org.apache.pekko.pattern.Patterns.gracefulStop;
import org.apache.pekko.pattern.AskTimeoutException;
import java.util.concurrent.CompletionStage;
// #import-gracefulStop
// #import-terminated
import org.apache.pekko.actor.Terminated;
// #import-terminated
public class ActorDocTest extends AbstractJavaTest {
public static Config config =
ConfigFactory.parseString(
"akka {\n"
+ " loggers = [\"org.apache.pekko.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() {
TestKit.shutdownActorSystem(system);
}
public
// #context-actorOf
static 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
public static class SomeActor extends AbstractActor {
// #createReceive
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> System.out.println(s.toLowerCase())).build();
}
// #createReceive
}
public
// #well-structured
static 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
public
// #optimized
static 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
public static class ActorWithArgs extends AbstractActor {
private final String args;
public ActorWithArgs(String args) {
this.args = args;
}
@Override
public Receive createReceive() {
return receiveBuilder().matchAny(x -> {}).build();
}
}
public
// #props-factory
static class DemoActor extends AbstractActor {
/**
* Create Props for an actor of this type.
*
* @param magicNumber The magic number to be passed to this actors 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
public
// #props-factory
static 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
public
// #messages-in-companion
static class DemoMessagesActor extends AbstractLoggingActor {
public static 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
// dont 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://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 org.apache.pekko.actor.Status.Failure(e), getSelf());
throw e;
}
// #reply-exception
})
.build();
}
private String operation() {
return "Hi";
}
}
public
// #gracefulStop-actor
static 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.ofSeconds(5), 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();
}
}
public
// #swapper
static 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
public
// #swapper
static 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
}
// Commented out because this 'Props.create' overload is now 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
*/
}
public
// #receive-timeout
static class ReceiveTimeoutActor extends AbstractActor {
// #receive-timeout
ActorRef target = getContext().getSystem().deadLetters();
// #receive-timeout
public ReceiveTimeoutActor() {
// To set an initial delay
getContext().setReceiveTimeout(Duration.ofSeconds(10));
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(
"Hello",
s -> {
// To set in a response to a message
getContext().setReceiveTimeout(Duration.ofSeconds(1));
// #receive-timeout
target = getSender();
target.tell("Hello world", getSelf());
// #receive-timeout
})
.match(
ReceiveTimeout.class,
r -> {
// To turn it off
getContext().cancelReceiveTimeout();
// #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 class ReceiveTimeoutWithCancelActor extends AbstractActor {
private ActorRef target = getContext().getSystem().deadLetters();
public ReceiveTimeoutWithCancelActor() {
getContext().setReceiveTimeout(Duration.ofSeconds(1));
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(
"Hello",
s -> {
target = getSender();
target.tell("Hello world", getSelf());
getContext().setReceiveTimeout(Duration.ofMillis(200));
})
.match(
ReceiveTimeout.class,
r -> {
getContext().cancelReceiveTimeout();
target.tell("timeout", getSelf());
})
.build();
}
}
@Test
public void cancel_receiveTimeout() {
final ActorRef myActor = system.actorOf(Props.create(ReceiveTimeoutWithCancelActor.class));
new TestKit(system) {
{
myActor.tell("Hello", getRef());
expectMsgEquals("Hello world");
expectMsgEquals("timeout");
expectNoMessage(Duration.ofMillis(400));
}
};
}
public
// #hot-swap-actor
static 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?");
expectNoMessage(Duration.ofSeconds(1));
}
};
}
public
// #stash
static 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");
}
public
// #watch
static 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");
}
};
}
public
// #identify
static 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.ofSeconds(2), 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
final Duration t = Duration.ofSeconds(5);
// using 1000ms timeout
CompletableFuture<Object> future1 =
ask(actorA, "request", Duration.ofMillis(1000)).toCompletableFuture();
// using timeout from above
CompletableFuture<Object> future2 = ask(actorB, "another request", t).toCompletableFuture();
CompletableFuture<Result> transformed =
future1.thenCombine(future2, (x, s) -> new Result((String) x, (String) 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(org.apache.pekko.actor.Kill.getInstance(), ActorRef.noSender());
// expecting the actor to indeed terminate:
expectTerminated(Duration.ofSeconds(3), victim);
// #kill
}
};
}
@Test
public void usePoisonPill() {
new TestKit(system) {
{
ActorRef victim = system.actorOf(TestActors.echoActorProps());
watch(victim);
// #poison-pill
victim.tell(org.apache.pekko.actor.PoisonPill.getInstance(), ActorRef.noSender());
// #poison-pill
expectTerminated(Duration.ofSeconds(3), victim);
}
};
}
@Test
public void coordinatedShutdownActorTermination() {
ActorRef someActor = system.actorOf(Props.create(FirstActor.class));
someActor.tell(PoisonPill.getInstance(), ActorRef.noSender());
// https://github.com/akka/akka/issues/29056
// #coordinated-shutdown-addActorTerminationTask
CoordinatedShutdown.get(system)
.addActorTerminationTask(
CoordinatedShutdown.PhaseBeforeServiceUnbind(),
"someTaskName",
someActor,
Optional.of("stop"));
// #coordinated-shutdown-addActorTerminationTask
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #bytebufserializer-with-manifest
import org.apache.pekko.serialization.ByteBufferSerializer;
import org.apache.pekko.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, acquire 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
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
// #import
import org.apache.pekko.actor.Actor;
import org.apache.pekko.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() {
TestKit.shutdownActorSystem(system);
}
// 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("...");
}
};
}
}

View file

@ -0,0 +1,528 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://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 java.time.Duration;
import org.apache.pekko.actor.*;
import org.apache.pekko.dispatch.Mapper;
import org.apache.pekko.event.LoggingReceive;
import org.apache.pekko.japi.pf.DeciderBuilder;
import org.apache.pekko.pattern.Patterns;
import org.apache.pekko.util.Timeout;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import static org.apache.pekko.japi.Util.classTag;
import static org.apache.pekko.actor.SupervisorStrategy.restart;
import static org.apache.pekko.actor.SupervisorStrategy.stop;
import static org.apache.pekko.actor.SupervisorStrategy.escalate;
import static org.apache.pekko.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.ofSeconds(15));
}
@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 = Timeout.create(Duration.ofSeconds(5));
// 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()
.scheduleWithFixedDelay(
Duration.ZERO,
Duration.ofSeconds(1L),
getSelf(),
Do,
getContext().getDispatcher(),
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.ofSeconds(5),
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.ofSeconds(10),
getSelf(),
Reconnect,
getContext().getDispatcher(),
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

View file

@ -0,0 +1,224 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import org.apache.pekko.actor.*;
import org.apache.pekko.testkit.javadsl.TestKit;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import jdocs.AbstractJavaTest;
import java.util.Optional;
import java.time.Duration;
import static org.apache.pekko.pattern.Patterns.ask;
// #testkit
import org.apache.pekko.testkit.TestProbe;
import org.apache.pekko.testkit.ErrorFilter;
import org.apache.pekko.testkit.EventFilter;
import org.apache.pekko.testkit.TestEvent;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.pekko.japi.Util.immutableSeq;
import static org.junit.Assert.assertEquals;
import scala.concurrent.Await;
// #testkit
// #supervisor
import org.apache.pekko.japi.pf.DeciderBuilder;
import org.apache.pekko.actor.SupervisorStrategy;
// #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 = [\"org.apache.pekko.testkit.TestEventListener\"]\n"
+ " loglevel = \"WARNING\"\n"
+ " stdout-loglevel = \"WARNING\"\n"
+ "}\n");
public
// #supervisor
static class Supervisor extends AbstractActor {
// #strategy
private static SupervisorStrategy strategy =
new OneForOneStrategy(
10,
Duration.ofMinutes(1),
DeciderBuilder.match(ArithmeticException.class, e -> SupervisorStrategy.resume())
.match(NullPointerException.class, e -> SupervisorStrategy.restart())
.match(IllegalArgumentException.class, e -> SupervisorStrategy.stop())
.matchAny(o -> SupervisorStrategy.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
public
// #supervisor2
static class Supervisor2 extends AbstractActor {
// #strategy2
private static SupervisorStrategy strategy =
new OneForOneStrategy(
10,
Duration.ofMinutes(1),
DeciderBuilder.match(ArithmeticException.class, e -> SupervisorStrategy.resume())
.match(NullPointerException.class, e -> SupervisorStrategy.restart())
.match(IllegalArgumentException.class, e -> SupervisorStrategy.stop())
.matchAny(o -> SupervisorStrategy.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
public
// #child
static 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;
scala.concurrent.duration.Duration timeout =
scala.concurrent.duration.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.getEventStream().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());
assertEquals(42, Await.result(ask(child, "get", 5000), timeout));
child.tell(new ArithmeticException(), ActorRef.noSender());
assertEquals(42, Await.result(ask(child, "get", 5000), timeout));
// #resume
// #restart
child.tell(new NullPointerException(), ActorRef.noSender());
assertEquals(0, Await.result(ask(child, "get", 5000), timeout));
// #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);
assertEquals(0, Await.result(ask(child, "get", 5000), timeout));
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());
assertEquals(23, Await.result(ask(child, "get", 5000), timeout));
child.tell(new Exception(), ActorRef.noSender());
assertEquals(0, Await.result(ask(child, "get", 5000), timeout));
// #escalate-restart
// #testkit
}
}
// #testkit

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #imports
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.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

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://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

View file

@ -0,0 +1,217 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.japi.pf.FI;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.time.Duration;
import java.util.Optional;
public class InitializationDocTest extends AbstractJavaTest {
static ActorSystem system = null;
@BeforeClass
public static void beforeClass() {
system = ActorSystem.create("InitializationDocTest");
}
@AfterClass
public static void afterClass() {
TestKit.shutdownActorSystem(system);
}
public static 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();
}
}
static class GenericActorWithPredicateAlwaysResponse extends AbstractActor {
private boolean alwaysResponse() {
return true;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchUnchecked(
GenericMessage.class,
this::alwaysResponse,
(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());
expectNoMessage(Duration.ofSeconds(1));
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());
expectNoMessage();
genericTestActor.tell(nonEmptyGenericMessage, getRef());
expectMsgEquals("A");
}
};
}
@Test
public void actorShouldAlwaysRespondForEmptyMessage() {
new TestKit(system) {
{
ActorRef genericTestActor =
system.actorOf(
Props.create(GenericActorWithPredicateAlwaysResponse.class),
"genericActorWithPredicateAlwaysResponse");
GenericMessage<String> emptyGenericMessage = new GenericMessage<String>("");
GenericMessage<String> nonEmptyGenericMessage = new GenericMessage<String>("a");
genericTestActor.tell(emptyGenericMessage, getRef());
expectMsg("");
genericTestActor.tell(nonEmptyGenericMessage, getRef());
expectMsgEquals("A");
}
};
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Messages {
public
// #immutable-message
static 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 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;
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #imports
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.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

View file

@ -0,0 +1,13 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #my-bounded-classic-actor
import org.apache.pekko.dispatch.BoundedMessageQueueSemantics;
import org.apache.pekko.dispatch.RequiresMessageQueue;
public class MyBoundedActor extends MyActor
implements RequiresMessageQueue<BoundedMessageQueueSemantics> {}
// #my-bounded-classic-actor

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #my-stopping-actor
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.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

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #sample-actor
import org.apache.pekko.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

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import jdocs.AbstractJavaTest;
import org.apache.pekko.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);
expectNoMessage();
}
};
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #imports1
import java.time.Duration;
// #imports1
// #imports2
import org.apache.pekko.actor.Cancellable;
// #imports2
import jdocs.AbstractJavaTest;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.testkit.AkkaSpec;
import org.apache.pekko.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.ofMillis(50), testActor, "foo", system.dispatcher(), ActorRef.noSender());
// #schedule-one-off-message
// #schedule-one-off-thunk
system
.scheduler()
.scheduleOnce(
Duration.ofMillis(50),
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 something
})
.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()
.scheduleWithFixedDelay(
Duration.ZERO,
Duration.ofMillis(50),
tickActor,
"Tick",
system.dispatcher(),
ActorRef.noSender());
// This cancels further Ticks to be sent
cancellable.cancel();
// #schedule-recurring
system.stop(tickActor);
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2017-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor;
// #timers
import java.time.Duration;
import org.apache.pekko.actor.AbstractActorWithTimers;
// #timers
public class TimerDocTest {
public
// #timers
static class MyActor extends AbstractActorWithTimers {
private static Object TICK_KEY = "TickKey";
private static final class FirstTick {}
private static final class Tick {}
public MyActor() {
getTimers().startSingleTimer(TICK_KEY, new FirstTick(), Duration.ofMillis(500));
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
FirstTick.class,
message -> {
// do something useful here
getTimers().startTimerWithFixedDelay(TICK_KEY, new Tick(), Duration.ofSeconds(1));
})
.match(
Tick.class,
message -> {
// do something useful here
})
.build();
}
}
// #timers
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.fsm;
// #simple-imports
import org.apache.pekko.actor.AbstractFSM;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.japi.pf.UnitMatch;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.time.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.ofSeconds(1L),
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

View file

@ -0,0 +1,80 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.fsm;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import jdocs.AbstractJavaTest;
import org.apache.pekko.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);
expectNoMessage();
system.stop(buncher);
}
};
}
}
// #test-code

View file

@ -0,0 +1,109 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.fsm;
import org.apache.pekko.actor.ActorRef;
import java.util.List;
public class Events {
public
// #simple-events
static 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
public
// #simple-events
static 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
public
// #simple-events
static 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
public
// #simple-events
static enum Flush {
Flush
}
// #simple-events
}

View file

@ -0,0 +1,222 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.fsm;
import org.apache.pekko.actor.*;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import static jdocs.actor.fsm.FSMDocTest.StateType.*;
import static jdocs.actor.fsm.FSMDocTest.Messages.*;
import java.time.Duration;
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 enum StateType {
SomeState,
Processing,
Idle,
Active,
Error
}
public enum Messages {
WillDo,
Tick
}
public 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.ofSeconds(5))
.replying(WillDo);
}));
// #modifier-syntax
// #NullFunction
when(SomeState, AbstractFSM.NullFunction());
// #NullFunction
// #transition-syntax
onTransition(
matchState(
Idle,
Active,
() -> startTimerWithFixedDelay("timeout", Tick, Duration.ofSeconds(1L)))
.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
}
public
// #logging-fsm
static 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/"));
}
};
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.io.dns;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.io.Dns;
import org.apache.pekko.io.dns.DnsProtocol;
import static org.apache.pekko.pattern.Patterns.ask;
import scala.Option;
import java.time.Duration;
import java.util.concurrent.CompletionStage;
public class DnsCompileOnlyDocTest {
public static void example() {
ActorSystem system = ActorSystem.create();
ActorRef actorRef = null;
final Duration timeout = Duration.ofMillis(1000L);
// #resolve
Option<DnsProtocol.Resolved> initial =
Dns.get(system)
.cache()
.resolve(
new DnsProtocol.Resolve("google.com", DnsProtocol.ipRequestType()),
system,
actorRef);
Option<DnsProtocol.Resolved> cached =
Dns.get(system)
.cache()
.cached(new DnsProtocol.Resolve("google.com", DnsProtocol.ipRequestType()));
// #resolve
{
// #actor-api-inet-address
final ActorRef dnsManager = Dns.get(system).manager();
CompletionStage<Object> resolved =
ask(
dnsManager,
new DnsProtocol.Resolve("google.com", DnsProtocol.ipRequestType()),
timeout);
// #actor-api-inet-address
}
{
// #actor-api-async
final ActorRef dnsManager = Dns.get(system).manager();
CompletionStage<Object> resolved =
ask(dnsManager, DnsProtocol.resolve("google.com"), timeout);
// #actor-api-async
}
{
// #srv
final ActorRef dnsManager = Dns.get(system).manager();
CompletionStage<Object> resolved =
ask(dnsManager, DnsProtocol.resolve("google.com", DnsProtocol.srvRequestType()), timeout);
// #srv
}
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
// #blocking-in-actor
import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;
public class BlockingActor extends AbstractBehavior<Integer> {
public static Behavior<Integer> create() {
return Behaviors.setup(BlockingActor::new);
}
private BlockingActor(ActorContext<Integer> context) {
super(context);
}
@Override
public Receive<Integer> createReceive() {
return newReceiveBuilder()
.onMessage(
Integer.class,
i -> {
// DO NOT DO THIS HERE: this is an example of incorrect code,
// better alternatives are described further on.
// block for 5 seconds, representing blocking I/O, etc
Thread.sleep(5000);
System.out.println("Blocking operation finished: " + i);
return Behaviors.same();
})
.build();
}
}
// #blocking-in-actor

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;
public class BlockingDispatcherTest {
public static void main(String args[]) {
// #blocking-main
Behavior<Void> root =
Behaviors.setup(
context -> {
for (int i = 0; i < 100; i++) {
context.spawn(BlockingActor.create(), "BlockingActor-" + i).tell(i);
context.spawn(PrintActor.create(), "PrintActor-" + i).tell(i);
}
return Behaviors.ignore();
});
// #blocking-main
ActorSystem<Void> system = ActorSystem.<Void>create(root, "BlockingDispatcherTest");
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import org.apache.pekko.Done;
import org.apache.pekko.actor.Cancellable;
import org.apache.pekko.actor.CoordinatedShutdown;
import org.apache.pekko.actor.typed.ActorRef;
import org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.actor.typed.Behavior;
import org.apache.pekko.actor.typed.javadsl.*;
// #coordinated-shutdown-addTask
import static org.apache.pekko.actor.typed.javadsl.AskPattern.ask;
// #coordinated-shutdown-addTask
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class CoordinatedActorShutdownTest {
// #coordinated-shutdown-addTask
public static class MyActor extends AbstractBehavior<MyActor.Messages> {
interface Messages {}
// ...
static final class Stop implements Messages {
final ActorRef<Done> replyTo;
Stop(ActorRef<Done> replyTo) {
this.replyTo = replyTo;
}
}
// #coordinated-shutdown-addTask
public static Behavior<Messages> create() {
return Behaviors.setup(MyActor::new);
}
private MyActor(ActorContext<Messages> context) {
super(context);
}
// #coordinated-shutdown-addTask
@Override
public Receive<Messages> createReceive() {
return newReceiveBuilder().onMessage(Stop.class, this::stop).build();
}
private Behavior<Messages> stop(Stop stop) {
// shut down the actor internal
// ...
stop.replyTo.tell(Done.done());
return Behaviors.stopped();
}
}
// #coordinated-shutdown-addTask
public static class Root extends AbstractBehavior<Void> {
public static Behavior<Void> create() {
return Behaviors.setup(
context -> {
ActorRef<MyActor.Messages> myActor = context.spawn(MyActor.create(), "my-actor");
ActorSystem<Void> system = context.getSystem();
// #coordinated-shutdown-addTask
CoordinatedShutdown.get(system)
.addTask(
CoordinatedShutdown.PhaseBeforeServiceUnbind(),
"someTaskName",
() ->
ask(myActor, MyActor.Stop::new, Duration.ofSeconds(5), system.scheduler()));
// #coordinated-shutdown-addTask
return Behaviors.empty();
});
}
private Root(ActorContext<Void> context) {
super(context);
}
@Override
public Receive<Void> createReceive() {
return newReceiveBuilder().build();
}
}
private CompletionStage<Done> cleanup() {
return CompletableFuture.completedFuture(Done.done());
}
public void mount() {
ActorSystem<Void> system = ActorSystem.create(Root.create(), "main");
// #coordinated-shutdown-cancellable
Cancellable cancellable =
CoordinatedShutdown.get(system)
.addCancellableTask(
CoordinatedShutdown.PhaseBeforeServiceUnbind(), "someTaskCleanup", () -> cleanup());
// much later...
cancellable.cancel();
// #coordinated-shutdown-cancellable
// #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
// shut down with `ActorSystemTerminateReason`
system.terminate();
// or define a specific reason
class UserInitiatedShutdown implements CoordinatedShutdown.Reason {
@Override
public String toString() {
return "UserInitiatedShutdown";
}
}
CompletionStage<Done> done =
CoordinatedShutdown.get(system).runAll(new UserInitiatedShutdown());
// #coordinated-shutdown-run
}
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import scala.concurrent.ExecutionContextExecutor;
import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;
@SuppressWarnings("unused")
public class DispatcherDocTest {
private final ActorSystem<Void> system = null;
private final ActorContext<Void> context = null;
public void defineDispatcherInCode() {
// #defining-dispatcher-in-code
ActorRef<Integer> myActor =
context.spawn(
PrintActor.create(), "PrintActor", DispatcherSelector.fromConfig("my-dispatcher"));
// #defining-dispatcher-in-code
}
public void defineFixedPoolSizeDispatcher() {
// #defining-fixed-pool-size-dispatcher
ActorRef<Integer> myActor =
context.spawn(
PrintActor.create(),
"PrintActor",
DispatcherSelector.fromConfig("blocking-io-dispatcher"));
// #defining-fixed-pool-size-dispatcher
}
public void definePinnedDispatcher() {
// #defining-pinned-dispatcher
ActorRef<Integer> myActor =
context.spawn(
PrintActor.create(),
"PrintActor",
DispatcherSelector.fromConfig("my-pinned-dispatcher"));
// #defining-pinned-dispatcher
}
public void compileLookup() {
// #lookup
// this is scala.concurrent.ExecutionContextExecutor, which implements
// both scala.concurrent.ExecutionContext (for use with Futures, Scheduler, etc.)
// and java.util.concurrent.Executor (for use with CompletableFuture etc.)
final ExecutionContextExecutor ex =
system.dispatchers().lookup(DispatcherSelector.fromConfig("my-dispatcher"));
// #lookup
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;
// #print-actor
class PrintActor extends AbstractBehavior<Integer> {
public static Behavior<Integer> create() {
return Behaviors.setup(PrintActor::new);
}
private PrintActor(ActorContext<Integer> context) {
super(context);
}
@Override
public Receive<Integer> createReceive() {
return newReceiveBuilder()
.onMessage(
Integer.class,
i -> {
System.out.println("PrintActor: " + i);
return Behaviors.same();
})
.build();
}
}
// #print-actor

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;
// #separate-dispatcher
class SeparateDispatcherFutureActor extends AbstractBehavior<Integer> {
private final Executor ec;
public static Behavior<Integer> create() {
return Behaviors.setup(SeparateDispatcherFutureActor::new);
}
private SeparateDispatcherFutureActor(ActorContext<Integer> context) {
super(context);
ec =
context
.getSystem()
.dispatchers()
.lookup(DispatcherSelector.fromConfig("my-blocking-dispatcher"));
}
@Override
public Receive<Integer> createReceive() {
return newReceiveBuilder()
.onMessage(
Integer.class,
i -> {
triggerFutureBlockingOperation(i, ec);
return Behaviors.same();
})
.build();
}
private static void triggerFutureBlockingOperation(Integer i, Executor ec) {
System.out.println("Calling blocking Future on separate dispatcher: " + i);
CompletableFuture<Integer> f =
CompletableFuture.supplyAsync(
() -> {
try {
Thread.sleep(5000);
System.out.println("Blocking future finished: " + i);
return i;
} catch (InterruptedException e) {
return -1;
}
},
ec);
}
}
// #separate-dispatcher

View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.actor.typed;
import org.apache.pekko.actor.typed.ActorRef;
import org.apache.pekko.actor.typed.Behavior;
import org.apache.pekko.actor.typed.javadsl.AbstractBehavior;
import org.apache.pekko.actor.typed.javadsl.ActorContext;
import org.apache.pekko.actor.typed.javadsl.Receive;
import org.apache.pekko.actor.typed.javadsl.AskPattern;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
interface SharedMutableStateDocTest {
static CompletableFuture<String> expensiveCalculation() {
throw new UnsupportedOperationException("just a sample signature");
}
class Query {
public final ActorRef<String> replyTo;
public Query(ActorRef<String> replyTo) {
this.replyTo = replyTo;
}
}
// #mutable-state
class MyActor extends AbstractBehavior<MyActor.Command> {
interface Command {}
class Message implements Command {
public final ActorRef<Object> otherActor;
public Message(ActorRef<Object> replyTo) {
this.otherActor = replyTo;
}
}
class UpdateState implements Command {
public final String newState;
public UpdateState(String newState) {
this.newState = newState;
}
}
private String state = "";
private Set<String> mySet = new HashSet<>();
public MyActor(ActorContext<Command> context) {
super(context);
}
@Override
public Receive<Command> createReceive() {
return newReceiveBuilder()
.onMessage(Message.class, this::onMessage)
.onMessage(UpdateState.class, this::onUpdateState)
.build();
}
private Behavior<Command> onMessage(Message message) {
// Very bad: shared mutable object allows
// the other actor to mutate your own state,
// or worse, you might get weird race conditions
message.otherActor.tell(mySet);
// Example of incorrect approach
// Very bad: shared mutable state will cause your
// application to break in weird ways
CompletableFuture.runAsync(
() -> {
state = "This will race";
});
// Example of incorrect approach
// Very bad: shared mutable state will cause your
// application to break in weird ways
expensiveCalculation()
.whenComplete(
(result, failure) -> {
if (result != null) state = "new state: " + result;
});
// Example of correct approach
// Turn the future result into a message that is sent to
// self when future completes
CompletableFuture<String> futureResult = expensiveCalculation();
getContext()
.pipeToSelf(
futureResult,
(result, failure) -> {
if (result != null) return new UpdateState(result);
else throw new RuntimeException(failure);
});
// Another example of incorrect approach
// mutating actor state from ask future callback
CompletionStage<String> response =
AskPattern.ask(
message.otherActor,
Query::new,
Duration.ofSeconds(3),
getContext().getSystem().scheduler());
response.whenComplete(
(result, failure) -> {
if (result != null) state = "new state: " + result;
});
// use context.ask instead, turns the completion
// into a message sent to self
getContext()
.ask(
String.class,
message.otherActor,
Duration.ofSeconds(3),
Query::new,
(result, failure) -> {
if (result != null) return new UpdateState(result);
else throw new RuntimeException(failure);
});
return this;
}
private Behavior<Command> onUpdateState(UpdateState command) {
// safe as long as `newState` is immutable, if it is mutable we'd need to
// make a defensive copy
this.state = command.newState;
return this;
}
}
// #mutable-state
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.circuitbreaker;
// #imports1
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.LoggingAdapter;
import java.time.Duration;
import org.apache.pekko.pattern.CircuitBreaker;
import org.apache.pekko.event.Logging;
import static org.apache.pekko.pattern.Patterns.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().getDispatcher(),
getContext().getSystem().getScheduler(),
5,
Duration.ofSeconds(10),
Duration.ofMinutes(1))
.addOnOpenListener(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().getDispatcher())
.to(sender()))
.match(
String.class,
"block for me"::equals,
m -> {
sender().tell(breaker.callWithSyncCircuitBreaker(this::dangerousCall), self());
})
.build();
}
// #circuit-breaker-usage
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.circuitbreaker;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.pattern.CircuitBreaker;
import java.time.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().getDispatcher(),
getContext().getSystem().getScheduler(),
5,
Duration.ofSeconds(10),
Duration.ofMinutes(1));
}
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;
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.circuitbreaker;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ReceiveTimeout;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.pattern.CircuitBreaker;
import java.time.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().getDispatcher(),
getContext().getSystem().getScheduler(),
5,
Duration.ofSeconds(10),
Duration.ofMinutes(1))
.addOnOpenListener(this::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
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import org.apache.pekko.testkit.javadsl.TestKit;
import com.typesafe.config.ConfigFactory;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import jdocs.AbstractJavaTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
// #join-seed-nodes-imports
import org.apache.pekko.actor.Address;
import org.apache.pekko.cluster.Cluster;
// #join-seed-nodes-imports
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.cluster.Member;
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
}
// compile only
@SuppressWarnings("unused")
public void demonstrateDataCenter() {
// #dcAccess
final Cluster cluster = Cluster.get(system);
// this node's data center
String dc = cluster.selfDataCenter();
// all known data centers
Set<String> allDc = cluster.state().getAllDataCenters();
// a specific member's data center
Member aMember = cluster.state().getMembers().iterator().next();
String aDc = aMember.dataCenter();
// #dcAccess
}
// compile only
@SuppressWarnings("unused")
public void demonstrateJoinSeedNodes() {
// #join-seed-nodes
final Cluster cluster = Cluster.get(system);
List<Address> list =
new LinkedList<>(); // replace this with your method to dynamically get seed nodes
cluster.joinSeedNodes(list);
// #join-seed-nodes
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import org.apache.pekko.actor.AbstractActor;
import static org.apache.pekko.pattern.Patterns.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

View file

@ -0,0 +1,119 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.time.Duration;
import org.apache.pekko.actor.Props;
import org.apache.pekko.cluster.metrics.AdaptiveLoadBalancingGroup;
import org.apache.pekko.cluster.metrics.AdaptiveLoadBalancingPool;
import org.apache.pekko.cluster.metrics.HeapMetricsSelector;
import org.apache.pekko.cluster.metrics.SystemLoadAverageMetricsSelector;
import org.apache.pekko.cluster.routing.ClusterRouterGroup;
import org.apache.pekko.cluster.routing.ClusterRouterGroupSettings;
import org.apache.pekko.cluster.routing.ClusterRouterPool;
import org.apache.pekko.cluster.routing.ClusterRouterPoolSettings;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ReceiveTimeout;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.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.ofSeconds(10));
}
@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;
Set<String> useRoles = new HashSet<>(Arrays.asList("backend"));
ActorRef backend =
getContext()
.actorOf(
new ClusterRouterGroup(
new AdaptiveLoadBalancingGroup(
HeapMetricsSelector.getInstance(), Collections.<String>emptyList()),
new ClusterRouterGroupSettings(
totalInstances, routeesPaths, allowLocalRoutees, useRoles))
.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;
Set<String> useRoles = new HashSet<>(Arrays.asList("backend"));
ActorRef backend =
getContext()
.actorOf(
new ClusterRouterPool(
new AdaptiveLoadBalancingPool(
SystemLoadAverageMetricsSelector.getInstance(), 0),
new ClusterRouterPoolSettings(
totalInstances, maxInstancesPerNode, allowLocalRoutees, useRoles))
.props(Props.create(FactorialBackend.class)),
"factorialBackendRouter3");
// #router-deploy-in-code
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.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 {
system.getWhenTerminated().toCompletableFuture().get(10, TimeUnit.SECONDS);
} catch (Exception e) {
System.exit(-1);
}
}
}.start();
}
});
// #registerOnRemoved
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
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;
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
// #metrics-listener
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ClusterEvent.CurrentClusterState;
import org.apache.pekko.cluster.metrics.ClusterMetricsChanged;
import org.apache.pekko.cluster.metrics.NodeMetrics;
import org.apache.pekko.cluster.metrics.StandardMetrics;
import org.apache.pekko.cluster.metrics.StandardMetrics.HeapMemory;
import org.apache.pekko.cluster.metrics.StandardMetrics.Cpu;
import org.apache.pekko.cluster.metrics.ClusterMetricsExtension;
import org.apache.pekko.event.Logging;
import org.apache.pekko.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

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ClusterEvent;
import org.apache.pekko.cluster.ClusterEvent.MemberEvent;
import org.apache.pekko.cluster.ClusterEvent.MemberUp;
import org.apache.pekko.cluster.ClusterEvent.MemberRemoved;
import org.apache.pekko.cluster.ClusterEvent.UnreachableMember;
import org.apache.pekko.event.Logging;
import org.apache.pekko.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();
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ClusterEvent.CurrentClusterState;
import org.apache.pekko.cluster.ClusterEvent.MemberEvent;
import org.apache.pekko.cluster.ClusterEvent.MemberUp;
import org.apache.pekko.cluster.ClusterEvent.MemberRemoved;
import org.apache.pekko.cluster.ClusterEvent.UnreachableMember;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
public class SimpleClusterListener2 extends AbstractActor {
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
// #join
Cluster cluster = Cluster.get(getContext().getSystem());
// #join
// subscribe to cluster changes
@Override
public void preStart() {
// #join
cluster.join(cluster.selfAddress());
// #join
// #subscribe
cluster.subscribe(getSelf(), MemberEvent.class, UnreachableMember.class);
// #subscribe
// #register-on-memberup
cluster.registerOnMemberUp(
() -> cluster.subscribe(getSelf(), MemberEvent.class, UnreachableMember.class));
// #register-on-memberup
}
// 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();
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.util.ArrayList;
import java.util.List;
import java.time.Duration;
import jdocs.cluster.StatsMessages.JobFailed;
import jdocs.cluster.StatsMessages.StatsResult;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ReceiveTimeout;
import org.apache.pekko.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.ofSeconds(3));
}
@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

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
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

View file

@ -0,0 +1,111 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jdocs.cluster.StatsMessages.JobFailed;
import jdocs.cluster.StatsMessages.StatsJob;
import jdocs.cluster.StatsMessages.StatsResult;
import java.util.concurrent.ThreadLocalRandom;
import java.time.Duration;
import org.apache.pekko.actor.ActorSelection;
import org.apache.pekko.actor.Address;
import org.apache.pekko.actor.Cancellable;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ClusterEvent.UnreachableMember;
import org.apache.pekko.cluster.ClusterEvent.ReachableMember;
import org.apache.pekko.cluster.ClusterEvent.CurrentClusterState;
import org.apache.pekko.cluster.ClusterEvent.MemberEvent;
import org.apache.pekko.cluster.ClusterEvent.MemberUp;
import org.apache.pekko.cluster.ClusterEvent.ReachabilityEvent;
import org.apache.pekko.cluster.Member;
import org.apache.pekko.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;
Duration interval = Duration.ofMillis(2);
tickTask =
getContext()
.getSystem()
.scheduler()
.scheduleWithFixedDelay(
interval, interval, getSelf(), "tick", getContext().getDispatcher(), 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();
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import com.typesafe.config.ConfigFactory;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.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");
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.PoisonPill;
import org.apache.pekko.actor.Props;
import org.apache.pekko.cluster.singleton.ClusterSingletonManager;
import org.apache.pekko.cluster.singleton.ClusterSingletonManagerSettings;
import org.apache.pekko.cluster.singleton.ClusterSingletonProxy;
import org.apache.pekko.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.classic.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
}
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import org.apache.pekko.cluster.routing.ClusterRouterGroup;
import org.apache.pekko.cluster.routing.ClusterRouterGroupSettings;
import org.apache.pekko.cluster.routing.ClusterRouterPool;
import org.apache.pekko.cluster.routing.ClusterRouterPoolSettings;
import org.apache.pekko.routing.ConsistentHashingGroup;
import org.apache.pekko.routing.ConsistentHashingPool;
import jdocs.cluster.StatsMessages.StatsJob;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
import org.apache.pekko.routing.FromConfig;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
// #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;
Set<String> useRoles = new HashSet<>(Arrays.asList("compute"));
ActorRef workerRouter =
getContext()
.actorOf(
new ClusterRouterGroup(
new ConsistentHashingGroup(routeesPaths),
new ClusterRouterGroupSettings(
totalInstances, routeesPaths, allowLocalRoutees, useRoles))
.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;
Set<String> useRoles = new HashSet<>(Arrays.asList("compute"));
ActorRef workerRouter =
getContext()
.actorOf(
new ClusterRouterPool(
new ConsistentHashingPool(0),
new ClusterRouterPoolSettings(
totalInstances, maxInstancesPerNode, allowLocalRoutees, useRoles))
.props(Props.create(StatsWorker.class)),
"workerRouter3");
// #router-deploy-in-code
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import java.util.HashMap;
import java.util.Map;
import org.apache.pekko.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

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster;
import static jdocs.cluster.TransformationMessages.BACKEND_REGISTRATION;
import jdocs.cluster.TransformationMessages.TransformationJob;
import jdocs.cluster.TransformationMessages.TransformationResult;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ClusterEvent.CurrentClusterState;
import org.apache.pekko.cluster.ClusterEvent.MemberUp;
import org.apache.pekko.cluster.Member;
import org.apache.pekko.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

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
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 org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Terminated;
import org.apache.pekko.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

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
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

View file

@ -0,0 +1,34 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster.singleton;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.SupervisorStrategy;
// #singleton-supervisor-actor-usage-imports
import org.apache.pekko.actor.PoisonPill;
import org.apache.pekko.actor.Props;
import org.apache.pekko.cluster.singleton.ClusterSingletonManager;
import org.apache.pekko.cluster.singleton.ClusterSingletonManagerSettings;
// #singleton-supervisor-actor-usage-imports
abstract class ClusterSingletonSupervision extends AbstractActor {
public ActorRef createSingleton(String name, Props props, SupervisorStrategy supervisorStrategy) {
// #singleton-supervisor-actor-usage
return getContext()
.system()
.actorOf(
ClusterSingletonManager.props(
Props.create(
SupervisorActor.class, () -> new SupervisorActor(props, supervisorStrategy)),
PoisonPill.getInstance(),
ClusterSingletonManagerSettings.create(getContext().system())),
name = name);
// #singleton-supervisor-actor-usage
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.cluster.singleton;
// #singleton-supervisor-actor
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.AbstractActor.Receive;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.SupervisorStrategy;
public class SupervisorActor extends AbstractActor {
final Props childProps;
final SupervisorStrategy supervisorStrategy;
final ActorRef child;
SupervisorActor(Props childProps, SupervisorStrategy supervisorStrategy) {
this.childProps = childProps;
this.supervisorStrategy = supervisorStrategy;
this.child = getContext().actorOf(childProps, "supervised-child");
}
@Override
public SupervisorStrategy supervisorStrategy() {
return supervisorStrategy;
}
@Override
public Receive createReceive() {
return receiveBuilder().matchAny(msg -> child.forward(msg, getContext())).build();
}
}
// #singleton-supervisor-actor

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.config;
// #imports
import org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.actor.typed.Behavior;
import org.apache.pekko.actor.typed.javadsl.Behaviors;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
// #imports
import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit;
public class ConfigDocTest {
private Behavior<Void> rootBehavior = Behaviors.empty();
public void customConfig() {
// #custom-config
Config customConf = ConfigFactory.parseString("akka.log-config-on-start = on");
// ConfigFactory.load sandwiches customConfig between default reference
// config and default overrides, and then resolves it.
ActorSystem<Void> system =
ActorSystem.create(rootBehavior, "MySystem", ConfigFactory.load(customConf));
// #custom-config
ActorTestKit.shutdown(system);
}
public void compileOnlyPrintConfig() {
// #dump-config
ActorSystem<Void> system = ActorSystem.create(rootBehavior, "MySystem");
system.logConfiguration();
// #dump-config
}
public void compileOnlySeparateApps() {
// #separate-apps
Config config = ConfigFactory.load();
ActorSystem<Void> app1 =
ActorSystem.create(rootBehavior, "MyApp1", config.getConfig("myapp1").withFallback(config));
ActorSystem<Void> app2 =
ActorSystem.create(
rootBehavior,
"MyApp2",
config.getConfig("myapp2").withOnlyPath("akka").withFallback(config));
// #separate-apps
}
public ActorSystem createConfiguredSystem() {
// #custom-config-2
// 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(rootBehavior, "myname", complete);
// #custom-config-2
return system;
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.coordination;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.coordination.lease.LeaseSettings;
import org.apache.pekko.coordination.lease.javadsl.Lease;
import org.apache.pekko.coordination.lease.javadsl.LeaseProvider;
import org.apache.pekko.testkit.javadsl.TestKit;
import docs.coordination.LeaseDocSpec;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.scalatestplus.junit.JUnitSuite;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
@SuppressWarnings("unused")
public class LeaseDocTest extends JUnitSuite {
// #lease-example
static class SampleLease extends Lease {
private LeaseSettings settings;
public SampleLease(LeaseSettings settings) {
this.settings = settings;
}
@Override
public LeaseSettings getSettings() {
return settings;
}
@Override
public CompletionStage<Boolean> acquire() {
return CompletableFuture.completedFuture(true);
}
@Override
public CompletionStage<Boolean> acquire(Consumer<Optional<Throwable>> leaseLostCallback) {
return CompletableFuture.completedFuture(true);
}
@Override
public CompletionStage<Boolean> release() {
return CompletableFuture.completedFuture(true);
}
@Override
public boolean checkLease() {
return true;
}
}
// #lease-example
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("LeaseDocTest", LeaseDocSpec.config());
}
@AfterClass
public static void teardown() {
TestKit.shutdownActorSystem(system);
system = null;
}
private void doSomethingImportant(Optional<Throwable> leaseLostReason) {}
@Test
public void javaLeaseBeLoadableFromJava() {
// #lease-usage
Lease lease =
LeaseProvider.get(system).getLease("<name of the lease>", "jdocs-lease", "<owner name>");
CompletionStage<Boolean> acquired = lease.acquire();
boolean stillAcquired = lease.checkLease();
CompletionStage<Boolean> released = lease.release();
// #lease-usage
// #lost-callback
lease.acquire(this::doSomethingImportant);
// #lost-callback
// #cluster-owner
// String owner = Cluster.get(system).selfAddress().hostPort();
// #cluster-owner
}
@Test
public void scalaLeaseBeLoadableFromJava() {
Lease lease =
LeaseProvider.get(system).getLease("<name of the lease>", "docs-lease", "<owner name>");
CompletionStage<Boolean> acquired = lease.acquire();
boolean stillAcquired = lease.checkLease();
CompletionStage<Boolean> released = lease.release();
lease.acquire(this::doSomethingImportant);
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.ddata;
// #data-bot
import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Cancellable;
import org.apache.pekko.cluster.Cluster;
import org.apache.pekko.cluster.ddata.DistributedData;
import org.apache.pekko.cluster.ddata.Key;
import org.apache.pekko.cluster.ddata.ORSet;
import org.apache.pekko.cluster.ddata.ORSetKey;
import org.apache.pekko.cluster.ddata.Replicator;
import org.apache.pekko.cluster.ddata.Replicator.Changed;
import org.apache.pekko.cluster.ddata.Replicator.Subscribe;
import org.apache.pekko.cluster.ddata.Replicator.Update;
import org.apache.pekko.cluster.ddata.Replicator.UpdateResponse;
import org.apache.pekko.cluster.ddata.SelfUniqueAddress;
import org.apache.pekko.event.Logging;
import org.apache.pekko.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 SelfUniqueAddress node =
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
private final Cancellable tickTask =
getContext()
.getSystem()
.scheduler()
.scheduleWithFixedDelay(
Duration.ofSeconds(5),
Duration.ofSeconds(5),
getSelf(),
TICK,
getContext().getDispatcher(),
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 -> receiveUpdateResponse())
.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 receiveUpdateResponse() {
// 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

View file

@ -0,0 +1,469 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://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 org.apache.pekko.actor.*;
import org.apache.pekko.testkit.javadsl.TestKit;
import com.typesafe.config.ConfigFactory;
import docs.ddata.DistributedDataDocSpec;
import jdocs.AbstractJavaTest;
import java.time.Duration;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.apache.pekko.cluster.ddata.*;
import org.apache.pekko.japi.pf.ReceiveBuilder;
import static org.apache.pekko.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 SelfUniqueAddress node =
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
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.ofSeconds(1));
replicator.tell(
new Replicator.Update<GSet<String>>(
set1Key, GSet.create(), writeTo3, curr -> curr.add("hello")),
getSelf());
final WriteConsistency writeMajority = new WriteMajority(Duration.ofSeconds(5));
replicator.tell(
new Replicator.Update<ORSet<String>>(
set2Key, ORSet.create(), writeMajority, curr -> curr.add(node, "hello")),
getSelf());
final WriteConsistency writeAll = new WriteAll(Duration.ofSeconds(5));
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
final WriteConsistency writeTwo = new WriteTo(2, Duration.ofSeconds(3));
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.ofSeconds(1));
replicator.tell(new Replicator.Get<GSet<String>>(set1Key, readFrom3), getSelf());
final ReadConsistency readMajority = new ReadMajority(Duration.ofSeconds(5));
replicator.tell(new Replicator.Get<ORSet<String>>(set2Key, readMajority), getSelf());
final ReadConsistency readAll = new ReadAll(Duration.ofSeconds(5));
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.ofSeconds(3));
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.ofSeconds(5));
replicator.tell(new Delete<PNCounter>(counter1Key, writeMajority), getSelf());
})
.build();
}
}
// #delete
public void demonstratePNCounter() {
// #pncounter
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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 SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
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);
}
}

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.ddata;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.time.Duration;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.cluster.ddata.*;
import org.apache.pekko.cluster.ddata.Replicator.GetFailure;
import org.apache.pekko.cluster.ddata.Replicator.GetResponse;
import org.apache.pekko.cluster.ddata.Replicator.GetSuccess;
import org.apache.pekko.cluster.ddata.Replicator.NotFound;
import org.apache.pekko.cluster.ddata.Replicator.ReadConsistency;
import org.apache.pekko.cluster.ddata.Replicator.ReadMajority;
import org.apache.pekko.cluster.ddata.Replicator.Update;
import org.apache.pekko.cluster.ddata.Replicator.UpdateFailure;
import org.apache.pekko.cluster.ddata.Replicator.UpdateSuccess;
import org.apache.pekko.cluster.ddata.Replicator.UpdateTimeout;
import org.apache.pekko.cluster.ddata.Replicator.WriteConsistency;
import org.apache.pekko.cluster.ddata.Replicator.WriteMajority;
@SuppressWarnings("unchecked")
public class ShoppingCart extends AbstractActor {
// #read-write-majority
private final WriteConsistency writeMajority = new WriteMajority(Duration.ofSeconds(3));
private static final ReadConsistency readMajority = new ReadMajority(Duration.ofSeconds(3));
// #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 SelfUniqueAddress node =
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
@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();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.ddata;
import java.util.HashSet;
import java.util.Set;
import org.apache.pekko.cluster.ddata.AbstractReplicatedData;
import org.apache.pekko.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

View file

@ -0,0 +1,89 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://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 org.apache.pekko.actor.ExtendedActorSystem;
import org.apache.pekko.cluster.ddata.GSet;
import org.apache.pekko.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

View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://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 org.apache.pekko.actor.ExtendedActorSystem;
import org.apache.pekko.cluster.ddata.GSet;
import org.apache.pekko.cluster.ddata.protobuf.AbstractSerializationSupport;
import org.apache.pekko.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

View file

@ -0,0 +1,31 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.ddata.protobuf;
import jdocs.ddata.TwoPhaseSet;
import org.apache.pekko.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
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.discovery;
import org.apache.pekko.actor.ActorSystem;
// #lookup-dns
import org.apache.pekko.discovery.Discovery;
import org.apache.pekko.discovery.ServiceDiscovery;
// #lookup-dns
import org.apache.pekko.testkit.javadsl.TestKit;
import docs.discovery.DnsDiscoveryDocSpec;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.scalatestplus.junit.JUnitSuite;
import java.time.Duration;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unused")
public class DnsDiscoveryDocTest extends JUnitSuite {
private static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("DnsDiscoveryDocTest", DnsDiscoveryDocSpec.config());
}
@AfterClass
public static void teardown() {
TestKit.shutdownActorSystem(system);
system = null;
}
@Test
public void dnsDiscoveryShouldResolveAkkaIo() throws Exception {
try {
// #lookup-dns
ServiceDiscovery discovery = Discovery.get(system).discovery();
// ...
CompletionStage<ServiceDiscovery.Resolved> result =
discovery.lookup("foo", Duration.ofSeconds(3));
// #lookup-dns
result.toCompletableFuture().get(5, TimeUnit.SECONDS);
} catch (Exception e) {
system.log().warning("Failed lookup akka.io, but ignoring: " + e);
// don't fail this test
}
}
}

View file

@ -0,0 +1,269 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.dispatcher;
import org.apache.pekko.dispatch.ControlMessage;
import org.apache.pekko.dispatch.RequiresMessageQueue;
import org.apache.pekko.testkit.AkkaSpec;
import org.apache.pekko.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.ExecutionContextExecutor;
// #imports
import org.apache.pekko.actor.*;
// #imports
// #imports-prio
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
// #imports-prio
// #imports-prio-mailbox
import org.apache.pekko.dispatch.PriorityGenerator;
import org.apache.pekko.dispatch.UnboundedStablePriorityMailbox;
import org.apache.pekko.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.ExecutionContextExecutor, which implements
// both scala.concurrent.ExecutionContext (for use with Futures, Scheduler, etc.)
// and java.util.concurrent.Executor (for use with CompletableFuture etc.)
final ExecutionContextExecutor 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);
}
public
// #prio-mailbox
static 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
public
// #control-aware-mailbox-messages
static 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"));
}
public
// #require-mailbox-on-actor
static 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));
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.dispatcher;
// #mailbox-implementation-example
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.dispatch.Envelope;
import org.apache.pekko.dispatch.MailboxType;
import org.apache.pekko.dispatch.MessageQueue;
import org.apache.pekko.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) {
while (!queue.isEmpty()) {
deadLetters.enqueue(owner, dequeue());
}
}
}
// 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

View file

@ -0,0 +1,10 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.dispatcher;
// #mailbox-marker-interface
// Marker interface used for mailbox requirements mapping
public interface MyUnboundedMessageQueueSemantics {}
// #mailbox-marker-interface

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2013-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.duration;
// #import
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.Deadline;
import static org.junit.Assert.assertTrue;
// #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);
assertTrue(diff.lt(fivesec));
assertTrue(Duration.Zero().lt(Duration.Inf()));
// #dsl
// #deadline
final Deadline deadline = Duration.create(10, "seconds").fromNow();
final Duration rest = deadline.timeLeft();
// #deadline
rest.toString();
}
}

View file

@ -0,0 +1,341 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.event;
import org.apache.pekko.event.japi.EventBus;
import java.time.Duration;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.ClassRule;
import org.junit.Test;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.testkit.AkkaJUnitActorSystemResource;
import org.apache.pekko.util.Subclassification;
// #lookup-bus
import org.apache.pekko.event.japi.LookupEventBus;
// #lookup-bus
// #subchannel-bus
import org.apache.pekko.event.japi.SubchannelEventBus;
// #subchannel-bus
// #scanning-bus
import org.apache.pekko.event.japi.ScanningEventBus;
// #scanning-bus
// #actor-bus
import org.apache.pekko.event.japi.ManagedActorEventBus;
// #actor-bus
public class EventBusDocTest extends AbstractJavaTest {
public static class Event {}
public static class Subscriber {}
public static class Classifier {}
public static 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
}
public
// #lookup-bus
static class MsgEnvelope {
public final String topic;
public final Object payload;
public MsgEnvelope(String topic, Object payload) {
this.topic = topic;
this.payload = payload;
}
}
// #lookup-bus
public
// #lookup-bus
/**
* Publishes the payload of the MsgEnvelope when the topic of the MsgEnvelope equals the String
* specified when subscribing.
*/
static 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 events 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
public
// #subchannel-bus
static 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
public
// #subchannel-bus
/**
* Publishes the payload of the MsgEnvelope when the topic of the MsgEnvelope starts with the
* String specified when subscribing.
*/
static 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 events classifier
@Override
public void publish(MsgEnvelope event, ActorRef subscriber) {
subscriber.tell(event.payload, ActorRef.noSender());
}
}
// #subchannel-bus
public
// #scanning-bus
/**
* Publishes String messages with length less than or equal to the length specified when
* subscribing.
*/
static 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 events classifier
@Override
public void publish(String event, ActorRef subscriber) {
subscriber.tell(event, ActorRef.noSender());
}
}
// #scanning-bus
public
// #actor-bus
static class Notification {
public final ActorRef ref;
public final int id;
public Notification(ActorRef ref, int id) {
this.ref = ref;
this.id = id;
}
}
// #actor-bus
public
// #actor-bus
static 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.expectNoMessage(Duration.ofMillis(500));
// #actor-bus-test
}
}

View file

@ -0,0 +1,262 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.event;
// #imports
import org.apache.pekko.actor.*;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
// #imports
// #imports-listener
import org.apache.pekko.event.Logging.InitializeLogger;
import org.apache.pekko.event.Logging.Error;
import org.apache.pekko.event.Logging.Warning;
import org.apache.pekko.event.Logging.Info;
import org.apache.pekko.event.Logging.Debug;
// #imports-listener
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.Test;
import java.util.Optional;
// #imports-mdc
import org.apache.pekko.event.DiagnosticLoggingAdapter;
import java.util.HashMap;
import java.util.Map;
// #imports-mdc
// #imports-deadletter
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.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.getEventStream().subscribe(actor, DeadLetter.class);
// #deadletters
TestKit.shutdownActorSystem(system);
}
// #superclass-subscription-eventstream
interface AllKindsOfMusic {}
class Jazz implements AllKindsOfMusic {
public final String artist;
public Jazz(String artist) {
this.artist = artist;
}
}
class Electronic implements AllKindsOfMusic {
public final 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.getEventStream().subscribe(actor, DeadLetter.class);
final ActorRef jazzListener = system.actorOf(Props.create(Listener.class));
final ActorRef musicListener = system.actorOf(Props.create(Listener.class));
system.getEventStream().subscribe(jazzListener, Jazz.class);
system.getEventStream().subscribe(musicListener, AllKindsOfMusic.class);
// only musicListener gets this message, since it listens to *all* kinds of music:
system.getEventStream().publish(new Electronic("Parov Stelar"));
// jazzListener and musicListener will be notified about Jazz:
system.getEventStream().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.getEventStream().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.getEventStream().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
public
// #deadletter-actor
static class DeadLetterActor extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
DeadLetter.class,
msg -> {
System.out.println(msg);
})
.build();
}
}
// #deadletter-actor
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.extension;
// #imports
import org.apache.pekko.actor.*;
import java.util.concurrent.atomic.AtomicLong;
// #imports
import jdocs.AbstractJavaTest;
import org.junit.Test;
public class ExtensionDocTest extends AbstractJavaTest {
public
// #extension
static 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
public
// #extensionid
static class CountExtension extends AbstractExtensionId<CountExtensionImpl>
implements ExtensionIdProvider {
// This will be the identifier of our CountExtension
public static final 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
public
// #extension-usage-actor
static 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
}
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.extension;
// #imports
import org.apache.pekko.actor.Extension;
import org.apache.pekko.actor.AbstractExtensionId;
import org.apache.pekko.actor.ExtensionIdProvider;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ExtendedActorSystem;
import com.typesafe.config.Config;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
// #imports
import jdocs.AbstractJavaTest;
import org.apache.pekko.actor.AbstractActor;
import org.junit.Test;
public class SettingsExtensionDocTest extends AbstractJavaTest {
public
// #extension
static 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.ofMillis(
config.getDuration("myapp.circuit-breaker.timeout", TimeUnit.MILLISECONDS));
}
}
// #extension
public
// #extensionid
static class Settings extends AbstractExtensionId<SettingsImpl> implements ExtensionIdProvider {
public static final Settings SettingsProvider = new Settings();
private Settings() {}
public Settings lookup() {
return Settings.SettingsProvider;
}
public SettingsImpl createExtension(ExtendedActorSystem system) {
return new SettingsImpl(system.settings().config());
}
}
// #extensionid
public
// #extension-usage-actor
static 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
}
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.future;
// #context-dispatcher
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.dispatch.Futures;
public class ActorWithFuture extends AbstractActor {
ActorWithFuture() {
Futures.future(() -> "hello", getContext().dispatcher());
}
@Override
public Receive createReceive() {
return AbstractActor.emptyBehavior();
}
}
// #context-dispatcher

View file

@ -0,0 +1,80 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.future;
import org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.dispatch.Futures;
import org.apache.pekko.pattern.Patterns;
import org.apache.pekko.testkit.AkkaJUnitActorSystemResource;
import org.apache.pekko.testkit.AkkaSpec;
import org.apache.pekko.util.Timeout;
import jdocs.AbstractJavaTest;
import org.junit.ClassRule;
import org.junit.Test;
import scala.compat.java8.FutureConverters;
import scala.concurrent.Await;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import static org.apache.pekko.actor.typed.javadsl.Adapter.toTyped;
import static org.apache.pekko.dispatch.Futures.future;
// #imports
import org.apache.pekko.pattern.Patterns;
// #imports
import static java.util.concurrent.TimeUnit.SECONDS;
public class FutureDocTest extends AbstractJavaTest {
@ClassRule
public static AkkaJUnitActorSystemResource actorSystemResource =
new AkkaJUnitActorSystemResource("FutureDocTest", AkkaSpec.testConf());
private final ActorSystem<Void> system = toTyped(actorSystemResource.getSystem());
@Test(expected = java.util.concurrent.CompletionException.class)
public void useAfter() throws Exception {
final ExecutionContext ec = system.executionContext();
// #after
CompletionStage<String> failWithException =
CompletableFuture.supplyAsync(
() -> {
throw new IllegalStateException("OHNOES1");
});
CompletionStage<String> delayed =
Patterns.after(Duration.ofMillis(200), system, () -> failWithException);
// #after
Future<String> future =
future(
() -> {
Thread.sleep(1000);
return "foo";
},
ec);
Future<String> result =
Futures.firstCompletedOf(
Arrays.<Future<String>>asList(future, FutureConverters.toScala(delayed)), ec);
Timeout timeout = Timeout.create(Duration.ofSeconds(2));
Await.result(result, timeout.duration());
}
@Test
public void useRetry() throws Exception {
// #retry
Callable<CompletionStage<String>> attempt = () -> CompletableFuture.completedFuture("test");
CompletionStage<String> retriedFuture =
Patterns.retry(attempt, 3, java.time.Duration.ofMillis(200), system);
// #retry
retriedFuture.toCompletableFuture().get(2, SECONDS);
}
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.AbstractActor;
// #imports
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.io.TcpSO;
import org.apache.pekko.util.ByteString;
import java.time.Duration;
// #imports
public class IODocTest {
public static 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));
Duration timeout = null;
tcp.tell(
TcpMessage.connect(remoteAddr, localAddr, options, timeout, 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
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
import java.time.Duration;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
/** Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com> */
public class JavaReadBackPressure {
public static 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>();
Duration timeout = null;
tcp.tell(
TcpMessage.connect(
new InetSocketAddress("localhost", 3000), null, options, timeout, true),
getSelf());
// #pull-mode-connect
}
}
public static class Ack implements Tcp.Event {}
public static 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
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
// #imports
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Udp;
import org.apache.pekko.io.UdpMessage;
import org.apache.pekko.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();
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.io.Udp;
import org.apache.pekko.testkit.SocketUtil;
import jdocs.AbstractJavaTest;
import org.apache.pekko.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
// https://www.rfc-editor.org/rfc/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;
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.japi.pf.ReceiveBuilder;
import org.junit.Test;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.AbstractActor;
// #imports
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.UdpConnected;
import org.apache.pekko.io.UdpConnectedMessage;
import org.apache.pekko.io.UdpSO;
import org.apache.pekko.util.ByteString;
import static org.apache.pekko.util.ByteString.emptyByteString;
// #imports
public class UdpConnectedDocTest {
public static 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 = emptyByteString();
// #send
connectionActor.tell(UdpConnectedMessage.send(data), getSelf());
// #send
});
return builder.build();
}
}
@Test
public void demonstrateConnect() {}
}

View file

@ -0,0 +1,181 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
// #imports
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.PoisonPill;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Udp;
import org.apache.pekko.io.UdpConnected;
import org.apache.pekko.io.UdpConnectedMessage;
import org.apache.pekko.io.UdpMessage;
import org.apache.pekko.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
}

View file

@ -0,0 +1,259 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp.CommandFailed;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Event;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.Tcp.Write;
import org.apache.pekko.io.Tcp.WritingResumed;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
// #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<>();
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
static final 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) {
assertEquals(storageOffset, ack);
assertFalse(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

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.SupervisorStrategy;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.Tcp.Bind;
import org.apache.pekko.io.Tcp.Bound;
import org.apache.pekko.io.Tcp.Connected;
import org.apache.pekko.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();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.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();
}
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import org.apache.pekko.testkit.AkkaJUnitActorSystemResource;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.ClassRule;
import org.junit.Test;
// #imports
import java.net.InetSocketAddress;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.Tcp.Bound;
import org.apache.pekko.io.Tcp.CommandFailed;
import org.apache.pekko.io.Tcp.Connected;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
// #imports
import org.apache.pekko.testkit.AkkaSpec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class IODocTest extends AbstractJavaTest {
public
// #server
static 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
public
// #simplistic-handler
static 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
public
// #client
static 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);
assertTrue(c1.localAddress().equals(c2.remoteAddress()));
assertTrue(c2.localAddress().equals(c1.remoteAddress()));
client.tell(ByteString.fromString("hello"), getRef());
final ByteString reply = expectMsgClass(ByteString.class);
assertEquals("hello", reply.utf8String());
watch(client);
client.tell("close", getRef());
expectTerminated(client);
}
};
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2013-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
// #message
public class Message {
public static 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

View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Event;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.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 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<>();
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

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.util.concurrent.CountDownLatch;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Terminated;
import org.apache.pekko.actor.AbstractActor;
public class Watcher extends AbstractActor {
public static 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();
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.pattern;
import org.apache.pekko.actor.*;
import org.apache.pekko.pattern.BackoffOpts;
import org.apache.pekko.pattern.BackoffSupervisor;
import org.apache.pekko.testkit.TestActors.EchoActor;
// #backoff-imports
import java.time.Duration;
// #backoff-imports
public class BackoffSupervisorDocTest {
void exampleStop(ActorSystem system) {
// #backoff-stop
final Props childProps = Props.create(EchoActor.class);
final Props supervisorProps =
BackoffSupervisor.props(
BackoffOpts.onStop(
childProps,
"myEcho",
Duration.ofSeconds(3),
Duration.ofSeconds(30),
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(
BackoffOpts.onFailure(
childProps,
"myEcho",
Duration.ofSeconds(3),
Duration.ofSeconds(30),
0.2)); // adds 20% "noise" to vary the intervals slightly
system.actorOf(supervisorProps, "echoSupervisor");
// #backoff-fail
}
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.pattern;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeoutException;
import java.time.Duration;
import org.apache.pekko.actor.ActorKilledException;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorRefFactory;
import org.apache.pekko.actor.Cancellable;
import org.apache.pekko.actor.OneForOneStrategy;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.Scheduler;
import org.apache.pekko.actor.Status;
import org.apache.pekko.actor.SupervisorStrategy;
import org.apache.pekko.actor.Terminated;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.pattern.Patterns;
public class SupervisedAsk {
private static class AskParam {
Props props;
Object message;
Duration timeout;
AskParam(Props props, Object message, Duration 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,
getSelf(),
new AskTimeout(),
getContext().getDispatcher(),
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, Duration timeout) {
AskParam param = new AskParam(props, message, timeout);
return Patterns.ask(supervisorCreator, param, timeout);
}
public static synchronized ActorRef createSupervisorCreator(ActorRefFactory factory) {
return factory.actorOf(Props.create(AskSupervisorCreator.class));
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.pattern;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorRefFactory;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import java.time.Duration;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
public class SupervisedAskSpec {
public Object execute(
Class<? extends AbstractActor> someActor,
Object message,
Duration timeout,
ActorRefFactory actorSystem)
throws Exception {
// example usage
try {
ActorRef supervisorCreator = SupervisedAsk.createSupervisorCreator(actorSystem);
CompletionStage<Object> finished =
SupervisedAsk.askOf(supervisorCreator, Props.create(someActor), message, timeout);
return finished.toCompletableFuture().get(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (Exception e) {
// exception propagated by supervision
throw e;
}
}
}

View file

@ -0,0 +1,740 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
import org.apache.pekko.actor.*;
import org.apache.pekko.japi.Procedure;
import org.apache.pekko.pattern.BackoffOpts;
import org.apache.pekko.pattern.BackoffSupervisor;
import org.apache.pekko.persistence.*;
import java.io.Serializable;
import java.time.Duration;
import java.util.Optional;
public class LambdaPersistenceDocTest {
public interface SomeOtherMessage {}
public interface PersistentActorMethods {
// #persistence-id
public String persistenceId();
// #persistence-id
}
public interface PersistenceActorRecoveryMethods {
// #recovery-status
public boolean recoveryRunning();
public boolean recoveryFinished();
// #recovery-status
}
static Object o2 =
new Object() {
abstract class MyPersistentActor1 extends AbstractPersistentActor {
// #recovery-disabled
@Override
public Recovery recovery() {
return Recovery.none();
}
// #recovery-disabled
// #recover-on-restart-disabled
@Override
public void preRestart(Throwable reason, Optional<Object> message) {}
// #recover-on-restart-disabled
}
abstract class MyPersistentActor2 extends AbstractPersistentActor {
// #recovery-custom
@Override
public Recovery recovery() {
return Recovery.create(457L);
}
// #recovery-custom
}
class MyPersistentActor4 extends AbstractPersistentActor implements PersistentActorMethods {
// #persistence-id-override
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
// #persistence-id-override
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
String.class,
cmd -> {
/* ... */
})
.build();
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder()
.match(
String.class,
evt -> {
/* ... */
})
.build();
}
}
// #recovery-completed
class MyPersistentActor5 extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder()
.match(
RecoveryCompleted.class,
r -> {
// perform init after recovery, before any other messages
// ...
})
.match(String.class, this::handleEvent)
.build();
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(String.class, s -> s.equals("cmd"), s -> persist("evt", this::handleEvent))
.build();
}
private void handleEvent(String event) {
// update state
// ...
}
}
// #recovery-completed
abstract class MyPersistentActor6 extends AbstractPersistentActor {
// #recovery-no-snap
@Override
public Recovery recovery() {
return Recovery.create(SnapshotSelectionCriteria.none());
}
// #recovery-no-snap
}
abstract class MyActor extends AbstractPersistentActor {
// #backoff
@Override
public void preStart() throws Exception {
final Props childProps = Props.create(MyPersistentActor1.class);
final Props props =
BackoffSupervisor.props(
BackoffOpts.onStop(
childProps, "myActor", Duration.ofSeconds(3), Duration.ofSeconds(30), 0.2));
getContext().actorOf(props, "mySupervisor");
super.preStart();
}
// #backoff
}
};
static Object atLeastOnceExample =
new Object() {
// #at-least-once-example
class Msg implements Serializable {
private static final long serialVersionUID = 1L;
public final long deliveryId;
public final String s;
public Msg(long deliveryId, String s) {
this.deliveryId = deliveryId;
this.s = s;
}
}
class Confirm implements Serializable {
private static final long serialVersionUID = 1L;
public final long deliveryId;
public Confirm(long deliveryId) {
this.deliveryId = deliveryId;
}
}
class MsgSent implements Serializable {
private static final long serialVersionUID = 1L;
public final String s;
public MsgSent(String s) {
this.s = s;
}
}
class MsgConfirmed implements Serializable {
private static final long serialVersionUID = 1L;
public final long deliveryId;
public MsgConfirmed(long deliveryId) {
this.deliveryId = deliveryId;
}
}
class MyPersistentActor extends AbstractPersistentActorWithAtLeastOnceDelivery {
private final ActorSelection destination;
public MyPersistentActor(ActorSelection destination) {
this.destination = destination;
}
@Override
public String persistenceId() {
return "persistence-id";
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
String.class,
s -> {
persist(new MsgSent(s), evt -> updateState(evt));
})
.match(
Confirm.class,
confirm -> {
persist(new MsgConfirmed(confirm.deliveryId), evt -> updateState(evt));
})
.build();
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().match(Object.class, evt -> updateState(evt)).build();
}
void updateState(Object event) {
if (event instanceof MsgSent) {
final MsgSent evt = (MsgSent) event;
deliver(destination, deliveryId -> new Msg(deliveryId, evt.s));
} else if (event instanceof MsgConfirmed) {
final MsgConfirmed evt = (MsgConfirmed) event;
confirmDelivery(evt.deliveryId);
}
}
}
class MyDestination extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Msg.class,
msg -> {
// ...
getSender().tell(new Confirm(msg.deliveryId), getSelf());
})
.build();
}
}
// #at-least-once-example
};
static Object o4 =
new Object() {
class MyPersistentActor extends AbstractPersistentActor {
private void updateState(String evt) {}
// #save-snapshot
private Object state;
private int snapShotInterval = 1000;
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
SaveSnapshotSuccess.class,
ss -> {
SnapshotMetadata metadata = ss.metadata();
// ...
})
.match(
SaveSnapshotFailure.class,
sf -> {
SnapshotMetadata metadata = sf.metadata();
// ...
})
.match(
String.class,
cmd -> {
persist(
"evt-" + cmd,
e -> {
updateState(e);
if (lastSequenceNr() % snapShotInterval == 0 && lastSequenceNr() != 0)
saveSnapshot(state);
});
})
.build();
}
// #save-snapshot
@Override
public String persistenceId() {
return "persistence-id";
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder()
.match(
RecoveryCompleted.class,
r -> {
/* ...*/
})
.build();
}
}
};
static Object o5 =
new Object() {
class MyPersistentActor extends AbstractPersistentActor {
// #snapshot-criteria
@Override
public Recovery recovery() {
return Recovery.create(
SnapshotSelectionCriteria.create(457L, System.currentTimeMillis()));
}
// #snapshot-criteria
// #snapshot-offer
private Object state;
@Override
public Receive createReceiveRecover() {
return receiveBuilder()
.match(
SnapshotOffer.class,
s -> {
state = s.snapshot();
// ...
})
.match(
String.class,
s -> {
/* ...*/
})
.build();
}
// #snapshot-offer
@Override
public String persistenceId() {
return "persistence-id";
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
String.class,
s -> {
/* ...*/
})
.build();
}
}
class MyActor extends AbstractActor {
private final ActorRef persistentActor =
getContext().actorOf(Props.create(MyPersistentActor.class));
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Object.class,
o -> {
/* ... */
})
.build();
}
}
};
static Object o9 =
new Object() {
// #persist-async
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
private void handleCommand(String c) {
getSender().tell(c, getSelf());
persistAsync(
String.format("evt-%s-1", c),
e -> {
getSender().tell(e, getSelf());
});
persistAsync(
String.format("evt-%s-2", c),
e -> {
getSender().tell(e, getSelf());
});
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
}
// #persist-async
public void usage() {
final ActorSystem system = ActorSystem.create("example");
// #persist-async-usage
final ActorRef persistentActor = system.actorOf(Props.create(MyPersistentActor.class));
persistentActor.tell("a", null);
persistentActor.tell("b", null);
// possible order of received messages:
// a
// b
// evt-a-1
// evt-a-2
// evt-b-1
// evt-b-2
// #persist-async-usage
}
};
static Object o10 =
new Object() {
// #defer
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
private void handleCommand(String c) {
persistAsync(
String.format("evt-%s-1", c),
e -> {
getSender().tell(e, getSelf());
});
persistAsync(
String.format("evt-%s-2", c),
e -> {
getSender().tell(e, getSelf());
});
deferAsync(
String.format("evt-%s-3", c),
e -> {
getSender().tell(e, getSelf());
});
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
}
// #defer
public void usage() {
final ActorSystem system = ActorSystem.create("example");
final ActorRef sender = null; // your imaginary sender here
// #defer-caller
final ActorRef persistentActor = system.actorOf(Props.create(MyPersistentActor.class));
persistentActor.tell("a", sender);
persistentActor.tell("b", sender);
// order of received messages:
// a
// b
// evt-a-1
// evt-a-2
// evt-a-3
// evt-b-1
// evt-b-2
// evt-b-3
// #defer-caller
}
};
static Object o100 =
new Object() {
// #defer-with-persist
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
private void handleCommand(String c) {
persist(
String.format("evt-%s-1", c),
e -> {
sender().tell(e, self());
});
persist(
String.format("evt-%s-2", c),
e -> {
sender().tell(e, self());
});
defer(
String.format("evt-%s-3", c),
e -> {
sender().tell(e, self());
});
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, this::handleCommand).build();
}
}
// #defer-with-persist
};
static Object o11 =
new Object() {
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
@Override
public Receive createReceive() {
return receiveBuilder().matchAny(event -> {}).build();
}
// #nested-persist-persist
@Override
public Receive createReceiveRecover() {
final Procedure<String> replyToSender = event -> getSender().tell(event, getSelf());
return receiveBuilder()
.match(
String.class,
msg -> {
persist(
String.format("%s-outer-1", msg),
event -> {
getSender().tell(event, getSelf());
persist(String.format("%s-inner-1", event), replyToSender);
});
persist(
String.format("%s-outer-2", msg),
event -> {
getSender().tell(event, getSelf());
persist(String.format("%s-inner-2", event), replyToSender);
});
})
.build();
}
// #nested-persist-persist
void usage(ActorRef persistentActor) {
// #nested-persist-persist-caller
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
// order of received messages:
// a
// a-outer-1
// a-outer-2
// a-inner-1
// a-inner-2
// and only then process "b"
// b
// b-outer-1
// b-outer-2
// b-inner-1
// b-inner-2
// #nested-persist-persist-caller
}
}
class MyPersistAsyncActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "my-stable-persistence-id";
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().matchAny(event -> {}).build();
}
// #nested-persistAsync-persistAsync
@Override
public Receive createReceive() {
final Procedure<String> replyToSender = event -> getSender().tell(event, getSelf());
return receiveBuilder()
.match(
String.class,
msg -> {
persistAsync(
String.format("%s-outer-1", msg),
event -> {
getSender().tell(event, getSelf());
persistAsync(String.format("%s-inner-1", event), replyToSender);
});
persistAsync(
String.format("%s-outer-2", msg),
event -> {
getSender().tell(event, getSelf());
persistAsync(String.format("%s-inner-1", event), replyToSender);
});
})
.build();
}
// #nested-persistAsync-persistAsync
void usage(ActorRef persistentActor) {
// #nested-persistAsync-persistAsync-caller
persistentActor.tell("a", getSelf());
persistentActor.tell("b", getSelf());
// order of received messages:
// a
// b
// a-outer-1
// a-outer-2
// b-outer-1
// b-outer-2
// a-inner-1
// a-inner-2
// b-inner-1
// b-inner-2
// which can be seen as the following causal relationship:
// a -> a-outer-1 -> a-outer-2 -> a-inner-1 -> a-inner-2
// b -> b-outer-1 -> b-outer-2 -> b-inner-1 -> b-inner-2
// #nested-persistAsync-persistAsync-caller
}
}
};
static Object o14 =
new Object() {
// #safe-shutdown
final class Shutdown {}
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "some-persistence-id";
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Shutdown.class,
shutdown -> {
getContext().stop(getSelf());
})
.match(
String.class,
msg -> {
System.out.println(msg);
persist("handle-" + msg, e -> System.out.println(e));
})
.build();
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder().matchAny(any -> {}).build();
}
}
// #safe-shutdown
public void usage() {
final ActorSystem system = ActorSystem.create("example");
final ActorRef persistentActor = system.actorOf(Props.create(MyPersistentActor.class));
// #safe-shutdown-example-bad
// UN-SAFE, due to PersistentActor's command stashing:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(PoisonPill.getInstance(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # PoisonPill arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// PoisonPill is an AutoReceivedMessage, is handled automatically
// !! stop !!
// Actor is stopped without handling `b` nor the `a` handler!
// #safe-shutdown-example-bad
// #safe-shutdown-example-good
// SAFE:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(new Shutdown(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # Shutdown arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// handle-a
// # unstashing; internal-stash = [Shutdown]
// b
// handle-b
// # unstashing; internal-stash = []
// Shutdown
// -- stop --
// #safe-shutdown-example-good
}
};
}

View file

@ -0,0 +1,215 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
// #plugin-imports
import org.apache.pekko.dispatch.Futures;
import org.apache.pekko.persistence.*;
import org.apache.pekko.persistence.journal.japi.*;
import org.apache.pekko.persistence.snapshot.japi.*;
// #plugin-imports
import org.apache.pekko.actor.*;
import org.apache.pekko.persistence.journal.leveldb.SharedLeveldbJournal;
import org.apache.pekko.persistence.journal.leveldb.SharedLeveldbStore;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.junit.runner.RunWith;
import org.scalatestplus.junit.JUnitRunner;
import scala.concurrent.Future;
import java.util.function.Consumer;
import org.iq80.leveldb.util.FileUtils;
import java.util.Optional;
import org.apache.pekko.persistence.japi.journal.JavaJournalSpec;
import org.apache.pekko.persistence.japi.snapshot.JavaSnapshotStoreSpec;
public class LambdaPersistencePluginDocTest {
static Object o1 =
new Object() {
final ActorSystem system = null;
// #shared-store-creation
final ActorRef store = system.actorOf(Props.create(SharedLeveldbStore.class), "store");
// #shared-store-creation
// #shared-store-usage
class SharedStorageUsage extends AbstractActor {
@Override
public void preStart() throws Exception {
String path = "akka://example@127.0.0.1:2552/user/store";
ActorSelection selection = getContext().actorSelection(path);
selection.tell(new Identify(1), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
ActorIdentity.class,
ai -> {
if (ai.correlationId().equals(1)) {
Optional<ActorRef> store = ai.getActorRef();
if (store.isPresent()) {
SharedLeveldbJournal.setStore(store.get(), getContext().getSystem());
} else {
throw new RuntimeException("Couldn't identify store");
}
}
})
.build();
}
}
// #shared-store-usage
};
class MySnapshotStore extends SnapshotStore {
@Override
public Future<Optional<SelectedSnapshot>> doLoadAsync(
String persistenceId, SnapshotSelectionCriteria criteria) {
return null;
}
@Override
public Future<Void> doSaveAsync(SnapshotMetadata metadata, Object snapshot) {
return null;
}
@Override
public Future<Void> doDeleteAsync(SnapshotMetadata metadata) {
return Futures.successful(null);
}
@Override
public Future<Void> doDeleteAsync(String persistenceId, SnapshotSelectionCriteria criteria) {
return Futures.successful(null);
}
}
class MyAsyncJournal extends AsyncWriteJournal {
// #sync-journal-plugin-api
@Override
public Future<Iterable<Optional<Exception>>> doAsyncWriteMessages(
Iterable<AtomicWrite> messages) {
try {
Iterable<Optional<Exception>> result = new ArrayList<Optional<Exception>>();
// blocking call here...
// result.add(..)
return Futures.successful(result);
} catch (Exception e) {
return Futures.failed(e);
}
}
// #sync-journal-plugin-api
@Override
public Future<Void> doAsyncDeleteMessagesTo(String persistenceId, long toSequenceNr) {
return null;
}
@Override
public Future<Void> doAsyncReplayMessages(
String persistenceId,
long fromSequenceNr,
long toSequenceNr,
long max,
Consumer<PersistentRepr> replayCallback) {
return null;
}
@Override
public Future<Long> doAsyncReadHighestSequenceNr(String persistenceId, long fromSequenceNr) {
return null;
}
}
static Object o2 =
new Object() {
// #journal-tck-java
@RunWith(JUnitRunner.class)
class MyJournalSpecTest extends JavaJournalSpec {
public MyJournalSpecTest() {
super(
ConfigFactory.parseString(
"akka.persistence.journal.plugin = "
+ "\"akka.persistence.journal.leveldb-shared\""));
}
@Override
public CapabilityFlag supportsRejectingNonSerializableObjects() {
return CapabilityFlag.off();
}
}
// #journal-tck-java
};
static Object o3 =
new Object() {
// #snapshot-store-tck-java
@RunWith(JUnitRunner.class)
class MySnapshotStoreTest extends JavaSnapshotStoreSpec {
public MySnapshotStoreTest() {
super(
ConfigFactory.parseString(
"akka.persistence.snapshot-store.plugin = "
+ "\"akka.persistence.snapshot-store.local\""));
}
}
// #snapshot-store-tck-java
};
static Object o4 =
new Object() {
// https://github.com/akka/akka/issues/26826
// #journal-tck-before-after-java
@RunWith(JUnitRunner.class)
class MyJournalSpecTest extends JavaJournalSpec {
List<File> storageLocations = new ArrayList<File>();
public MyJournalSpecTest() {
super(
ConfigFactory.parseString(
"persistence.journal.plugin = "
+ "\"akka.persistence.journal.leveldb-shared\""));
Config config = system().settings().config();
storageLocations.add(
new File(config.getString("akka.persistence.journal.leveldb.dir")));
storageLocations.add(
new File(config.getString("akka.persistence.snapshot-store.local.dir")));
}
@Override
public CapabilityFlag supportsRejectingNonSerializableObjects() {
return CapabilityFlag.on();
}
@Override
public void beforeAll() {
for (File storageLocation : storageLocations) {
FileUtils.deleteRecursively(storageLocation);
}
super.beforeAll();
}
@Override
public void afterAll() {
super.afterAll();
for (File storageLocation : storageLocations) {
FileUtils.deleteRecursively(storageLocation);
}
}
}
// #journal-tck-before-after-java
};
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
import org.apache.pekko.persistence.journal.EventAdapter;
import org.apache.pekko.persistence.journal.EventSeq;
public class PersistenceEventAdapterDocTest {
@SuppressWarnings("unused")
static
// #identity-event-adapter
class MyEventAdapter implements EventAdapter {
@Override
public String manifest(Object event) {
return ""; // if no manifest needed, return ""
}
@Override
public Object toJournal(Object event) {
return event; // identity
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
return EventSeq.single(event); // identity
}
}
// #identity-event-adapter
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
import org.apache.pekko.persistence.AbstractPersistentActor;
import org.apache.pekko.persistence.RuntimePluginConfig;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
public class PersistenceMultiDocTest {
// #default-plugins
abstract class AbstractPersistentActorWithDefaultPlugins extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "123";
}
}
// #default-plugins
// #override-plugins
abstract class AbstractPersistentActorWithOverridePlugins extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "123";
}
// Absolute path to the journal plugin configuration entry in the `reference.conf`
@Override
public String journalPluginId() {
return "akka.persistence.chronicle.journal";
}
// Absolute path to the snapshot store plugin configuration entry in the `reference.conf`
@Override
public String snapshotPluginId() {
return "akka.persistence.chronicle.snapshot-store";
}
}
// #override-plugins
// #runtime-config
abstract class AbstractPersistentActorWithRuntimePluginConfig extends AbstractPersistentActor
implements RuntimePluginConfig {
// Variable that is retrieved at runtime, from an external service for instance.
String runtimeDistinction = "foo";
@Override
public String persistenceId() {
return "123";
}
// Absolute path to the journal plugin configuration entry in the `reference.conf`
@Override
public String journalPluginId() {
return "journal-plugin-" + runtimeDistinction;
}
// Absolute path to the snapshot store plugin configuration entry in the `reference.conf`
@Override
public String snapshotPluginId() {
return "snapshot-store-plugin-" + runtimeDistinction;
}
// Configuration which contains the journal plugin id defined above
@Override
public Config journalPluginConfig() {
return ConfigFactory.empty()
.withValue(
"journal-plugin-" + runtimeDistinction,
getContext()
.getSystem()
.settings()
.config()
.getValue(
"journal-plugin") // or a very different configuration coming from an external
// service.
);
}
// Configuration which contains the snapshot store plugin id defined above
@Override
public Config snapshotPluginConfig() {
return ConfigFactory.empty()
.withValue(
"snapshot-plugin-" + runtimeDistinction,
getContext()
.getSystem()
.settings()
.config()
.getValue(
"snapshot-store-plugin") // or a very different configuration coming from an
// external service.
);
}
}
// #runtime-config
}

View file

@ -0,0 +1,422 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
import java.sql.Connection;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import org.apache.pekko.NotUsed;
import org.apache.pekko.persistence.query.Sequence;
import org.apache.pekko.persistence.query.Offset;
import com.typesafe.config.Config;
import org.apache.pekko.actor.*;
import org.apache.pekko.persistence.query.*;
import org.apache.pekko.stream.javadsl.Sink;
import org.apache.pekko.stream.javadsl.Source;
import jdocs.persistence.query.MyEventsByTagSource;
import org.reactivestreams.Subscriber;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionStage;
public class PersistenceQueryDocTest {
final ActorSystem system = ActorSystem.create();
public
// #advanced-journal-query-types
static class RichEvent {
public final Set<String> tags;
public final Object payload;
public RichEvent(Set<String> tags, Object payload) {
this.tags = tags;
this.payload = payload;
}
}
// #advanced-journal-query-types
public
// #advanced-journal-query-types
// a plugin can provide:
static final class QueryMetadata {
public final boolean deterministicOrder;
public final boolean infinite;
public QueryMetadata(boolean deterministicOrder, boolean infinite) {
this.deterministicOrder = deterministicOrder;
this.infinite = infinite;
}
}
// #advanced-journal-query-types
interface OrderCompleted {}
public
// #my-read-journal
static class MyReadJournalProvider implements ReadJournalProvider {
private final MyJavadslReadJournal javadslReadJournal;
public MyReadJournalProvider(ExtendedActorSystem system, Config config) {
this.javadslReadJournal = new MyJavadslReadJournal(system, config);
}
@Override
public MyScaladslReadJournal scaladslReadJournal() {
return new MyScaladslReadJournal(javadslReadJournal);
}
@Override
public MyJavadslReadJournal javadslReadJournal() {
return this.javadslReadJournal;
}
}
// #my-read-journal
public
// #my-read-journal
static class MyJavadslReadJournal
implements org.apache.pekko.persistence.query.javadsl.ReadJournal,
org.apache.pekko.persistence.query.javadsl.EventsByTagQuery,
org.apache.pekko.persistence.query.javadsl.EventsByPersistenceIdQuery,
org.apache.pekko.persistence.query.javadsl.PersistenceIdsQuery,
org.apache.pekko.persistence.query.javadsl.CurrentPersistenceIdsQuery {
private final Duration refreshInterval;
private Connection conn;
public MyJavadslReadJournal(ExtendedActorSystem system, Config config) {
refreshInterval = config.getDuration("refresh-interval");
}
/**
* You can use `NoOffset` to retrieve all events with a given tag or retrieve a subset of all
* events by specifying a `Sequence` `offset`. The `offset` corresponds to an ordered sequence
* number for the specific tag. Note that the corresponding offset of each event is provided in
* the [[akka.persistence.query.EventEnvelope]], which makes it possible to resume the stream at
* a later point from a given offset.
*
* <p>The `offset` is exclusive, i.e. the event with the exact same sequence number will not be
* included in the returned stream. This means that you can use the offset that is returned in
* `EventEnvelope` as the `offset` parameter in a subsequent query.
*/
@Override
public Source<EventEnvelope, NotUsed> eventsByTag(String tag, Offset offset) {
if (offset instanceof Sequence) {
Sequence sequenceOffset = (Sequence) offset;
return Source.fromGraph(
new MyEventsByTagSource(conn, tag, sequenceOffset.value(), refreshInterval));
} else if (offset == NoOffset.getInstance())
return eventsByTag(tag, Offset.sequence(0L)); // recursive
else
throw new IllegalArgumentException(
"MyJavadslReadJournal does not support " + offset.getClass().getName() + " offsets");
}
@Override
public Source<EventEnvelope, NotUsed> eventsByPersistenceId(
String persistenceId, long fromSequenceNr, long toSequenceNr) {
// implement in a similar way as eventsByTag
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public Source<String, NotUsed> persistenceIds() {
// implement in a similar way as eventsByTag
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public Source<String, NotUsed> currentPersistenceIds() {
// implement in a similar way as eventsByTag
throw new UnsupportedOperationException("Not implemented yet");
}
// possibility to add more plugin specific queries
// #advanced-journal-query-definition
public Source<RichEvent, QueryMetadata> byTagsWithMeta(Set<String> tags) {
// #advanced-journal-query-definition
// implement in a similar way as eventsByTag
throw new UnsupportedOperationException("Not implemented yet");
}
}
// #my-read-journal
public
// #my-read-journal
static class MyScaladslReadJournal
implements org.apache.pekko.persistence.query.scaladsl.ReadJournal,
org.apache.pekko.persistence.query.scaladsl.EventsByTagQuery,
org.apache.pekko.persistence.query.scaladsl.EventsByPersistenceIdQuery,
org.apache.pekko.persistence.query.scaladsl.PersistenceIdsQuery,
org.apache.pekko.persistence.query.scaladsl.CurrentPersistenceIdsQuery {
private final MyJavadslReadJournal javadslReadJournal;
public MyScaladslReadJournal(MyJavadslReadJournal javadslReadJournal) {
this.javadslReadJournal = javadslReadJournal;
}
@Override
public org.apache.pekko.stream.scaladsl.Source<EventEnvelope, NotUsed> eventsByTag(
String tag, org.apache.pekko.persistence.query.Offset offset) {
return javadslReadJournal.eventsByTag(tag, offset).asScala();
}
@Override
public org.apache.pekko.stream.scaladsl.Source<EventEnvelope, NotUsed> eventsByPersistenceId(
String persistenceId, long fromSequenceNr, long toSequenceNr) {
return javadslReadJournal
.eventsByPersistenceId(persistenceId, fromSequenceNr, toSequenceNr)
.asScala();
}
@Override
public org.apache.pekko.stream.scaladsl.Source<String, NotUsed> persistenceIds() {
return javadslReadJournal.persistenceIds().asScala();
}
@Override
public org.apache.pekko.stream.scaladsl.Source<String, NotUsed> currentPersistenceIds() {
return javadslReadJournal.currentPersistenceIds().asScala();
}
// possibility to add more plugin specific queries
public org.apache.pekko.stream.scaladsl.Source<RichEvent, QueryMetadata> byTagsWithMeta(
scala.collection.Set<String> tags) {
Set<String> jTags = scala.collection.JavaConverters.setAsJavaSetConverter(tags).asJava();
return javadslReadJournal.byTagsWithMeta(jTags).asScala();
}
}
// #my-read-journal
void demonstrateBasicUsage() {
final ActorSystem system = ActorSystem.create();
// #basic-usage
// obtain read journal by plugin id
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// issue query to journal
Source<EventEnvelope, NotUsed> source =
readJournal.eventsByPersistenceId("user-1337", 0, Long.MAX_VALUE);
// materialize stream, consuming events
source.runForeach(event -> System.out.println("Event: " + event), system);
// #basic-usage
}
void demonstrateAllPersistenceIdsLive() {
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #all-persistence-ids-live
readJournal.persistenceIds();
// #all-persistence-ids-live
}
void demonstrateNoRefresh() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #all-persistence-ids-snap
readJournal.currentPersistenceIds();
// #all-persistence-ids-snap
}
void demonstrateRefresh() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #events-by-persistent-id
readJournal.eventsByPersistenceId("user-us-1337", 0L, Long.MAX_VALUE);
// #events-by-persistent-id
}
void demonstrateEventsByTag() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #events-by-tag
// assuming journal is able to work with numeric offsets we can:
final Source<EventEnvelope, NotUsed> completedOrders =
readJournal.eventsByTag("order-completed", new Sequence(0L));
// find first 10 completed orders:
final CompletionStage<List<OrderCompleted>> firstCompleted =
completedOrders
.map(EventEnvelope::event)
.collectType(OrderCompleted.class)
.take(10) // cancels the query stream after pulling 10 elements
.runFold(
new ArrayList<>(10),
(acc, e) -> {
acc.add(e);
return acc;
},
system);
// start another query, from the known offset
Source<EventEnvelope, NotUsed> furtherOrders =
readJournal.eventsByTag("order-completed", new Sequence(10));
// #events-by-tag
}
void demonstrateMaterializedQueryValues() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #advanced-journal-query-usage
Set<String> tags = new HashSet<String>();
tags.add("red");
tags.add("blue");
final Source<RichEvent, QueryMetadata> events =
readJournal
.byTagsWithMeta(tags)
.mapMaterializedValue(
meta -> {
System.out.println(
"The query is: "
+ "ordered deterministically: "
+ meta.deterministicOrder
+ " "
+ "infinite: "
+ meta.infinite);
return meta;
});
events
.map(
event -> {
System.out.println("Event payload: " + event.payload);
return event.payload;
})
.runWith(Sink.ignore(), system);
// #advanced-journal-query-usage
}
class ReactiveStreamsCompatibleDBDriver {
Subscriber<List<Object>> batchWriter() {
return null;
}
}
void demonstrateWritingIntoDifferentStore() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #projection-into-different-store-rs
final ReactiveStreamsCompatibleDBDriver driver = new ReactiveStreamsCompatibleDBDriver();
final Subscriber<List<Object>> dbBatchWriter = driver.batchWriter();
// Using an example (Reactive Streams) Database driver
readJournal
.eventsByPersistenceId("user-1337", 0L, Long.MAX_VALUE)
.map(envelope -> envelope.event())
.grouped(20) // batch inserts into groups of 20
.runWith(Sink.fromSubscriber(dbBatchWriter), system); // write batches to read-side database
// #projection-into-different-store-rs
}
// #projection-into-different-store-simple-classes
static class ExampleStore {
CompletionStage<Void> save(Object any) {
// ...
// #projection-into-different-store-simple-classes
return null;
// #projection-into-different-store-simple-classes
}
}
// #projection-into-different-store-simple-classes
void demonstrateWritingIntoDifferentStoreWithMapAsync() {
final ActorSystem system = ActorSystem.create();
final MyJavadslReadJournal readJournal =
PersistenceQuery.get(system)
.getReadJournalFor(
MyJavadslReadJournal.class, "akka.persistence.query.my-read-journal");
// #projection-into-different-store-simple
final ExampleStore store = new ExampleStore();
readJournal
.eventsByTag("bid", new Sequence(0L))
.mapAsync(1, store::save)
.runWith(Sink.ignore(), system);
// #projection-into-different-store-simple
}
// #projection-into-different-store
static class MyResumableProjection {
private final String name;
public MyResumableProjection(String name) {
this.name = name;
}
public CompletionStage<Long> saveProgress(Offset offset) {
// ...
// #projection-into-different-store
return null;
// #projection-into-different-store
}
public CompletionStage<Long> latestOffset() {
// ...
// #projection-into-different-store
return null;
// #projection-into-different-store
}
}
// #projection-into-different-store
static class ComplexState {
boolean readyToSave() {
return false;
}
}
static class Record {
static Record of(Object any) {
return new Record();
}
}
}

View file

@ -0,0 +1,547 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
import docs.persistence.ExampleJsonMarshaller;
import docs.persistence.proto.FlightAppModels;
import java.io.NotSerializableException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import spray.json.JsObject;
import org.apache.pekko.persistence.journal.EventAdapter;
import org.apache.pekko.persistence.journal.EventSeq;
import org.apache.pekko.protobufv3.internal.InvalidProtocolBufferException;
import org.apache.pekko.serialization.SerializerWithStringManifest;
public class PersistenceSchemaEvolutionDocTest {
public
// #protobuf-read-optional-model
static enum SeatType {
Window("W"),
Aisle("A"),
Other("O"),
Unknown("");
private final String code;
private SeatType(String code) {
this.code = code;
}
public static SeatType fromCode(String c) {
if (Window.code.equals(c)) return Window;
else if (Aisle.code.equals(c)) return Aisle;
else if (Other.code.equals(c)) return Other;
else return Unknown;
}
}
// #protobuf-read-optional-model
public
// #protobuf-read-optional-model
static class SeatReserved {
public final String letter;
public final int row;
public final SeatType seatType;
public SeatReserved(String letter, int row, SeatType seatType) {
this.letter = letter;
this.row = row;
this.seatType = seatType;
}
}
// #protobuf-read-optional-model
public
// #protobuf-read-optional
/**
* Example serializer impl which uses protocol buffers generated classes (proto.*) to perform the
* to/from binary marshalling.
*/
static class AddedFieldsSerializerWithProtobuf extends SerializerWithStringManifest {
@Override
public int identifier() {
return 67876;
}
private final String seatReservedManifest = SeatReserved.class.getName();
@Override
public String manifest(Object o) {
return o.getClass().getName();
}
@Override
public Object fromBinary(byte[] bytes, String manifest) throws NotSerializableException {
if (seatReservedManifest.equals(manifest)) {
// use generated protobuf serializer
try {
return seatReserved(FlightAppModels.SeatReserved.parseFrom(bytes));
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage());
}
} else {
throw new NotSerializableException("Unable to handle manifest: " + manifest);
}
}
@Override
public byte[] toBinary(Object o) {
if (o instanceof SeatReserved) {
SeatReserved s = (SeatReserved) o;
return FlightAppModels.SeatReserved.newBuilder()
.setRow(s.row)
.setLetter(s.letter)
.setSeatType(s.seatType.code)
.build()
.toByteArray();
} else {
throw new IllegalArgumentException("Unable to handle: " + o);
}
}
// -- fromBinary helpers --
private SeatReserved seatReserved(FlightAppModels.SeatReserved p) {
return new SeatReserved(p.getLetter(), p.getRow(), seatType(p));
}
// handle missing field by assigning "Unknown" value
private SeatType seatType(FlightAppModels.SeatReserved p) {
if (p.hasSeatType()) return SeatType.fromCode(p.getSeatType());
else return SeatType.Unknown;
}
}
// #protobuf-read-optional
public static class RenamePlainJson {
public
// #rename-plain-json
static class JsonRenamedFieldAdapter implements EventAdapter {
// use your favorite json library
private final ExampleJsonMarshaller marshaller = new ExampleJsonMarshaller();
private final String V1 = "v1";
private final String V2 = "v2";
// this could be done independently for each event type
@Override
public String manifest(Object event) {
return V2;
}
@Override
public JsObject toJournal(Object event) {
return marshaller.toJson(event);
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
if (event instanceof JsObject) {
JsObject json = (JsObject) event;
if (V1.equals(manifest)) json = rename(json, "code", "seatNr");
return EventSeq.single(json);
} else {
throw new IllegalArgumentException(
"Can only work with JSON, was: " + event.getClass().getName());
}
}
private JsObject rename(JsObject json, String from, String to) {
// use your favorite json library to rename the field
JsObject renamed = json;
return renamed;
}
}
// #rename-plain-json
}
public static class SimplestCustomSerializer {
public
// #simplest-custom-serializer-model
static class Person {
public final String name;
public final String surname;
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
}
// #simplest-custom-serializer-model
public
// #simplest-custom-serializer
/**
* Simplest possible serializer, uses a string representation of the Person class.
*
* <p>Usually a serializer like this would use a library like: protobuf, kryo, avro, cap'n
* proto, flatbuffers, SBE or some other dedicated serializer backend to perform the actual
* to/from bytes marshalling.
*/
static class SimplestPossiblePersonSerializer extends SerializerWithStringManifest {
private final Charset utf8 = StandardCharsets.UTF_8;
private final String personManifest = Person.class.getName();
// unique identifier of the serializer
@Override
public int identifier() {
return 1234567;
}
// extract manifest to be stored together with serialized object
@Override
public String manifest(Object o) {
return o.getClass().getName();
}
// serialize the object
@Override
public byte[] toBinary(Object obj) {
if (obj instanceof Person) {
Person p = (Person) obj;
return (p.name + "|" + p.surname).getBytes(utf8);
} else {
throw new IllegalArgumentException(
"Unable to serialize to bytes, clazz was: " + obj.getClass().getName());
}
}
// deserialize the object, using the manifest to indicate which logic to apply
@Override
public Object fromBinary(byte[] bytes, String manifest) throws NotSerializableException {
if (personManifest.equals(manifest)) {
String nameAndSurname = new String(bytes, utf8);
String[] parts = nameAndSurname.split("[|]");
return new Person(parts[0], parts[1]);
} else {
throw new NotSerializableException(
"Unable to deserialize from bytes, manifest was: "
+ manifest
+ "! Bytes length: "
+ bytes.length);
}
}
}
// #simplest-custom-serializer
}
public static class SamplePayload {
private final Object payload;
public SamplePayload(Object payload) {
this.payload = payload;
}
public Object getPayload() {
return payload;
}
}
// #split-events-during-recovery
interface Version1 {};
interface Version2 {}
// #split-events-during-recovery
public
// #split-events-during-recovery
// V1 event:
static class UserDetailsChanged implements Version1 {
public final String name;
public final String address;
public UserDetailsChanged(String name, String address) {
this.name = name;
this.address = address;
}
}
// #split-events-during-recovery
public
// #split-events-during-recovery
// corresponding V2 events:
static class UserNameChanged implements Version2 {
public final String name;
public UserNameChanged(String name) {
this.name = name;
}
}
// #split-events-during-recovery
public
// #split-events-during-recovery
static class UserAddressChanged implements Version2 {
public final String address;
public UserAddressChanged(String address) {
this.address = address;
}
}
// #split-events-during-recovery
public
// #split-events-during-recovery
// event splitting adapter:
static class UserEventsAdapter implements EventAdapter {
@Override
public String manifest(Object event) {
return "";
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
if (event instanceof UserDetailsChanged) {
UserDetailsChanged c = (UserDetailsChanged) event;
if (c.name == null) return EventSeq.single(new UserAddressChanged(c.address));
else if (c.address == null) return EventSeq.single(new UserNameChanged(c.name));
else return EventSeq.create(new UserNameChanged(c.name), new UserAddressChanged(c.address));
} else {
return EventSeq.single(event);
}
}
@Override
public Object toJournal(Object event) {
return event;
}
}
// #split-events-during-recovery
public static class CustomerBlinked {
public final long customerId;
public CustomerBlinked(long customerId) {
this.customerId = customerId;
}
}
public
// #string-serializer-skip-deleved-event-by-manifest
static class EventDeserializationSkipped {
public static EventDeserializationSkipped instance = new EventDeserializationSkipped();
private EventDeserializationSkipped() {}
}
// #string-serializer-skip-deleved-event-by-manifest
public
// #string-serializer-skip-deleved-event-by-manifest
static class RemovedEventsAwareSerializer extends SerializerWithStringManifest {
private final Charset utf8 = StandardCharsets.UTF_8;
private final String customerBlinkedManifest = "blinked";
// unique identifier of the serializer
@Override
public int identifier() {
return 8337;
}
// extract manifest to be stored together with serialized object
@Override
public String manifest(Object o) {
if (o instanceof CustomerBlinked) return customerBlinkedManifest;
else return o.getClass().getName();
}
@Override
public byte[] toBinary(Object o) {
return o.toString().getBytes(utf8); // example serialization
}
@Override
public Object fromBinary(byte[] bytes, String manifest) {
if (customerBlinkedManifest.equals(manifest)) return EventDeserializationSkipped.instance;
else return new String(bytes, utf8);
}
}
// #string-serializer-skip-deleved-event-by-manifest
public
// #string-serializer-skip-deleved-event-by-manifest-adapter
static class SkippedEventsAwareAdapter implements EventAdapter {
@Override
public String manifest(Object event) {
return "";
}
@Override
public Object toJournal(Object event) {
return event;
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
if (event == EventDeserializationSkipped.instance) return EventSeq.empty();
else return EventSeq.single(event);
}
}
// #string-serializer-skip-deleved-event-by-manifest-adapter
// #string-serializer-handle-rename
public
// #string-serializer-handle-rename
static class RenamedEventAwareSerializer extends SerializerWithStringManifest {
private final Charset utf8 = StandardCharsets.UTF_8;
// unique identifier of the serializer
@Override
public int identifier() {
return 8337;
}
private final String oldPayloadClassName =
"docs.persistence.OldPayload"; // class NOT available anymore
private final String myPayloadClassName = SamplePayload.class.getName();
// extract manifest to be stored together with serialized object
@Override
public String manifest(Object o) {
return o.getClass().getName();
}
@Override
public byte[] toBinary(Object o) {
if (o instanceof SamplePayload) {
SamplePayload s = (SamplePayload) o;
return s.payload.toString().getBytes(utf8);
} else {
// previously also handled "old" events here.
throw new IllegalArgumentException(
"Unable to serialize to bytes, clazz was: " + o.getClass().getName());
}
}
@Override
public Object fromBinary(byte[] bytes, String manifest) throws NotSerializableException {
if (oldPayloadClassName.equals(manifest)) return new SamplePayload(new String(bytes, utf8));
else if (myPayloadClassName.equals(manifest))
return new SamplePayload(new String(bytes, utf8));
else throw new NotSerializableException("unexpected manifest [" + manifest + "]");
}
}
// #string-serializer-handle-rename
public
// #detach-models
// Domain model - highly optimised for domain language and maybe "fluent" usage
static class Customer {
public final String name;
public Customer(String name) {
this.name = name;
}
}
// #detach-models
public
// #detach-models
static class Seat {
public final String code;
public Seat(String code) {
this.code = code;
}
public SeatBooked bookFor(Customer customer) {
return new SeatBooked(code, customer);
}
}
// #detach-models
public
// #detach-models
static class SeatBooked {
public final String code;
public final Customer customer;
public SeatBooked(String code, Customer customer) {
this.code = code;
this.customer = customer;
}
}
// #detach-models
public
// #detach-models
// Data model - highly optimised for schema evolution and persistence
static class SeatBookedData {
public final String code;
public final String customerName;
public SeatBookedData(String code, String customerName) {
this.code = code;
this.customerName = customerName;
}
}
// #detach-models
// #detach-models-adapter
class DetachedModelsAdapter implements EventAdapter {
@Override
public String manifest(Object event) {
return "";
}
@Override
public Object toJournal(Object event) {
if (event instanceof SeatBooked) {
SeatBooked s = (SeatBooked) event;
return new SeatBookedData(s.code, s.customer.name);
} else {
throw new IllegalArgumentException("Unsupported: " + event.getClass());
}
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
if (event instanceof SeatBookedData) {
SeatBookedData d = (SeatBookedData) event;
return EventSeq.single(new SeatBooked(d.code, new Customer(d.customerName)));
} else {
throw new IllegalArgumentException("Unsupported: " + event.getClass());
}
}
}
// #detach-models-adapter
public
// #detach-models-adapter-json
static class JsonDataModelAdapter implements EventAdapter {
// use your favorite json library
private final ExampleJsonMarshaller marshaller = new ExampleJsonMarshaller();
@Override
public String manifest(Object event) {
return "";
}
@Override
public JsObject toJournal(Object event) {
return marshaller.toJson(event);
}
@Override
public EventSeq fromJournal(Object event, String manifest) {
if (event instanceof JsObject) {
JsObject json = (JsObject) event;
return EventSeq.single(marshaller.fromJson(json));
} else {
throw new IllegalArgumentException(
"Unable to fromJournal a non-JSON object! Was: " + event.getClass());
}
}
}
// #detach-models-adapter-json
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence;
// #persistent-actor-example
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.persistence.AbstractPersistentActor;
import org.apache.pekko.persistence.SnapshotOffer;
import java.io.Serializable;
import java.util.ArrayList;
class Cmd implements Serializable {
private static final long serialVersionUID = 1L;
private final String data;
public Cmd(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
class Evt implements Serializable {
private static final long serialVersionUID = 1L;
private final String data;
public Evt(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
class ExampleState implements Serializable {
private static final long serialVersionUID = 1L;
private final ArrayList<String> events;
public ExampleState() {
this(new ArrayList<>());
}
public ExampleState(ArrayList<String> events) {
this.events = events;
}
public ExampleState copy() {
return new ExampleState(new ArrayList<>(events));
}
public void update(Evt evt) {
events.add(evt.getData());
}
public int size() {
return events.size();
}
@Override
public String toString() {
return events.toString();
}
}
class ExamplePersistentActor extends AbstractPersistentActor {
private ExampleState state = new ExampleState();
private int snapShotInterval = 1000;
public int getNumEvents() {
return state.size();
}
@Override
public String persistenceId() {
return "sample-id-1";
}
@Override
public Receive createReceiveRecover() {
return receiveBuilder()
.match(Evt.class, state::update)
.match(SnapshotOffer.class, ss -> state = (ExampleState) ss.snapshot())
.build();
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Cmd.class,
c -> {
final String data = c.getData();
final Evt evt = new Evt(data + "-" + getNumEvents());
persist(
evt,
(Evt e) -> {
state.update(e);
getContext().getSystem().getEventStream().publish(e);
if (lastSequenceNr() % snapShotInterval == 0 && lastSequenceNr() != 0)
// IMPORTANT: create a copy of snapshot because ExampleState is mutable
saveSnapshot(state.copy());
});
})
.matchEquals("print", s -> System.out.println(state))
.build();
}
}
// #persistent-actor-example
public class PersistentActorExample {
public static void main(String... args) throws Exception {
final ActorSystem system = ActorSystem.create("example");
final ActorRef persistentActor =
system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-4-java8");
persistentActor.tell(new Cmd("foo"), null);
persistentActor.tell(new Cmd("baz"), null);
persistentActor.tell(new Cmd("bar"), null);
persistentActor.tell(new Cmd("buzz"), null);
persistentActor.tell("print", null);
Thread.sleep(10000);
system.terminate();
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence.query;
import java.util.HashSet;
import java.util.Set;
import org.apache.pekko.NotUsed;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.persistence.journal.WriteEventAdapter;
import org.apache.pekko.persistence.journal.Tagged;
import org.apache.pekko.persistence.query.EventEnvelope;
import org.apache.pekko.persistence.query.Sequence;
import org.apache.pekko.persistence.query.PersistenceQuery;
import org.apache.pekko.persistence.query.journal.leveldb.javadsl.LeveldbReadJournal;
import org.apache.pekko.stream.javadsl.Source;
public class LeveldbPersistenceQueryDocTest {
final ActorSystem system = ActorSystem.create();
public void demonstrateReadJournal() {
// #get-read-journal
LeveldbReadJournal queries =
PersistenceQuery.get(system)
.getReadJournalFor(LeveldbReadJournal.class, LeveldbReadJournal.Identifier());
// #get-read-journal
}
public void demonstrateEventsByPersistenceId() {
// #EventsByPersistenceId
LeveldbReadJournal queries =
PersistenceQuery.get(system)
.getReadJournalFor(LeveldbReadJournal.class, LeveldbReadJournal.Identifier());
Source<EventEnvelope, NotUsed> source =
queries.eventsByPersistenceId("some-persistence-id", 0, Long.MAX_VALUE);
// #EventsByPersistenceId
}
public void demonstrateAllPersistenceIds() {
// #AllPersistenceIds
LeveldbReadJournal queries =
PersistenceQuery.get(system)
.getReadJournalFor(LeveldbReadJournal.class, LeveldbReadJournal.Identifier());
Source<String, NotUsed> source = queries.persistenceIds();
// #AllPersistenceIds
}
public void demonstrateEventsByTag() {
// #EventsByTag
LeveldbReadJournal queries =
PersistenceQuery.get(system)
.getReadJournalFor(LeveldbReadJournal.class, LeveldbReadJournal.Identifier());
Source<EventEnvelope, NotUsed> source = queries.eventsByTag("green", new Sequence(0L));
// #EventsByTag
}
public
// #tagger
static class MyTaggingEventAdapter implements WriteEventAdapter {
@Override
public Object toJournal(Object event) {
if (event instanceof String) {
String s = (String) event;
Set<String> tags = new HashSet<String>();
if (s.contains("green")) tags.add("green");
if (s.contains("black")) tags.add("black");
if (s.contains("blue")) tags.add("blue");
if (tags.isEmpty()) return event;
else return new Tagged(event, tags);
} else {
return event;
}
}
@Override
public String manifest(Object event) {
return "";
}
}
// #tagger
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence.query;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.persistence.query.EventEnvelope;
import org.apache.pekko.persistence.query.Offset;
import org.apache.pekko.serialization.Serialization;
import org.apache.pekko.serialization.SerializationExtension;
import org.apache.pekko.stream.*;
import org.apache.pekko.stream.stage.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
// #events-by-tag-publisher
public class MyEventsByTagSource extends GraphStage<SourceShape<EventEnvelope>> {
public Outlet<EventEnvelope> out = Outlet.create("MyEventByTagSource.out");
private static final String QUERY =
"SELECT id, persistence_id, seq_nr, serializer_id, serializer_manifest, payload "
+ "FROM journal WHERE tag = ? AND id > ? "
+ "ORDER BY id LIMIT ?";
enum Continue {
INSTANCE;
}
private static final int LIMIT = 1000;
private final Connection connection;
private final String tag;
private final long initialOffset;
private final Duration refreshInterval;
// assumes a shared connection, could also be a factory for creating connections/pool
public MyEventsByTagSource(
Connection connection, String tag, long initialOffset, Duration refreshInterval) {
this.connection = connection;
this.tag = tag;
this.initialOffset = initialOffset;
this.refreshInterval = refreshInterval;
}
@Override
public Attributes initialAttributes() {
return Attributes.apply(ActorAttributes.IODispatcher());
}
@Override
public SourceShape<EventEnvelope> shape() {
return SourceShape.of(out);
}
@Override
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
return new TimerGraphStageLogic(shape()) {
private ActorSystem system = materializer().system();
private long currentOffset = initialOffset;
private List<EventEnvelope> buf = new LinkedList<>();
private final Serialization serialization = SerializationExtension.get(system);
@Override
public void preStart() {
scheduleWithFixedDelay(Continue.INSTANCE, refreshInterval, refreshInterval);
}
@Override
public void onTimer(Object timerKey) {
query();
deliver();
}
private void deliver() {
if (isAvailable(out) && !buf.isEmpty()) {
push(out, buf.remove(0));
}
}
private void query() {
if (buf.isEmpty()) {
try (PreparedStatement s = connection.prepareStatement(QUERY)) {
s.setString(1, tag);
s.setLong(2, currentOffset);
s.setLong(3, LIMIT);
try (ResultSet rs = s.executeQuery()) {
final List<EventEnvelope> res = new ArrayList<>(LIMIT);
while (rs.next()) {
Object deserialized =
serialization
.deserialize(
rs.getBytes("payload"),
rs.getInt("serializer_id"),
rs.getString("serializer_manifest"))
.get();
currentOffset = rs.getLong("id");
res.add(
new EventEnvelope(
Offset.sequence(currentOffset),
rs.getString("persistence_id"),
rs.getLong("seq_nr"),
deserialized,
System.currentTimeMillis()));
}
buf = res;
}
} catch (Exception e) {
failStage(e);
}
}
}
{
setHandler(
out,
new AbstractOutHandler() {
@Override
public void onPull() {
query();
deliver();
}
});
}
};
}
}
// #events-by-tag-publisher

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence.testkit;
import org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.actor.typed.Behavior;
import org.apache.pekko.persistence.testkit.PersistenceTestKitPlugin;
import org.apache.pekko.persistence.testkit.PersistenceTestKitSnapshotPlugin;
import org.apache.pekko.persistence.testkit.javadsl.PersistenceTestKit;
import org.apache.pekko.persistence.testkit.javadsl.SnapshotTestKit;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
public class Configuration {
// #testkit-typed-conf
public class PersistenceTestKitConfig {
Config conf =
PersistenceTestKitPlugin.getInstance()
.config()
.withFallback(ConfigFactory.defaultApplication());
ActorSystem<Command> system = ActorSystem.create(new SomeBehavior(), "example", conf);
PersistenceTestKit testKit = PersistenceTestKit.create(system);
}
// #testkit-typed-conf
// #snapshot-typed-conf
public class SnapshotTestKitConfig {
Config conf =
PersistenceTestKitSnapshotPlugin.getInstance()
.config()
.withFallback(ConfigFactory.defaultApplication());
ActorSystem<Command> system = ActorSystem.create(new SomeBehavior(), "example", conf);
SnapshotTestKit testKit = SnapshotTestKit.create(system);
}
// #snapshot-typed-conf
}
class SomeBehavior extends Behavior<Command> {
public SomeBehavior() {
super(1);
}
}
class Command {}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence.testkit;
import org.apache.pekko.actor.testkit.typed.javadsl.TestKitJunitResource;
import com.typesafe.config.ConfigFactory;
import jdocs.AbstractJavaTest;
import org.junit.ClassRule;
import org.junit.Test;
import java.util.UUID;
// #imports
import org.apache.pekko.persistence.testkit.javadsl.PersistenceInit;
import org.apache.pekko.Done;
import java.time.Duration;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
// #imports
public class PersistenceInitTest extends AbstractJavaTest {
@ClassRule
public static final TestKitJunitResource testKit =
new TestKitJunitResource(
ConfigFactory.parseString(
"akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n"
+ "akka.persistence.journal.inmem.test-serialization = on \n"
+ "akka.persistence.snapshot-store.plugin = \"akka.persistence.snapshot-store.local\" \n"
+ "akka.persistence.snapshot-store.local.dir = \"target/snapshot-"
+ UUID.randomUUID().toString()
+ "\" \n")
.withFallback(ConfigFactory.defaultApplication()));
@Test
public void testInit() throws Exception {
// #init
Duration timeout = Duration.ofSeconds(5);
CompletionStage<Done> done =
PersistenceInit.initializeDefaultPlugins(testKit.system(), timeout);
done.toCompletableFuture().get(timeout.getSeconds(), TimeUnit.SECONDS);
// #init
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.persistence.testkit;
import org.apache.pekko.actor.testkit.typed.javadsl.TestKitJunitResource;
import org.apache.pekko.actor.typed.ActorRef;
import org.apache.pekko.persistence.testkit.JournalOperation;
import org.apache.pekko.persistence.testkit.PersistenceTestKitPlugin;
import org.apache.pekko.persistence.testkit.ProcessingPolicy;
import org.apache.pekko.persistence.testkit.ProcessingResult;
import org.apache.pekko.persistence.testkit.ProcessingSuccess;
import org.apache.pekko.persistence.testkit.StorageFailure;
import org.apache.pekko.persistence.testkit.WriteEvents;
import org.apache.pekko.persistence.testkit.javadsl.PersistenceTestKit;
import org.apache.pekko.persistence.typed.PersistenceId;
import com.typesafe.config.ConfigFactory;
import jdocs.AbstractJavaTest;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
// #policy-test
public class PersistenceTestKitPolicySampleTest extends AbstractJavaTest {
@ClassRule
public static final TestKitJunitResource testKit =
new TestKitJunitResource(
PersistenceTestKitPlugin.getInstance()
.config()
.withFallback(ConfigFactory.defaultApplication()));
PersistenceTestKit persistenceTestKit = PersistenceTestKit.create(testKit.system());
@Before
public void beforeEach() {
persistenceTestKit.clearAll();
persistenceTestKit.resetPolicy();
}
@Test
public void test() {
SampleEventStoragePolicy policy = new SampleEventStoragePolicy();
persistenceTestKit.withPolicy(policy);
PersistenceId persistenceId = PersistenceId.ofUniqueId("some-id");
ActorRef<YourPersistentBehavior.Cmd> ref =
testKit.spawn(YourPersistentBehavior.create(persistenceId));
YourPersistentBehavior.Cmd cmd = new YourPersistentBehavior.Cmd("data");
ref.tell(cmd);
persistenceTestKit.expectNothingPersisted(persistenceId.id());
}
static class SampleEventStoragePolicy implements ProcessingPolicy<JournalOperation> {
@Override
public ProcessingResult tryProcess(String processId, JournalOperation processingUnit) {
if (processingUnit instanceof WriteEvents) {
return StorageFailure.create();
} else {
return ProcessingSuccess.getInstance();
}
}
}
}
// #policy-test

Some files were not shown because too many files have changed in this diff Show more