231 lines
6 KiB
Java
231 lines
6 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* license agreements; and to You under the Apache License, version 2.0:
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* This file is part of the Apache Pekko project, derived from Akka.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
|
|
package jdocs.actor.fsm;
|
|
|
|
import org.apache.pekko.actor.*;
|
|
import jdocs.AbstractJavaTest;
|
|
import org.apache.pekko.testkit.javadsl.TestKit;
|
|
import org.junit.AfterClass;
|
|
import org.junit.BeforeClass;
|
|
import org.junit.Test;
|
|
|
|
import static org.junit.Assert.*;
|
|
|
|
import static jdocs.actor.fsm.FSMDocTest.StateType.*;
|
|
import static jdocs.actor.fsm.FSMDocTest.Messages.*;
|
|
|
|
import java.time.Duration;
|
|
|
|
public class FSMDocTest extends AbstractJavaTest {
|
|
static ActorSystem system;
|
|
|
|
@BeforeClass
|
|
public static void setup() {
|
|
system = ActorSystem.create("FSMDocTest");
|
|
}
|
|
|
|
@AfterClass
|
|
public static void tearDown() {
|
|
TestKit.shutdownActorSystem(system);
|
|
system = null;
|
|
}
|
|
|
|
public enum StateType {
|
|
SomeState,
|
|
Processing,
|
|
Idle,
|
|
Active,
|
|
Error
|
|
}
|
|
|
|
public enum Messages {
|
|
WillDo,
|
|
Tick
|
|
}
|
|
|
|
public enum Data {
|
|
Foo,
|
|
Bar
|
|
};
|
|
|
|
public static interface X {};
|
|
|
|
public static class DummyFSM extends AbstractFSM<StateType, Integer> {
|
|
Integer newData = 42;
|
|
// #alt-transition-syntax
|
|
public void handler(StateType from, StateType to) {
|
|
// handle transition here
|
|
}
|
|
|
|
// #alt-transition-syntax
|
|
{
|
|
// #modifier-syntax
|
|
when(
|
|
SomeState,
|
|
matchAnyEvent(
|
|
(msg, data) -> {
|
|
return goTo(Processing)
|
|
.using(newData)
|
|
.forMax(Duration.ofSeconds(5))
|
|
.replying(WillDo);
|
|
}));
|
|
// #modifier-syntax
|
|
|
|
// #NullFunction
|
|
when(SomeState, AbstractFSM.NullFunction());
|
|
// #NullFunction
|
|
|
|
// #transition-syntax
|
|
onTransition(
|
|
matchState(
|
|
Idle,
|
|
Active,
|
|
() -> startTimerWithFixedDelay("timeout", Tick, Duration.ofSeconds(1L)))
|
|
.state(Active, null, () -> cancelTimer("timeout"))
|
|
.state(null, Idle, (f, t) -> log().info("entering Idle from " + f)));
|
|
// #transition-syntax
|
|
|
|
// #alt-transition-syntax
|
|
onTransition(this::handler);
|
|
// #alt-transition-syntax
|
|
|
|
// #stop-syntax
|
|
when(
|
|
Error,
|
|
matchEventEquals(
|
|
"stop",
|
|
(event, data) -> {
|
|
// do cleanup ...
|
|
return stop();
|
|
}));
|
|
// #stop-syntax
|
|
|
|
// #termination-syntax
|
|
onTermination(
|
|
matchStop(
|
|
Normal(),
|
|
(state, data) -> {
|
|
/* Do something here */
|
|
})
|
|
.stop(
|
|
Shutdown(),
|
|
(state, data) -> {
|
|
/* Do something here */
|
|
})
|
|
.stop(
|
|
Failure.class,
|
|
(reason, state, data) -> {
|
|
/* Do something here */
|
|
}));
|
|
// #termination-syntax
|
|
|
|
// #unhandled-syntax
|
|
whenUnhandled(
|
|
matchEvent(
|
|
X.class,
|
|
(x, data) -> {
|
|
log().info("Received unhandled event: " + x);
|
|
return stay();
|
|
})
|
|
.anyEvent(
|
|
(event, data) -> {
|
|
log().warning("Received unknown event: " + event);
|
|
return goTo(Error);
|
|
}));
|
|
}
|
|
// #unhandled-syntax
|
|
}
|
|
|
|
public
|
|
// #logging-fsm
|
|
static class MyFSM extends AbstractLoggingFSM<StateType, Data> {
|
|
// #body-elided
|
|
// #logging-fsm
|
|
ActorRef target = null;
|
|
// #logging-fsm
|
|
@Override
|
|
public int logDepth() {
|
|
return 12;
|
|
}
|
|
|
|
{
|
|
onTermination(
|
|
matchStop(
|
|
Failure.class,
|
|
(reason, state, data) -> {
|
|
String lastEvents = getLog().mkString("\n\t");
|
|
log()
|
|
.warning(
|
|
"Failure in state "
|
|
+ state
|
|
+ " with data "
|
|
+ data
|
|
+ "\n"
|
|
+ "Events leading up to this point:\n\t"
|
|
+ lastEvents);
|
|
// #logging-fsm
|
|
target.tell(reason.cause(), getSelf());
|
|
target.tell(state, getSelf());
|
|
target.tell(data, getSelf());
|
|
target.tell(lastEvents, getSelf());
|
|
// #logging-fsm
|
|
}));
|
|
// ...
|
|
// #logging-fsm
|
|
startWith(SomeState, Data.Foo);
|
|
when(
|
|
SomeState,
|
|
matchEvent(
|
|
ActorRef.class,
|
|
Data.class,
|
|
(ref, data) -> {
|
|
target = ref;
|
|
target.tell("going active", getSelf());
|
|
return goTo(Active);
|
|
}));
|
|
when(
|
|
Active,
|
|
matchEventEquals(
|
|
"stop",
|
|
(event, data) -> {
|
|
target.tell("stopping", getSelf());
|
|
return stop(new Failure("This is not the error you're looking for"));
|
|
}));
|
|
initialize();
|
|
// #logging-fsm
|
|
}
|
|
// #body-elided
|
|
}
|
|
// #logging-fsm
|
|
|
|
@Test
|
|
public void testLoggingFSM() {
|
|
new TestKit(system) {
|
|
{
|
|
final ActorRef logger = system.actorOf(Props.create(MyFSM.class));
|
|
final ActorRef probe = getRef();
|
|
|
|
logger.tell(probe, probe);
|
|
expectMsgEquals("going active");
|
|
logger.tell("stop", probe);
|
|
expectMsgEquals("stopping");
|
|
expectMsgEquals("This is not the error you're looking for");
|
|
expectMsgEquals(Active);
|
|
expectMsgEquals(Data.Foo);
|
|
String msg = expectMsgClass(String.class);
|
|
assertTrue(msg.startsWith("LogEntry(SomeState,Foo,Actor[akka://FSMDocTest/system/"));
|
|
}
|
|
};
|
|
}
|
|
}
|