Translate Getting Started Guide to Typed, #25998
* convert example code * update doc content * update diagrams * rewrite parental supervison part
This commit is contained in:
parent
4966d5262e
commit
76276e6504
77 changed files with 6235 additions and 43 deletions
|
|
@ -102,7 +102,7 @@ public class Messages {
|
|||
}
|
||||
}
|
||||
|
||||
public static enum Swap {
|
||||
public enum Swap {
|
||||
Swap
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class FSMDocTest extends AbstractJavaTest {
|
|||
system = null;
|
||||
}
|
||||
|
||||
public static enum StateType {
|
||||
public enum StateType {
|
||||
SomeState,
|
||||
Processing,
|
||||
Idle,
|
||||
|
|
@ -40,12 +40,12 @@ public class FSMDocTest extends AbstractJavaTest {
|
|||
Error
|
||||
}
|
||||
|
||||
public static enum Messages {
|
||||
public enum Messages {
|
||||
WillDo,
|
||||
Tick
|
||||
}
|
||||
|
||||
public static enum Data {
|
||||
public enum Data {
|
||||
Foo,
|
||||
Bar
|
||||
};
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public class ClusterShardingTest {
|
|||
static//#counter-actor
|
||||
public class Counter extends AbstractPersistentActor {
|
||||
|
||||
public static enum CounterOp {
|
||||
public enum CounterOp {
|
||||
INCREMENT, DECREMENT
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Device extends AbstractActor {
|
|||
|
||||
//#read-protocol-2
|
||||
public static final class ReadTemperature {
|
||||
long requestId;
|
||||
final long requestId;
|
||||
|
||||
public ReadTemperature(long requestId) {
|
||||
this.requestId = requestId;
|
||||
|
|
@ -39,8 +39,8 @@ class Device extends AbstractActor {
|
|||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
long requestId;
|
||||
Optional<Double> value;
|
||||
final long requestId;
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(long requestId, Optional<Double> value) {
|
||||
this.requestId = requestId;
|
||||
|
|
|
|||
|
|
@ -77,22 +77,47 @@ public class DeviceGroup extends AbstractActor {
|
|||
public Temperature(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Temperature that = (Temperature) o;
|
||||
|
||||
return Double.compare(that.value, value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long temp = Double.doubleToLongBits(value);
|
||||
return (int) (temp ^ (temp >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Temperature{" +
|
||||
"value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TemperatureNotAvailable implements TemperatureReading {
|
||||
public enum TemperatureNotAvailable implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static final class DeviceNotAvailable implements TemperatureReading {
|
||||
public enum DeviceNotAvailable implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static final class DeviceTimedOut implements TemperatureReading {
|
||||
public enum DeviceTimedOut implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
//#query-protocol
|
||||
|
||||
|
||||
final Map<String, ActorRef> deviceIdToActor = new HashMap<>();
|
||||
final Map<ActorRef, String> actorToDeviceId = new HashMap<>();
|
||||
final long nextCollectionId = 0L;
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
|
|
|
|||
|
|
@ -76,17 +76,17 @@ public class DeviceGroupQuery extends AbstractActor {
|
|||
ActorRef deviceActor = getSender();
|
||||
DeviceGroup.TemperatureReading reading = r.value
|
||||
.map(v -> (DeviceGroup.TemperatureReading) new DeviceGroup.Temperature(v))
|
||||
.orElse(new DeviceGroup.TemperatureNotAvailable());
|
||||
.orElse(DeviceGroup.TemperatureNotAvailable.INSTANCE);
|
||||
receivedResponse(deviceActor, reading, stillWaiting, repliesSoFar);
|
||||
})
|
||||
.match(Terminated.class, t -> {
|
||||
receivedResponse(t.getActor(), new DeviceGroup.DeviceNotAvailable(), stillWaiting, repliesSoFar);
|
||||
receivedResponse(t.getActor(), DeviceGroup.DeviceNotAvailable.INSTANCE, stillWaiting, repliesSoFar);
|
||||
})
|
||||
.match(CollectionTimeout.class, t -> {
|
||||
Map<String, DeviceGroup.TemperatureReading> replies = new HashMap<>(repliesSoFar);
|
||||
for (ActorRef deviceActor : stillWaiting) {
|
||||
String deviceId = actorToDeviceId.get(deviceActor);
|
||||
replies.put(deviceId, new DeviceGroup.DeviceTimedOut());
|
||||
replies.put(deviceId, DeviceGroup.DeviceTimedOut.INSTANCE);
|
||||
}
|
||||
requester.tell(new DeviceGroup.RespondAllTemperatures(requestId, replies), getSelf());
|
||||
getContext().stop(getSelf());
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class DeviceGroupQueryTest extends JUnitSuite {
|
|||
expectedTemperatures.put("device1", new DeviceGroup.Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new DeviceGroup.Temperature(2.0));
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-normal
|
||||
|
||||
|
|
@ -101,10 +101,10 @@ public class DeviceGroupQueryTest extends JUnitSuite {
|
|||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, DeviceGroup.TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new DeviceGroup.TemperatureNotAvailable());
|
||||
expectedTemperatures.put("device1", DeviceGroup.TemperatureNotAvailable.INSTANCE);
|
||||
expectedTemperatures.put("device2", new DeviceGroup.Temperature(2.0));
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-no-reading
|
||||
|
||||
|
|
@ -137,9 +137,9 @@ public class DeviceGroupQueryTest extends JUnitSuite {
|
|||
|
||||
Map<String, DeviceGroup.TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new DeviceGroup.Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new DeviceGroup.DeviceNotAvailable());
|
||||
expectedTemperatures.put("device2", DeviceGroup.DeviceNotAvailable.INSTANCE);
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-stopped
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ public class DeviceGroupQueryTest extends JUnitSuite {
|
|||
expectedTemperatures.put("device1", new DeviceGroup.Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new DeviceGroup.Temperature(2.0));
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-stopped-later
|
||||
|
||||
|
|
@ -209,22 +209,10 @@ public class DeviceGroupQueryTest extends JUnitSuite {
|
|||
|
||||
Map<String, DeviceGroup.TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new DeviceGroup.Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new DeviceGroup.DeviceTimedOut());
|
||||
expectedTemperatures.put("device2", DeviceGroup.DeviceTimedOut.INSTANCE);
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-timeout
|
||||
|
||||
public static void assertEqualTemperatures(Map<String, DeviceGroup.TemperatureReading> expected, Map<String, DeviceGroup.TemperatureReading> actual) {
|
||||
for (Map.Entry<String, DeviceGroup.TemperatureReading> entry : expected.entrySet()) {
|
||||
DeviceGroup.TemperatureReading expectedReading = entry.getValue();
|
||||
DeviceGroup.TemperatureReading actualReading = actual.get(entry.getKey());
|
||||
assertNotNull(actualReading);
|
||||
assertEquals(expectedReading.getClass(), actualReading.getClass());
|
||||
if (expectedReading instanceof DeviceGroup.Temperature) {
|
||||
assertEquals(((DeviceGroup.Temperature) expectedReading).value, ((DeviceGroup.Temperature) actualReading).value, 0.01);
|
||||
}
|
||||
}
|
||||
assertEquals(expected.size(), actual.size());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import static org.junit.Assert.assertNotEquals;
|
|||
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import static jdocs.tutorial_5.DeviceGroupQueryTest.assertEqualTemperatures;
|
||||
|
||||
public class DeviceGroupTest extends JUnitSuite {
|
||||
|
||||
static ActorSystem system;
|
||||
|
|
@ -166,9 +164,9 @@ public class DeviceGroupTest extends JUnitSuite {
|
|||
Map<String, DeviceGroup.TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new DeviceGroup.Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new DeviceGroup.Temperature(2.0));
|
||||
expectedTemperatures.put("device3", new DeviceGroup.TemperatureNotAvailable());
|
||||
expectedTemperatures.put("device3", DeviceGroup.TemperatureNotAvailable.INSTANCE);
|
||||
|
||||
assertEqualTemperatures(expectedTemperatures, response.temperatures);
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#group-query-integration-test
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_1;
|
||||
|
||||
import akka.actor.typed.PreRestart;
|
||||
import akka.actor.typed.SupervisorStrategy;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.typed.PostStop;
|
||||
|
||||
//#print-refs
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.ActorSystem;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
class PrintMyActorRefActor extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(PrintMyActorRefActor::new);
|
||||
}
|
||||
|
||||
private final ActorContext<String> context;
|
||||
|
||||
private PrintMyActorRefActor(ActorContext<String> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessageEquals("printit", this::printIt)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> printIt() {
|
||||
ActorRef<String> secondRef = context.spawn(Behaviors.empty(), "second-actor");
|
||||
System.out.println("Second: " + secondRef);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
//#print-refs
|
||||
|
||||
//#start-stop
|
||||
class StartStopActor1 extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(context -> new StartStopActor1());
|
||||
}
|
||||
|
||||
private StartStopActor1() {
|
||||
System.out.println("first started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessageEquals("stop", Behaviors::stopped)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> postStop() {
|
||||
System.out.println("first stopped");
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StartStopActor2 extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(context -> new StartStopActor2());
|
||||
}
|
||||
|
||||
private StartStopActor2() {
|
||||
System.out.println("second started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> postStop() {
|
||||
System.out.println("second stopped");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
//#start-stop
|
||||
|
||||
//#supervise
|
||||
class SupervisingActor extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(SupervisingActor::new);
|
||||
}
|
||||
|
||||
private final ActorRef<String> child;
|
||||
|
||||
private SupervisingActor(ActorContext<String> context) {
|
||||
child = context.spawn(
|
||||
Behaviors.supervise(SupervisedActor.createBehavior()).onFailure(SupervisorStrategy.restart()),
|
||||
"supervised-actor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessageEquals("failChild", this::failChild)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> failChild() {
|
||||
child.tell("fail");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class SupervisedActor extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(context -> new SupervisedActor());
|
||||
}
|
||||
|
||||
private SupervisedActor() {
|
||||
System.out.println("supervised actor started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessageEquals("fail", this::fail)
|
||||
.onSignal(PreRestart.class, signal -> preRestart())
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> fail() {
|
||||
System.out.println("supervised actor fails now");
|
||||
throw new RuntimeException("I failed!");
|
||||
}
|
||||
|
||||
private Behavior<String> preRestart() {
|
||||
System.out.println("second will be restarted");
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<String> postStop() {
|
||||
System.out.println("second stopped");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
//#supervise
|
||||
|
||||
|
||||
//#print-refs
|
||||
|
||||
class Main extends AbstractBehavior<String> {
|
||||
|
||||
static Behavior<String> createBehavior() {
|
||||
return Behaviors.setup(Main::new);
|
||||
}
|
||||
|
||||
private final ActorContext<String> context;
|
||||
|
||||
private Main(ActorContext<String> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessageEquals("start", this::start)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<String> start() {
|
||||
ActorRef<String> firstRef = context.spawn(PrintMyActorRefActor.createBehavior(), "first-actor");
|
||||
|
||||
System.out.println("First: " + firstRef);
|
||||
firstRef.tell("printit");
|
||||
return Behaviors.same();
|
||||
}
|
||||
}
|
||||
|
||||
public class ActorHierarchyExperiments {
|
||||
public static void main(String[] args) {
|
||||
ActorSystem.create(Main.createBehavior(), "testSystem");
|
||||
}
|
||||
}
|
||||
//#print-refs
|
||||
|
||||
|
||||
class ActorHierarchyExperimentsTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testStartAndStopActors() {
|
||||
//#start-stop-main
|
||||
ActorRef<String> first = testKit.spawn(StartStopActor1.createBehavior(), "first");
|
||||
first.tell("stop");
|
||||
//#start-stop-main
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuperviseActors() throws Exception {
|
||||
//#supervise-main
|
||||
ActorRef<String> supervisingActor = testKit.spawn(SupervisingActor.createBehavior(), "supervising-actor");
|
||||
supervisingActor.tell("failChild");
|
||||
//#supervise-main
|
||||
Thread.sleep(200); // allow for the println/logging to complete
|
||||
}
|
||||
}
|
||||
18
akka-docs/src/test/java/jdocs/typed/tutorial_2/IotMain.java
Normal file
18
akka-docs/src/test/java/jdocs/typed/tutorial_2/IotMain.java
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_2;
|
||||
|
||||
//#iot-app
|
||||
import akka.actor.typed.ActorSystem;
|
||||
|
||||
public class IotMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create ActorSystem and top level supervisor
|
||||
ActorSystem.create(IotSupervisor.createBehavior(), "iot-system");
|
||||
}
|
||||
|
||||
}
|
||||
//#iot-app
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_2;
|
||||
|
||||
//#iot-supervisor
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
public class IotSupervisor extends AbstractBehavior<Void> {
|
||||
|
||||
public static Behavior<Void> createBehavior() {
|
||||
return Behaviors.setup(IotSupervisor::new);
|
||||
}
|
||||
|
||||
private final ActorContext<Void> context;
|
||||
|
||||
public IotSupervisor(ActorContext<Void> context) {
|
||||
this.context = context;
|
||||
context.getLog().info("IoT Application started");
|
||||
}
|
||||
|
||||
// No need to handle any messages
|
||||
@Override
|
||||
public Receive<Void> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private IotSupervisor postStop() {
|
||||
context.getLog().info("IoT Application stopped");
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
//#iot-supervisor
|
||||
73
akka-docs/src/test/java/jdocs/typed/tutorial_3/Device.java
Normal file
73
akka-docs/src/test/java/jdocs/typed/tutorial_3/Device.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3;
|
||||
|
||||
//#full-device
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
//#full-device
|
||||
import static jdocs.typed.tutorial_3.DeviceProtocol.*;
|
||||
/*
|
||||
//#full-device
|
||||
import static com.lightbend.akka.sample.DeviceProtocol.*;
|
||||
//#full-device
|
||||
*/
|
||||
//#full-device
|
||||
|
||||
public class Device extends AbstractBehavior<DeviceMessage> {
|
||||
|
||||
public static Behavior<DeviceMessage> createBehavior(String groupId, String deviceId) {
|
||||
return Behaviors.setup(context -> new Device(context, groupId, deviceId));
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceMessage> context;
|
||||
private final String groupId;
|
||||
private final String deviceId;
|
||||
|
||||
private Optional<Double> lastTemperatureReading = Optional.empty();
|
||||
|
||||
public Device(ActorContext<DeviceMessage> context, String groupId, String deviceId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
|
||||
context.getLog().info("Device actor {}-{} started", groupId, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<DeviceMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RecordTemperature.class, this::recordTemperature)
|
||||
.onMessage(ReadTemperature.class, this::readTemperature)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> recordTemperature(RecordTemperature r) {
|
||||
context.getLog().info("Recorded temperature reading {} with {}", r.value, r.requestId);
|
||||
lastTemperatureReading = Optional.of(r.value);
|
||||
r.replyTo.tell(new TemperatureRecorded(r.requestId));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> readTemperature(ReadTemperature r) {
|
||||
r.replyTo.tell(new RespondTemperature(r.requestId, lastTemperatureReading));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> postStop() {
|
||||
context.getLog().info("Device actor {}-{} stopped", groupId, deviceId);
|
||||
return Behaviors.stopped();
|
||||
}
|
||||
}
|
||||
//#full-device
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
//#write-protocol
|
||||
public static final class RecordTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final double value;
|
||||
final ActorRef<TemperatureRecorded> replyTo;
|
||||
|
||||
public RecordTemperature(long requestId, double value, ActorRef<TemperatureRecorded> replyTo){
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TemperatureRecorded {
|
||||
final long requestId;
|
||||
|
||||
public TemperatureRecorded(long requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
//#write-protocol
|
||||
|
||||
public static final class ReadTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final ActorRef<RespondTemperature> replyTo;
|
||||
|
||||
public ReadTemperature(long requestId, ActorRef<RespondTemperature> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
final long requestId;
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(long requestId, Optional<Double> value) {
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3;
|
||||
|
||||
//#device-read-test
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
//#device-read-test
|
||||
import static jdocs.typed.tutorial_3.DeviceProtocol.*;
|
||||
/*
|
||||
//#device-read-test
|
||||
import static com.lightbend.akka.sample.DeviceProtocol.*;
|
||||
|
||||
public class DeviceTest {
|
||||
//#device-read-test
|
||||
*/
|
||||
public class DeviceTest extends org.scalatest.junit.JUnitSuite {
|
||||
//#device-read-test
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testReplyWithEmptyReadingIfNoTemperatureIsKnown() {
|
||||
TestProbe<RespondTemperature> probe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
deviceActor.tell(new ReadTemperature(42L, probe.getRef()));
|
||||
RespondTemperature response = probe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(42L, response.requestId);
|
||||
assertEquals(Optional.empty(), response.value);
|
||||
}
|
||||
//#device-read-test
|
||||
|
||||
//#device-write-read-test
|
||||
@Test
|
||||
public void testReplyWithLatestTemperatureReading() {
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
TestProbe<RespondTemperature> readProbe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
|
||||
deviceActor.tell(new RecordTemperature(1L, 24.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(2L, readProbe.getRef()));
|
||||
RespondTemperature response1 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(2L, response1.requestId);
|
||||
assertEquals(Optional.of(24.0), response1.value);
|
||||
|
||||
deviceActor.tell(new RecordTemperature(3L, 55.0, recordProbe.getRef()));
|
||||
assertEquals(3L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(4L, readProbe.getRef()));
|
||||
RespondTemperature response2 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(4L, response2.requestId);
|
||||
assertEquals(Optional.of(55.0), response2.value);
|
||||
}
|
||||
//#device-write-read-test
|
||||
|
||||
//#device-read-test
|
||||
}
|
||||
//#device-read-test
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3.inprogress1;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
//#read-protocol-1
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {
|
||||
}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
public static final class ReadTemperature implements DeviceMessage {
|
||||
final ActorRef<RespondTemperature> replyTo;
|
||||
|
||||
public ReadTemperature(ActorRef<RespondTemperature> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(Optional<Double> value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#read-protocol-1
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3.inprogress2;
|
||||
|
||||
//#device-with-read
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
//#device-with-read
|
||||
import static jdocs.typed.tutorial_3.inprogress2.DeviceProtocol.*;
|
||||
/*
|
||||
//#device-with-read
|
||||
import static com.lightbend.akka.sample.DeviceProtocol.*;
|
||||
//#device-with-read
|
||||
*/
|
||||
//#device-with-read
|
||||
|
||||
public class Device extends AbstractBehavior<DeviceMessage> {
|
||||
|
||||
public static Behavior<DeviceMessage> createBehavior(String groupId, String deviceId) {
|
||||
return Behaviors.setup(context -> new Device(context, groupId, deviceId));
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceMessage> context;
|
||||
private final String groupId;
|
||||
private final String deviceId;
|
||||
|
||||
private Optional<Double> lastTemperatureReading = Optional.empty();
|
||||
|
||||
public Device(ActorContext<DeviceMessage> context, String groupId, String deviceId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
|
||||
context.getLog().info("Device actor {}-{} started", groupId, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<DeviceMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(ReadTemperature.class, this::readTemperature)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> readTemperature(ReadTemperature r) {
|
||||
r.replyTo.tell(new RespondTemperature(r.requestId, lastTemperatureReading));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Device postStop() {
|
||||
context.getLog().info("Device actor {}-{} stopped", groupId, deviceId);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#device-with-read
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3.inprogress2;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
//#read-protocol-2
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {
|
||||
}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
public static final class ReadTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final ActorRef<RespondTemperature> replyTo;
|
||||
|
||||
public ReadTemperature(long requestId, ActorRef<RespondTemperature> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
final long requestId;
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(long requestId, Optional<Double> value) {
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#read-protocol-2
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_3.inprogress3;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {
|
||||
}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
//#write-protocol-1
|
||||
public static final class RecordTemperature implements DeviceMessage {
|
||||
final double value;
|
||||
|
||||
public RecordTemperature(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
//#write-protocol-1
|
||||
}
|
||||
|
||||
74
akka-docs/src/test/java/jdocs/typed/tutorial_4/Device.java
Normal file
74
akka-docs/src/test/java/jdocs/typed/tutorial_4/Device.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
//#device-with-passivate
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
//#device-with-passivate
|
||||
import static jdocs.typed.tutorial_4.DeviceProtocol.*;
|
||||
/*
|
||||
//#device-with-passivate
|
||||
import static com.lightbend.akka.sample.DeviceProtocol.*;
|
||||
//#device-with-passivate
|
||||
*/
|
||||
//#device-with-passivate
|
||||
|
||||
public class Device extends AbstractBehavior<DeviceMessage> {
|
||||
|
||||
public static Behavior<DeviceMessage> createBehavior(String groupId, String deviceId) {
|
||||
return Behaviors.setup(context -> new Device(context, groupId, deviceId));
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceMessage> context;
|
||||
private final String groupId;
|
||||
private final String deviceId;
|
||||
|
||||
private Optional<Double> lastTemperatureReading = Optional.empty();
|
||||
|
||||
public Device(ActorContext<DeviceMessage> context, String groupId, String deviceId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
|
||||
context.getLog().info("Device actor {}-{} started", groupId, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<DeviceMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RecordTemperature.class, this::recordTemperature)
|
||||
.onMessage(ReadTemperature.class, this::readTemperature)
|
||||
.onMessage(Passivate.class, m -> Behaviors.stopped())
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> recordTemperature(RecordTemperature r) {
|
||||
context.getLog().info("Recorded temperature reading {} with {}", r.value, r.requestId);
|
||||
lastTemperatureReading = Optional.of(r.value);
|
||||
r.replyTo.tell(new TemperatureRecorded(r.requestId));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> readTemperature(ReadTemperature r) {
|
||||
r.replyTo.tell(new RespondTemperature(r.requestId, lastTemperatureReading));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> postStop() {
|
||||
context.getLog().info("Device actor {}-{} stopped", groupId, deviceId);
|
||||
return Behaviors.stopped();
|
||||
}
|
||||
}
|
||||
//#device-with-passivate
|
||||
114
akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java
Normal file
114
akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static jdocs.typed.tutorial_4.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_4.DeviceProtocol.DeviceMessage;
|
||||
|
||||
//#device-group-full
|
||||
//#device-group-remove
|
||||
//#device-group-register
|
||||
public class DeviceGroup extends AbstractBehavior<DeviceGroupMessage> {
|
||||
|
||||
public static Behavior<DeviceGroupMessage> createBehavior(String groupId) {
|
||||
return Behaviors.setup(context -> new DeviceGroup(context, groupId));
|
||||
}
|
||||
|
||||
//#device-terminated
|
||||
private class DeviceTerminated implements DeviceGroupMessage{
|
||||
public final ActorRef<DeviceProtocol.DeviceMessage> device;
|
||||
public final String groupId;
|
||||
public final String deviceId;
|
||||
|
||||
DeviceTerminated(ActorRef<DeviceProtocol.DeviceMessage> device, String groupId, String deviceId) {
|
||||
this.device = device;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
}
|
||||
//#device-terminated
|
||||
|
||||
private final ActorContext<DeviceGroupMessage> context;
|
||||
private final String groupId;
|
||||
private final Map<String, ActorRef<DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
|
||||
public DeviceGroup(ActorContext<DeviceGroupMessage> context, String groupId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
context.getLog().info("DeviceGroup {} started", groupId);
|
||||
}
|
||||
|
||||
private DeviceGroup onTrackDevice(RequestTrackDevice trackMsg) {
|
||||
if (this.groupId.equals(trackMsg.groupId)) {
|
||||
ActorRef<DeviceMessage> deviceActor = deviceIdToActor.get(trackMsg.deviceId);
|
||||
if (deviceActor != null) {
|
||||
trackMsg.replyTo.tell(new DeviceRegistered(deviceActor));
|
||||
} else {
|
||||
context.getLog().info("Creating device actor for {}", trackMsg.deviceId);
|
||||
deviceActor = context.spawn(Device.createBehavior(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId);
|
||||
//#device-group-register
|
||||
context.watchWith(deviceActor, new DeviceTerminated(deviceActor, groupId, trackMsg.deviceId));
|
||||
//#device-group-register
|
||||
deviceIdToActor.put(trackMsg.deviceId, deviceActor);
|
||||
trackMsg.replyTo.tell(new DeviceRegistered(deviceActor));
|
||||
}
|
||||
} else {
|
||||
context.getLog().warning(
|
||||
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
|
||||
groupId, this.groupId
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//#device-group-register
|
||||
//#device-group-remove
|
||||
|
||||
private DeviceGroup onDeviceList(RequestDeviceList r) {
|
||||
r.replyTo.tell(new ReplyDeviceList(r.requestId, deviceIdToActor.keySet()));
|
||||
return this;
|
||||
}
|
||||
//#device-group-remove
|
||||
|
||||
private DeviceGroup onTerminated(DeviceTerminated t) {
|
||||
context.getLog().info("Device actor for {} has been terminated", t.deviceId);
|
||||
deviceIdToActor.remove(t.deviceId);
|
||||
return this;
|
||||
}
|
||||
//#device-group-register
|
||||
|
||||
@Override
|
||||
public Receive<DeviceGroupMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RequestTrackDevice.class, this::onTrackDevice)
|
||||
//#device-group-register
|
||||
//#device-group-remove
|
||||
.onMessage(RequestDeviceList.class, r -> r.groupId.equals(groupId), this::onDeviceList)
|
||||
//#device-group-remove
|
||||
.onMessage(DeviceTerminated.class, this::onTerminated)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
//#device-group-register
|
||||
.build();
|
||||
}
|
||||
|
||||
private DeviceGroup postStop() {
|
||||
context.getLog().info("DeviceGroup {} stopped", groupId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
//#device-group-register
|
||||
//#device-group-remove
|
||||
//#device-group-full
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static jdocs.typed.tutorial_4.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_4.DeviceProtocol.*;
|
||||
|
||||
public class DeviceGroupTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
//#device-group-test-registration
|
||||
@Test
|
||||
public void testReplyToRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// another deviceId
|
||||
groupActor.tell(new RequestTrackDevice("group", "device3", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertNotEquals(registered1.device, registered2.device);
|
||||
|
||||
// Check that the device actors are working
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
registered1.device.tell(new RecordTemperature(0L, 1.0, recordProbe.getRef()));
|
||||
assertEquals(0L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
registered2.device.tell(new RecordTemperature(1L, 2.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreWrongRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
groupActor.tell(new RequestTrackDevice("wrongGroup", "device1", probe.getRef()));
|
||||
probe.expectNoMessage();
|
||||
}
|
||||
//#device-group-test-registration
|
||||
|
||||
//#device-group-test3
|
||||
@Test
|
||||
public void testReturnSameActorForSameDeviceId() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// registering same again should be idempotent
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertEquals(registered1.device, registered2.device);
|
||||
}
|
||||
//#device-group-test3
|
||||
|
||||
//#device-group-list-terminate-test
|
||||
@Test
|
||||
public void testListActiveDevices() {
|
||||
TestProbe<DeviceRegistered> registeredProbe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device1", registeredProbe.getRef()));
|
||||
registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device2", registeredProbe.getRef()));
|
||||
registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
TestProbe<ReplyDeviceList> deviceListProbe = testKit.createTestProbe(ReplyDeviceList.class);
|
||||
|
||||
groupActor.tell(new RequestDeviceList(0L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList reply = deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(0L, reply.requestId);
|
||||
assertEquals(Stream.of("device1", "device2").collect(Collectors.toSet()), reply.ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListActiveDevicesAfterOneShutsDown() {
|
||||
TestProbe<DeviceRegistered> registeredProbe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device1", registeredProbe.getRef()));
|
||||
DeviceRegistered registered1 = registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device2", registeredProbe.getRef()));
|
||||
DeviceRegistered registered2 = registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
ActorRef<DeviceMessage> toShutDown = registered1.device;
|
||||
|
||||
TestProbe<ReplyDeviceList> deviceListProbe = testKit.createTestProbe(ReplyDeviceList.class);
|
||||
|
||||
groupActor.tell(new RequestDeviceList(0L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList reply = deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(0L, reply.requestId);
|
||||
assertEquals(Stream.of("device1", "device2").collect(Collectors.toSet()), reply.ids);
|
||||
|
||||
toShutDown.tell(Passivate.INSTANCE);
|
||||
registeredProbe.expectTerminated(toShutDown, registeredProbe.getRemainingOrDefault());
|
||||
|
||||
// using awaitAssert to retry because it might take longer for the groupActor
|
||||
// to see the Terminated, that order is undefined
|
||||
registeredProbe.awaitAssert(() -> {
|
||||
groupActor.tell(new RequestDeviceList(1L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList r =
|
||||
deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(1L, r.requestId);
|
||||
assertEquals(Stream.of("device2").collect(Collectors.toSet()), r.ids);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
//#device-group-list-terminate-test
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static jdocs.typed.tutorial_4.DeviceManagerProtocol.*;
|
||||
|
||||
//#device-manager-full
|
||||
public class DeviceManager extends AbstractBehavior<DeviceManagerMessage> {
|
||||
|
||||
public static Behavior<DeviceManagerMessage> createBehavior() {
|
||||
return Behaviors.setup(DeviceManager::new);
|
||||
}
|
||||
|
||||
private static class DeviceGroupTerminated implements DeviceManagerMessage{
|
||||
public final String groupId;
|
||||
|
||||
DeviceGroupTerminated(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceManagerMessage> context;
|
||||
private final Map<String, ActorRef<DeviceGroupMessage>> groupIdToActor = new HashMap<>();
|
||||
|
||||
public DeviceManager(ActorContext<DeviceManagerMessage> context) {
|
||||
this.context = context;
|
||||
context.getLog().info("DeviceManager started");
|
||||
}
|
||||
|
||||
private DeviceManager onTrackDevice(RequestTrackDevice trackMsg) {
|
||||
String groupId = trackMsg.groupId;
|
||||
ActorRef<DeviceGroupMessage> ref = groupIdToActor.get(groupId);
|
||||
if (ref != null) {
|
||||
ref.tell(trackMsg);
|
||||
} else {
|
||||
context.getLog().info("Creating device group actor for {}", groupId);
|
||||
ActorRef<DeviceGroupMessage> groupActor =
|
||||
context.spawn(DeviceGroup.createBehavior(groupId), "group-" + groupId);
|
||||
context.watchWith(groupActor, new DeviceGroupTerminated(groupId));
|
||||
groupActor.tell(trackMsg);
|
||||
groupIdToActor.put(groupId, groupActor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceManager onRequestDeviceList(RequestDeviceList request) {
|
||||
ActorRef<DeviceGroupMessage> ref = groupIdToActor.get(request.groupId);
|
||||
if (ref != null) {
|
||||
ref.tell(request);
|
||||
} else {
|
||||
request.replyTo.tell(new ReplyDeviceList(request.requestId, Collections.emptySet()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceManager onTerminated(DeviceGroupTerminated t) {
|
||||
context.getLog().info("Device group actor for {} has been terminated", t.groupId);
|
||||
groupIdToActor.remove(t.groupId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Receive<DeviceManagerMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RequestTrackDevice.class, this::onTrackDevice)
|
||||
.onMessage(RequestDeviceList.class, this::onRequestDeviceList)
|
||||
.onMessage(DeviceGroupTerminated.class, this::onTerminated)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DeviceManager postStop() {
|
||||
context.getLog().info("DeviceManager stopped");
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
//#device-manager-full
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
//#device-registration-msgs
|
||||
abstract class DeviceManagerProtocol {
|
||||
// no instances of DeviceManagerProtocol class
|
||||
private DeviceManagerProtocol() {}
|
||||
|
||||
interface DeviceManagerMessage {}
|
||||
|
||||
interface DeviceGroupMessage {}
|
||||
|
||||
public static final class RequestTrackDevice implements DeviceManagerMessage, DeviceGroupMessage {
|
||||
public final String groupId;
|
||||
public final String deviceId;
|
||||
public final ActorRef<DeviceRegistered> replyTo;
|
||||
|
||||
public RequestTrackDevice(String groupId, String deviceId, ActorRef<DeviceRegistered> replyTo) {
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DeviceRegistered {
|
||||
public final ActorRef<DeviceProtocol.DeviceMessage> device;
|
||||
|
||||
public DeviceRegistered(ActorRef<DeviceProtocol.DeviceMessage> device) {
|
||||
this.device = device;
|
||||
}
|
||||
}
|
||||
//#device-registration-msgs
|
||||
|
||||
//#device-list-msgs
|
||||
public static final class RequestDeviceList implements DeviceManagerMessage, DeviceGroupMessage {
|
||||
final long requestId;
|
||||
final String groupId;
|
||||
final ActorRef<ReplyDeviceList> replyTo;
|
||||
|
||||
public RequestDeviceList(long requestId, String groupId, ActorRef<ReplyDeviceList> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.groupId = groupId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ReplyDeviceList {
|
||||
final long requestId;
|
||||
final Set<String> ids;
|
||||
|
||||
public ReplyDeviceList(long requestId, Set<String> ids) {
|
||||
this.requestId = requestId;
|
||||
this.ids = ids;
|
||||
}
|
||||
}
|
||||
//#device-list-msgs
|
||||
|
||||
//#device-registration-msgs
|
||||
}
|
||||
//#device-registration-msgs
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import static jdocs.typed.tutorial_4.DeviceManagerProtocol.*;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class DeviceManagerTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testReplyToRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceManagerMessage> managerActor = testKit.spawn(DeviceManager.createBehavior());
|
||||
|
||||
managerActor.tell(new RequestTrackDevice("group1", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// another group
|
||||
managerActor.tell(new RequestTrackDevice("group2", "device", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertNotEquals(registered1.device, registered2.device);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
public static final class RecordTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final double value;
|
||||
final ActorRef<TemperatureRecorded> replyTo;
|
||||
|
||||
public RecordTemperature(long requestId, double value, ActorRef<TemperatureRecorded> replyTo){
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TemperatureRecorded {
|
||||
final long requestId;
|
||||
|
||||
public TemperatureRecorded(long requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ReadTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final ActorRef<RespondTemperature> replyTo;
|
||||
|
||||
public ReadTemperature(long requestId, ActorRef<RespondTemperature> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
final long requestId;
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(long requestId, Optional<Double> value) {
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
//#passivate-msg
|
||||
static enum Passivate implements DeviceMessage {
|
||||
INSTANCE
|
||||
}
|
||||
//#passivate-msg
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_4;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static jdocs.typed.tutorial_4.DeviceProtocol.*;
|
||||
|
||||
public class DeviceTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
//#device-read-test
|
||||
@Test
|
||||
public void testReplyWithEmptyReadingIfNoTemperatureIsKnown() {
|
||||
TestProbe<RespondTemperature> probe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
deviceActor.tell(new ReadTemperature(42L, probe.getRef()));
|
||||
RespondTemperature response = probe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(42L, response.requestId);
|
||||
assertEquals(Optional.empty(), response.value);
|
||||
}
|
||||
//#device-read-test
|
||||
|
||||
//#device-write-read-test
|
||||
@Test
|
||||
public void testReplyWithLatestTemperatureReading() {
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
TestProbe<RespondTemperature> readProbe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
|
||||
deviceActor.tell(new RecordTemperature(1L, 24.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(2L, readProbe.getRef()));
|
||||
RespondTemperature response1 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(2L, response1.requestId);
|
||||
assertEquals(Optional.of(24.0), response1.value);
|
||||
|
||||
deviceActor.tell(new RecordTemperature(3L, 55.0, recordProbe.getRef()));
|
||||
assertEquals(3L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(4L, readProbe.getRef()));
|
||||
RespondTemperature response2 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(4L, response2.requestId);
|
||||
assertEquals(Optional.of(55.0), response2.value);
|
||||
}
|
||||
//#device-write-read-test
|
||||
|
||||
}
|
||||
64
akka-docs/src/test/java/jdocs/typed/tutorial_5/Device.java
Normal file
64
akka-docs/src/test/java/jdocs/typed/tutorial_5/Device.java
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceProtocol.*;
|
||||
|
||||
public class Device extends AbstractBehavior<DeviceMessage> {
|
||||
|
||||
public static Behavior<DeviceMessage> createBehavior(String groupId, String deviceId) {
|
||||
return Behaviors.setup(context -> new Device(context, groupId, deviceId));
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceMessage> context;
|
||||
private final String groupId;
|
||||
private final String deviceId;
|
||||
|
||||
private Optional<Double> lastTemperatureReading = Optional.empty();
|
||||
|
||||
public Device(ActorContext<DeviceMessage> context, String groupId, String deviceId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
|
||||
context.getLog().info("Device actor {}-{} started", groupId, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<DeviceMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RecordTemperature.class, this::recordTemperature)
|
||||
.onMessage(ReadTemperature.class, this::readTemperature)
|
||||
.onMessage(Passivate.class, m -> Behaviors.stopped())
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> recordTemperature(RecordTemperature r) {
|
||||
context.getLog().info("Recorded temperature reading {} with {}", r.value, r.requestId);
|
||||
lastTemperatureReading = Optional.of(r.value);
|
||||
r.replyTo.tell(new TemperatureRecorded(r.requestId));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> readTemperature(ReadTemperature r) {
|
||||
r.replyTo.tell(new RespondTemperature(r.requestId, deviceId, lastTemperatureReading));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<DeviceMessage> postStop() {
|
||||
context.getLog().info("Device actor {}-{} stopped", groupId, deviceId);
|
||||
return Behaviors.stopped();
|
||||
}
|
||||
}
|
||||
118
akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java
Normal file
118
akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_5.DeviceProtocol.DeviceMessage;
|
||||
|
||||
//#query-added
|
||||
public class DeviceGroup extends AbstractBehavior<DeviceGroupMessage> {
|
||||
|
||||
public static Behavior<DeviceGroupMessage> createBehavior(String groupId) {
|
||||
return Behaviors.setup(context -> new DeviceGroup(context, groupId));
|
||||
}
|
||||
|
||||
private class DeviceTerminated implements DeviceGroupMessage{
|
||||
public final ActorRef<DeviceProtocol.DeviceMessage> device;
|
||||
public final String groupId;
|
||||
public final String deviceId;
|
||||
|
||||
DeviceTerminated(ActorRef<DeviceProtocol.DeviceMessage> device, String groupId, String deviceId) {
|
||||
this.device = device;
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceGroupMessage> context;
|
||||
private final String groupId;
|
||||
private final Map<String, ActorRef<DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
|
||||
public DeviceGroup(ActorContext<DeviceGroupMessage> context, String groupId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
context.getLog().info("DeviceGroup {} started", groupId);
|
||||
}
|
||||
|
||||
//#query-added
|
||||
private DeviceGroup onTrackDevice(RequestTrackDevice trackMsg) {
|
||||
if (this.groupId.equals(trackMsg.groupId)) {
|
||||
ActorRef<DeviceMessage> deviceActor = deviceIdToActor.get(trackMsg.deviceId);
|
||||
if (deviceActor != null) {
|
||||
trackMsg.replyTo.tell(new DeviceRegistered(deviceActor));
|
||||
} else {
|
||||
context.getLog().info("Creating device actor for {}", trackMsg.deviceId);
|
||||
deviceActor = context.spawn(Device.createBehavior(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId);
|
||||
context.watchWith(deviceActor, new DeviceTerminated(deviceActor, groupId, trackMsg.deviceId));
|
||||
deviceIdToActor.put(trackMsg.deviceId, deviceActor);
|
||||
trackMsg.replyTo.tell(new DeviceRegistered(deviceActor));
|
||||
}
|
||||
} else {
|
||||
context.getLog().warning(
|
||||
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
|
||||
groupId, this.groupId
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceGroup onDeviceList(RequestDeviceList r) {
|
||||
r.replyTo.tell(new ReplyDeviceList(r.requestId, deviceIdToActor.keySet()));
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceGroup onTerminated(DeviceTerminated t) {
|
||||
context.getLog().info("Device actor for {} has been terminated", t.deviceId);
|
||||
deviceIdToActor.remove(t.deviceId);
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceGroup postStop() {
|
||||
context.getLog().info("DeviceGroup {} stopped", groupId);
|
||||
return this;
|
||||
}
|
||||
|
||||
//#query-added
|
||||
|
||||
private DeviceGroup onAllTemperatures(RequestAllTemperatures r) {
|
||||
// since Java collections are mutable, we want to avoid sharing them between actors (since multiple Actors (threads)
|
||||
// modifying the same mutable data-structure is not safe), and perform a defensive copy of the mutable map:
|
||||
//
|
||||
// Feel free to use your favourite immutable data-structures library with Akka in Java applications!
|
||||
Map<String, ActorRef<DeviceMessage>> deviceIdToActorCopy = new HashMap<>(this.deviceIdToActor);
|
||||
|
||||
context.spawnAnonymous(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActorCopy, r.requestId, r.replyTo, Duration.ofSeconds(3)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<DeviceGroupMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
//#query-added
|
||||
.onMessage(RequestTrackDevice.class, this::onTrackDevice)
|
||||
.onMessage(RequestDeviceList.class, r -> r.groupId.equals(groupId), this::onDeviceList)
|
||||
.onMessage(DeviceTerminated.class, this::onTerminated)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
//#query-added
|
||||
// ... other cases omitted
|
||||
.onMessage(RequestAllTemperatures.class, r -> r.groupId.equals(groupId), this::onAllTemperatures)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#query-added
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
import akka.actor.typed.javadsl.TimerScheduler;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
|
||||
//#query-full
|
||||
//#query-outline
|
||||
public class DeviceGroupQuery extends AbstractBehavior<DeviceGroupQueryMessage> {
|
||||
|
||||
public static Behavior<DeviceGroupQueryMessage> createBehavior(
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor,
|
||||
long requestId,
|
||||
ActorRef<RespondAllTemperatures> requester,
|
||||
Duration timeout) {
|
||||
return
|
||||
Behaviors.setup(context ->
|
||||
Behaviors.withTimers(timers -> new DeviceGroupQuery(
|
||||
deviceIdToActor, requestId, requester, timeout, context, timers)));
|
||||
}
|
||||
|
||||
private static enum CollectionTimeout implements DeviceGroupQueryMessage {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
static class WrappedRespondTemperature implements DeviceGroupQueryMessage {
|
||||
final DeviceProtocol.RespondTemperature response;
|
||||
|
||||
WrappedRespondTemperature(DeviceProtocol.RespondTemperature response) {
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeviceTerminated implements DeviceGroupQueryMessage {
|
||||
final String deviceId;
|
||||
|
||||
private DeviceTerminated(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
private final long requestId;
|
||||
private final ActorRef<RespondAllTemperatures> requester;
|
||||
//#query-outline
|
||||
//#query-state
|
||||
private Map<String, TemperatureReading> repliesSoFar = new HashMap<>();
|
||||
private final Set<String> stillWaiting;
|
||||
|
||||
//#query-state
|
||||
//#query-outline
|
||||
|
||||
public DeviceGroupQuery(
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor,
|
||||
long requestId,
|
||||
ActorRef<RespondAllTemperatures> requester,
|
||||
Duration timeout,
|
||||
ActorContext<DeviceGroupQueryMessage> context, TimerScheduler<DeviceGroupQueryMessage> timers) {
|
||||
this.requestId = requestId;
|
||||
this.requester = requester;
|
||||
|
||||
timers.startSingleTimer(CollectionTimeout.class, CollectionTimeout.INSTANCE, timeout);
|
||||
|
||||
ActorRef<DeviceProtocol.RespondTemperature> respondTemperatureAdapter =
|
||||
context.messageAdapter(DeviceProtocol.RespondTemperature.class, WrappedRespondTemperature::new);
|
||||
|
||||
for (Map.Entry<String, ActorRef<DeviceProtocol.DeviceMessage>> entry : deviceIdToActor.entrySet()) {
|
||||
context.watchWith(entry.getValue(), new DeviceTerminated(entry.getKey()));
|
||||
entry.getValue().tell(new DeviceProtocol.ReadTemperature(0L, respondTemperatureAdapter));
|
||||
}
|
||||
stillWaiting = new HashSet<>(deviceIdToActor.keySet());
|
||||
}
|
||||
|
||||
//#query-outline
|
||||
//#query-state
|
||||
@Override
|
||||
public Receive<DeviceGroupQueryMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(WrappedRespondTemperature.class, this::onRespondTemperature)
|
||||
.onMessage(DeviceTerminated.class, this::onDeviceTerminated)
|
||||
.onMessage(CollectionTimeout.class, this::onCollectionTimeout)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Behavior<DeviceGroupQueryMessage> onRespondTemperature(WrappedRespondTemperature r) {
|
||||
TemperatureReading reading = r.response.value
|
||||
.map(v -> (TemperatureReading) new Temperature(v))
|
||||
.orElse(TemperatureNotAvailable.INSTANCE);
|
||||
|
||||
String deviceId = r.response.deviceId;
|
||||
repliesSoFar.put(deviceId, reading);
|
||||
stillWaiting.remove(deviceId);
|
||||
|
||||
return respondWhenAllCollected();
|
||||
}
|
||||
|
||||
private Behavior<DeviceGroupQueryMessage> onDeviceTerminated(DeviceTerminated terminated) {
|
||||
if (stillWaiting.contains(terminated.deviceId)) {
|
||||
repliesSoFar.put(terminated.deviceId, DeviceNotAvailable.INSTANCE);
|
||||
stillWaiting.remove(terminated.deviceId);
|
||||
}
|
||||
return respondWhenAllCollected();
|
||||
}
|
||||
|
||||
private Behavior<DeviceGroupQueryMessage> onCollectionTimeout(CollectionTimeout timeout) {
|
||||
for (String deviceId : stillWaiting) {
|
||||
repliesSoFar.put(deviceId, DeviceTimedOut.INSTANCE);
|
||||
}
|
||||
stillWaiting.clear();
|
||||
return respondWhenAllCollected();
|
||||
}
|
||||
//#query-state
|
||||
|
||||
//#query-collect-reply
|
||||
private Behavior<DeviceGroupQueryMessage> respondWhenAllCollected() {
|
||||
if (stillWaiting.isEmpty()) {
|
||||
requester.tell(new RespondAllTemperatures(requestId, repliesSoFar));
|
||||
return Behaviors.stopped();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
//#query-collect-reply
|
||||
//#query-outline
|
||||
|
||||
}
|
||||
//#query-outline
|
||||
//#query-full
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_5.DeviceProtocol.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DeviceGroupQueryTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
//#query-test-normal
|
||||
@Test
|
||||
public void testReturnTemperatureValueForWorkingDevices() {
|
||||
TestProbe<RespondAllTemperatures> requester = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
TestProbe<DeviceMessage> device1 = testKit.createTestProbe(DeviceMessage.class);
|
||||
TestProbe<DeviceMessage> device2 = testKit.createTestProbe(DeviceMessage.class);
|
||||
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
deviceIdToActor.put("device1", device1.getRef());
|
||||
deviceIdToActor.put("device2", device2.getRef());
|
||||
|
||||
ActorRef<DeviceGroupQueryMessage> queryActor = testKit.spawn(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActor,
|
||||
1L,
|
||||
requester.getRef(),
|
||||
Duration.ofSeconds(3)));
|
||||
|
||||
device1.expectMessageClass(ReadTemperature.class);
|
||||
device2.expectMessageClass(ReadTemperature.class);
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device1", Optional.of(1.0))));
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device2", Optional.of(2.0))));
|
||||
|
||||
RespondAllTemperatures response = requester.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new Temperature(2.0));
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-normal
|
||||
|
||||
//#query-test-no-reading
|
||||
@Test
|
||||
public void testReturnTemperatureNotAvailableForDevicesWithNoReadings() {
|
||||
TestProbe<RespondAllTemperatures> requester = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
TestProbe<DeviceMessage> device1 = testKit.createTestProbe(DeviceMessage.class);
|
||||
TestProbe<DeviceMessage> device2 = testKit.createTestProbe(DeviceMessage.class);
|
||||
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
deviceIdToActor.put("device1", device1.getRef());
|
||||
deviceIdToActor.put("device2", device2.getRef());
|
||||
|
||||
ActorRef<DeviceGroupQueryMessage> queryActor = testKit.spawn(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActor,
|
||||
1L,
|
||||
requester.getRef(),
|
||||
Duration.ofSeconds(3)));
|
||||
|
||||
assertEquals(0L, device1.expectMessageClass(ReadTemperature.class).requestId);
|
||||
assertEquals(0L, device2.expectMessageClass(ReadTemperature.class).requestId);
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device1", Optional.empty())));
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device2", Optional.of(2.0))));
|
||||
|
||||
RespondAllTemperatures response = requester.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", TemperatureNotAvailable.INSTANCE);
|
||||
expectedTemperatures.put("device2", new Temperature(2.0));
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-no-reading
|
||||
|
||||
//#query-test-stopped
|
||||
@Test
|
||||
public void testReturnDeviceNotAvailableIfDeviceStopsBeforeAnswering() {
|
||||
TestProbe<RespondAllTemperatures> requester = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
TestProbe<DeviceMessage> device1 = testKit.createTestProbe(DeviceMessage.class);
|
||||
TestProbe<DeviceMessage> device2 = testKit.createTestProbe(DeviceMessage.class);
|
||||
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
deviceIdToActor.put("device1", device1.getRef());
|
||||
deviceIdToActor.put("device2", device2.getRef());
|
||||
|
||||
ActorRef<DeviceGroupQueryMessage> queryActor = testKit.spawn(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActor,
|
||||
1L,
|
||||
requester.getRef(),
|
||||
Duration.ofSeconds(3)));
|
||||
|
||||
assertEquals(0L, device1.expectMessageClass(ReadTemperature.class).requestId);
|
||||
assertEquals(0L, device2.expectMessageClass(ReadTemperature.class).requestId);
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device1", Optional.of(1.0))));
|
||||
|
||||
device2.stop();
|
||||
|
||||
RespondAllTemperatures response = requester.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new Temperature(1.0));
|
||||
expectedTemperatures.put("device2", DeviceNotAvailable.INSTANCE);
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-stopped
|
||||
|
||||
//#query-test-stopped-later
|
||||
@Test
|
||||
public void testReturnTemperatureReadingEvenIfDeviceStopsAfterAnswering() {
|
||||
TestProbe<RespondAllTemperatures> requester = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
TestProbe<DeviceMessage> device1 = testKit.createTestProbe(DeviceMessage.class);
|
||||
TestProbe<DeviceMessage> device2 = testKit.createTestProbe(DeviceMessage.class);
|
||||
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
deviceIdToActor.put("device1", device1.getRef());
|
||||
deviceIdToActor.put("device2", device2.getRef());
|
||||
|
||||
ActorRef<DeviceGroupQueryMessage> queryActor = testKit.spawn(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActor,
|
||||
1L,
|
||||
requester.getRef(),
|
||||
Duration.ofSeconds(3)));
|
||||
|
||||
assertEquals(0L, device1.expectMessageClass(ReadTemperature.class).requestId);
|
||||
assertEquals(0L, device2.expectMessageClass(ReadTemperature.class).requestId);
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device1", Optional.of(1.0))));
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device2", Optional.of(2.0))));
|
||||
|
||||
device2.stop();
|
||||
|
||||
RespondAllTemperatures response = requester.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new Temperature(2.0));
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-stopped-later
|
||||
|
||||
//#query-test-timeout
|
||||
@Test
|
||||
public void testReturnDeviceTimedOutIfDeviceDoesNotAnswerInTime() {
|
||||
TestProbe<RespondAllTemperatures> requester = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
TestProbe<DeviceMessage> device1 = testKit.createTestProbe(DeviceMessage.class);
|
||||
TestProbe<DeviceMessage> device2 = testKit.createTestProbe(DeviceMessage.class);
|
||||
|
||||
Map<String, ActorRef<DeviceProtocol.DeviceMessage>> deviceIdToActor = new HashMap<>();
|
||||
deviceIdToActor.put("device1", device1.getRef());
|
||||
deviceIdToActor.put("device2", device2.getRef());
|
||||
|
||||
ActorRef<DeviceGroupQueryMessage> queryActor = testKit.spawn(DeviceGroupQuery.createBehavior(
|
||||
deviceIdToActor,
|
||||
1L,
|
||||
requester.getRef(),
|
||||
Duration.ofMillis(200)));
|
||||
|
||||
assertEquals(0L, device1.expectMessageClass(ReadTemperature.class).requestId);
|
||||
assertEquals(0L, device2.expectMessageClass(ReadTemperature.class).requestId);
|
||||
|
||||
queryActor.tell(new DeviceGroupQuery.WrappedRespondTemperature(
|
||||
new RespondTemperature(0L, "device1", Optional.of(1.0))));
|
||||
|
||||
// no reply from device2
|
||||
|
||||
RespondAllTemperatures response = requester.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(1L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new Temperature(1.0));
|
||||
expectedTemperatures.put("device2", DeviceTimedOut.INSTANCE);
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#query-test-timeout
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_5.DeviceProtocol.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class DeviceGroupTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testReplyToRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// another deviceId
|
||||
groupActor.tell(new RequestTrackDevice("group", "device3", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertNotEquals(registered1.device, registered2.device);
|
||||
|
||||
// Check that the device actors are working
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
registered1.device.tell(new RecordTemperature(0L, 1.0, recordProbe.getRef()));
|
||||
assertEquals(0L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
registered2.device.tell(new RecordTemperature(1L, 2.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreWrongRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
groupActor.tell(new RequestTrackDevice("wrongGroup", "device1", probe.getRef()));
|
||||
probe.expectNoMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnSameActorForSameDeviceId() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// registering same again should be idempotent
|
||||
groupActor.tell(new RequestTrackDevice("group", "device", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertEquals(registered1.device, registered2.device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListActiveDevices() {
|
||||
TestProbe<DeviceRegistered> registeredProbe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device1", registeredProbe.getRef()));
|
||||
registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device2", registeredProbe.getRef()));
|
||||
registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
TestProbe<ReplyDeviceList> deviceListProbe = testKit.createTestProbe(ReplyDeviceList.class);
|
||||
|
||||
groupActor.tell(new RequestDeviceList(0L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList reply = deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(0L, reply.requestId);
|
||||
assertEquals(Stream.of("device1", "device2").collect(Collectors.toSet()), reply.ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListActiveDevicesAfterOneShutsDown() {
|
||||
TestProbe<DeviceRegistered> registeredProbe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device1", registeredProbe.getRef()));
|
||||
DeviceRegistered registered1 = registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device2", registeredProbe.getRef()));
|
||||
registeredProbe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
ActorRef<DeviceMessage> toShutDown = registered1.device;
|
||||
|
||||
TestProbe<ReplyDeviceList> deviceListProbe = testKit.createTestProbe(ReplyDeviceList.class);
|
||||
|
||||
groupActor.tell(new RequestDeviceList(0L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList reply = deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(0L, reply.requestId);
|
||||
assertEquals(Stream.of("device1", "device2").collect(Collectors.toSet()), reply.ids);
|
||||
|
||||
toShutDown.tell(Passivate.INSTANCE);
|
||||
registeredProbe.expectTerminated(toShutDown, registeredProbe.getRemainingOrDefault());
|
||||
|
||||
// using awaitAssert to retry because it might take longer for the groupActor
|
||||
// to see the Terminated, that order is undefined
|
||||
registeredProbe.awaitAssert(() -> {
|
||||
groupActor.tell(new RequestDeviceList(1L, "group", deviceListProbe.getRef()));
|
||||
ReplyDeviceList r =
|
||||
deviceListProbe.expectMessageClass(ReplyDeviceList.class);
|
||||
assertEquals(1L, r.requestId);
|
||||
assertEquals(Stream.of("device2").collect(Collectors.toSet()), r.ids);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
//#group-query-integration-test
|
||||
@Test
|
||||
public void testCollectTemperaturesFromAllActiveDevices() {
|
||||
TestProbe<DeviceRegistered> registeredProbe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceGroupMessage> groupActor = testKit.spawn(DeviceGroup.createBehavior("group"));
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device1", registeredProbe.getRef()));
|
||||
ActorRef<DeviceMessage> deviceActor1 = registeredProbe.expectMessageClass(DeviceRegistered.class).device;
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device2", registeredProbe.getRef()));
|
||||
ActorRef<DeviceMessage> deviceActor2 = registeredProbe.expectMessageClass(DeviceRegistered.class).device;
|
||||
|
||||
groupActor.tell(new RequestTrackDevice("group", "device3", registeredProbe.getRef()));
|
||||
ActorRef<DeviceMessage> deviceActor3 = registeredProbe.expectMessageClass(DeviceRegistered.class).device;
|
||||
|
||||
// Check that the device actors are working
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
deviceActor1.tell(new RecordTemperature(0L, 1.0, recordProbe.getRef()));
|
||||
assertEquals(0L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
deviceActor2.tell(new RecordTemperature(1L, 2.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
// No temperature for device 3
|
||||
|
||||
TestProbe<RespondAllTemperatures> allTempProbe = testKit.createTestProbe(RespondAllTemperatures.class);
|
||||
groupActor.tell(new RequestAllTemperatures(0L, "group", allTempProbe.getRef()));
|
||||
RespondAllTemperatures response = allTempProbe.expectMessageClass(RespondAllTemperatures.class);
|
||||
assertEquals(0L, response.requestId);
|
||||
|
||||
Map<String, TemperatureReading> expectedTemperatures = new HashMap<>();
|
||||
expectedTemperatures.put("device1", new Temperature(1.0));
|
||||
expectedTemperatures.put("device2", new Temperature(2.0));
|
||||
expectedTemperatures.put("device3", TemperatureNotAvailable.INSTANCE);
|
||||
|
||||
assertEquals(expectedTemperatures, response.temperatures);
|
||||
}
|
||||
//#group-query-integration-test
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.PostStop;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
|
||||
public class DeviceManager extends AbstractBehavior<DeviceManagerMessage> {
|
||||
|
||||
public static Behavior<DeviceManagerMessage> createBehavior() {
|
||||
return Behaviors.setup(DeviceManager::new);
|
||||
}
|
||||
|
||||
private static class DeviceGroupTerminated implements DeviceManagerMessage{
|
||||
public final String groupId;
|
||||
|
||||
DeviceGroupTerminated(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
private final ActorContext<DeviceManagerMessage> context;
|
||||
private final Map<String, ActorRef<DeviceGroupMessage>> groupIdToActor = new HashMap<>();
|
||||
|
||||
public DeviceManager(ActorContext<DeviceManagerMessage> context) {
|
||||
this.context = context;
|
||||
context.getLog().info("DeviceManager started");
|
||||
}
|
||||
|
||||
private DeviceManager onTrackDevice(RequestTrackDevice trackMsg) {
|
||||
String groupId = trackMsg.groupId;
|
||||
ActorRef<DeviceGroupMessage> ref = groupIdToActor.get(groupId);
|
||||
if (ref != null) {
|
||||
ref.tell(trackMsg);
|
||||
} else {
|
||||
context.getLog().info("Creating device group actor for {}", groupId);
|
||||
ActorRef<DeviceGroupMessage> groupActor =
|
||||
context.spawn(DeviceGroup.createBehavior(groupId), "group-" + groupId);
|
||||
context.watchWith(groupActor, new DeviceGroupTerminated(groupId));
|
||||
groupActor.tell(trackMsg);
|
||||
groupIdToActor.put(groupId, groupActor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceManager onRequestDeviceList(RequestDeviceList request) {
|
||||
ActorRef<DeviceGroupMessage> ref = groupIdToActor.get(request.groupId);
|
||||
if (ref != null) {
|
||||
ref.tell(request);
|
||||
} else {
|
||||
request.replyTo.tell(new ReplyDeviceList(request.requestId, Collections.emptySet()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceManager onRequestAllTemperatures(RequestAllTemperatures request) {
|
||||
ActorRef<DeviceGroupMessage> ref = groupIdToActor.get(request.groupId);
|
||||
if (ref != null) {
|
||||
ref.tell(request);
|
||||
} else {
|
||||
request.replyTo.tell(new RespondAllTemperatures(request.requestId, Collections.emptyMap()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private DeviceManager onTerminated(DeviceGroupTerminated t) {
|
||||
context.getLog().info("Device group actor for {} has been terminated", t.groupId);
|
||||
groupIdToActor.remove(t.groupId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Receive<DeviceManagerMessage> createReceive() {
|
||||
return receiveBuilder()
|
||||
.onMessage(RequestTrackDevice.class, this::onTrackDevice)
|
||||
.onMessage(RequestDeviceList.class, this::onRequestDeviceList)
|
||||
.onMessage(RequestAllTemperatures.class, this::onRequestAllTemperatures)
|
||||
.onMessage(DeviceGroupTerminated.class, this::onTerminated)
|
||||
.onSignal(PostStop.class, signal -> postStop())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DeviceManager postStop() {
|
||||
context.getLog().info("DeviceManager stopped");
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class DeviceManagerProtocol {
|
||||
// no instances of DeviceManagerProtocol class
|
||||
private DeviceManagerProtocol() {}
|
||||
|
||||
interface DeviceManagerMessage {}
|
||||
|
||||
interface DeviceGroupMessage {}
|
||||
|
||||
public static final class RequestTrackDevice implements DeviceManagerMessage, DeviceGroupMessage {
|
||||
public final String groupId;
|
||||
public final String deviceId;
|
||||
public final ActorRef<DeviceRegistered> replyTo;
|
||||
|
||||
public RequestTrackDevice(String groupId, String deviceId, ActorRef<DeviceRegistered> replyTo) {
|
||||
this.groupId = groupId;
|
||||
this.deviceId = deviceId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DeviceRegistered {
|
||||
public final ActorRef<DeviceProtocol.DeviceMessage> device;
|
||||
|
||||
public DeviceRegistered(ActorRef<DeviceProtocol.DeviceMessage> device) {
|
||||
this.device = device;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RequestDeviceList implements DeviceManagerMessage, DeviceGroupMessage {
|
||||
final long requestId;
|
||||
final String groupId;
|
||||
final ActorRef<ReplyDeviceList> replyTo;
|
||||
|
||||
public RequestDeviceList(long requestId, String groupId, ActorRef<ReplyDeviceList> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.groupId = groupId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ReplyDeviceList {
|
||||
final long requestId;
|
||||
final Set<String> ids;
|
||||
|
||||
public ReplyDeviceList(long requestId, Set<String> ids) {
|
||||
this.requestId = requestId;
|
||||
this.ids = ids;
|
||||
}
|
||||
}
|
||||
|
||||
//#query-protocol
|
||||
interface DeviceGroupQueryMessage {}
|
||||
|
||||
public static final class RequestAllTemperatures
|
||||
implements DeviceGroupQueryMessage, DeviceGroupMessage, DeviceManagerMessage {
|
||||
|
||||
final long requestId;
|
||||
final String groupId;
|
||||
final ActorRef<RespondAllTemperatures> replyTo;
|
||||
|
||||
public RequestAllTemperatures(long requestId, String groupId, ActorRef<RespondAllTemperatures> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.groupId = groupId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondAllTemperatures {
|
||||
final long requestId;
|
||||
final Map<String, TemperatureReading> temperatures;
|
||||
|
||||
public RespondAllTemperatures(long requestId, Map<String, TemperatureReading> temperatures) {
|
||||
this.requestId = requestId;
|
||||
this.temperatures = temperatures;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface TemperatureReading {
|
||||
}
|
||||
|
||||
public static final class Temperature implements TemperatureReading {
|
||||
public final double value;
|
||||
|
||||
public Temperature(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Temperature that = (Temperature) o;
|
||||
|
||||
return Double.compare(that.value, value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long temp = Double.doubleToLongBits(value);
|
||||
return (int) (temp ^ (temp >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Temperature{" +
|
||||
"value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public enum TemperatureNotAvailable implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public enum DeviceNotAvailable implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public enum DeviceTimedOut implements TemperatureReading {
|
||||
INSTANCE
|
||||
}
|
||||
//#query-protocol
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class DeviceManagerTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testReplyToRegistrationRequests() {
|
||||
TestProbe<DeviceRegistered> probe = testKit.createTestProbe(DeviceRegistered.class);
|
||||
ActorRef<DeviceManagerMessage> managerActor = testKit.spawn(DeviceManager.createBehavior());
|
||||
|
||||
managerActor.tell(new RequestTrackDevice("group1", "device", probe.getRef()));
|
||||
DeviceRegistered registered1 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
|
||||
// another group
|
||||
managerActor.tell(new RequestTrackDevice("group2", "device", probe.getRef()));
|
||||
DeviceRegistered registered2 = probe.expectMessageClass(DeviceRegistered.class);
|
||||
assertNotEquals(registered1.device, registered2.device);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class DeviceProtocol {
|
||||
// no instances of DeviceProtocol class
|
||||
private DeviceProtocol() {}
|
||||
|
||||
interface DeviceMessage {}
|
||||
|
||||
public static final class RecordTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final double value;
|
||||
final ActorRef<TemperatureRecorded> replyTo;
|
||||
|
||||
public RecordTemperature(long requestId, double value, ActorRef<TemperatureRecorded> replyTo){
|
||||
this.requestId = requestId;
|
||||
this.value = value;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TemperatureRecorded {
|
||||
final long requestId;
|
||||
|
||||
public TemperatureRecorded(long requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ReadTemperature implements DeviceMessage {
|
||||
final long requestId;
|
||||
final ActorRef<RespondTemperature> replyTo;
|
||||
|
||||
public ReadTemperature(long requestId, ActorRef<RespondTemperature> replyTo) {
|
||||
this.requestId = requestId;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RespondTemperature {
|
||||
final long requestId;
|
||||
final String deviceId;
|
||||
final Optional<Double> value;
|
||||
|
||||
public RespondTemperature(long requestId, String deviceId, Optional<Double> value) {
|
||||
this.requestId = requestId;
|
||||
this.deviceId = deviceId;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static enum Passivate implements DeviceMessage {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.typed.tutorial_5;
|
||||
|
||||
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static jdocs.typed.tutorial_5.DeviceManagerProtocol.*;
|
||||
import static jdocs.typed.tutorial_5.DeviceProtocol.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class DeviceTest extends JUnitSuite {
|
||||
|
||||
@ClassRule
|
||||
public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Test
|
||||
public void testReplyWithEmptyReadingIfNoTemperatureIsKnown() {
|
||||
TestProbe<RespondTemperature> probe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
deviceActor.tell(new ReadTemperature(42L, probe.getRef()));
|
||||
RespondTemperature response = probe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(42L, response.requestId);
|
||||
assertEquals(Optional.empty(), response.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplyWithLatestTemperatureReading() {
|
||||
TestProbe<TemperatureRecorded> recordProbe = testKit.createTestProbe(TemperatureRecorded.class);
|
||||
TestProbe<RespondTemperature> readProbe = testKit.createTestProbe(RespondTemperature.class);
|
||||
ActorRef<DeviceMessage> deviceActor = testKit.spawn(Device.createBehavior("group", "device"));
|
||||
|
||||
deviceActor.tell(new RecordTemperature(1L, 24.0, recordProbe.getRef()));
|
||||
assertEquals(1L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(2L, readProbe.getRef()));
|
||||
RespondTemperature response1 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(2L, response1.requestId);
|
||||
assertEquals(Optional.of(24.0), response1.value);
|
||||
|
||||
deviceActor.tell(new RecordTemperature(3L, 55.0, recordProbe.getRef()));
|
||||
assertEquals(3L, recordProbe.expectMessageClass(TemperatureRecorded.class).requestId);
|
||||
|
||||
deviceActor.tell(new ReadTemperature(4L, readProbe.getRef()));
|
||||
RespondTemperature response2 = readProbe.expectMessageClass(RespondTemperature.class);
|
||||
assertEquals(4L, response2.requestId);
|
||||
assertEquals(Optional.of(55.0), response2.value);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue