Translate Getting Started Guide to Typed, #25998

* convert example code
* update doc content
* update diagrams
* rewrite parental supervison part
This commit is contained in:
Patrik Nordwall 2018-10-28 08:49:58 -04:00
parent 4966d5262e
commit 76276e6504
77 changed files with 6235 additions and 43 deletions

View file

@ -102,7 +102,7 @@ public class Messages {
}
}
public static enum Swap {
public enum Swap {
Swap
}

View file

@ -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
};

View file

@ -156,7 +156,7 @@ public class ClusterShardingTest {
static//#counter-actor
public class Counter extends AbstractPersistentActor {
public static enum CounterOp {
public enum CounterOp {
INCREMENT, DECREMENT
}

View file

@ -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;

View file

@ -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() {

View file

@ -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());

View file

@ -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());
}
}

View file

@ -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
}

View file

@ -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
}
}

View 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

View file

@ -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

View 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

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View 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

View 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

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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
}

View file

@ -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
}

View 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();
}
}

View 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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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);
}
}

View file

@ -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
}
}

View file

@ -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);
}
}