From 0dcb6d6654b8065bb9b2a2d6dd4c1b25253a931d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjo=CC=88rn=20Antonsson?= Date: Fri, 21 Feb 2014 12:43:30 +0100 Subject: [PATCH] !act,doc #3831 Adding more Java with Lambda documentation and support * The Java with Lambda support documentation for AbstractActor and AbstractFSM are now on par with Scala * Many small fixes and additions of missing things * Added an AbstractActorContext that has convenience functions for getChild and getChildren --- akka-actor/src/main/java/akka/japi/pf/FI.java | 16 +- .../akka/japi/pf/FSMStateFunctionBuilder.java | 75 +- .../java/akka/japi/pf/FSMStopBuilder.java | 4 +- .../japi/pf/FSMTransitionHandlerBuilder.java | 42 +- .../src/main/java/akka/japi/pf/Match.java | 14 + .../src/main/java/akka/japi/pf/PFBuilder.java | 19 + .../java/akka/japi/pf/ReceiveBuilder.java | 30 +- .../src/main/java/akka/japi/pf/UnitMatch.java | 26 + .../main/java/akka/japi/pf/UnitPFBuilder.java | 18 + .../main/scala/akka/actor/AbstractActor.scala | 100 ++ .../src/main/scala/akka/actor/Actor.scala | 8 - .../src/main/scala/akka/actor/ActorCell.scala | 45 +- .../src/main/scala/akka/actor/FSM.scala | 110 ++- .../actor/MyReceiveTimeoutUntypedActor.java | 7 +- akka-docs/rst/java/lambda-actors.rst | 916 +++++++++++++++++- akka-docs/rst/java/lambda-fsm.rst | 116 ++- akka-docs/rst/java/untyped-actors.rst | 28 +- .../scala/code/docs/actor/ActorDocSpec.scala | 26 - akka-docs/rst/scala/fsm.rst | 2 +- .../build.sbt | 4 +- .../pom.xml | 6 +- .../project/build.properties | 0 .../test/java/docs/actor/ActorDocTest.java | 561 +++++++++++ .../docs/actor/InitializationDocTest.java | 67 ++ .../src/test/java/docs/actor/Messages.java | 149 +++ .../src/test/java/docs/actor}/MyActor.java | 15 +- .../test/java/docs/actor}/SampleActor.java | 6 +- .../java/docs/actor}/SampleActorTest.java | 7 +- .../test/java/docs/actor/fsm}/Buncher.java | 49 +- .../java/docs/actor/fsm}/BuncherTest.java | 37 +- .../src/test/java/docs/actor/fsm}/Events.java | 6 +- .../test/java/docs/actor/fsm/FSMDocTest.java | 179 ++++ akka-samples/akka-sample-java8/LICENSE | 13 - scripts/build/extra-build-steps.sh | 4 +- 34 files changed, 2494 insertions(+), 211 deletions(-) create mode 100644 akka-actor/src/main/scala/akka/actor/AbstractActor.scala rename akka-samples/{akka-sample-java8 => akka-docs-java-lambda}/build.sbt (89%) rename akka-samples/{akka-sample-java8 => akka-docs-java-lambda}/pom.xml (94%) rename akka-samples/{akka-sample-java8 => akka-docs-java-lambda}/project/build.properties (100%) create mode 100644 akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java create mode 100644 akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/InitializationDocTest.java create mode 100644 akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/Messages.java rename akka-samples/{akka-sample-java8/src/main/java/sample/java8 => akka-docs-java-lambda/src/test/java/docs/actor}/MyActor.java (64%) rename akka-samples/{akka-sample-java8/src/main/java/sample/java8 => akka-docs-java-lambda/src/test/java/docs/actor}/SampleActor.java (90%) rename akka-samples/{akka-sample-java8/src/test/java/sample => akka-docs-java-lambda/src/test/java/docs/actor}/SampleActorTest.java (93%) rename akka-samples/{akka-sample-java8/src/main/java/sample/java8/buncher => akka-docs-java-lambda/src/test/java/docs/actor/fsm}/Buncher.java (73%) rename akka-samples/{akka-sample-java8/src/test/java/sample => akka-docs-java-lambda/src/test/java/docs/actor/fsm}/BuncherTest.java (61%) rename akka-samples/{akka-sample-java8/src/main/java/sample/java8/buncher => akka-docs-java-lambda/src/test/java/docs/actor/fsm}/Events.java (94%) create mode 100644 akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java delete mode 100644 akka-samples/akka-sample-java8/LICENSE diff --git a/akka-actor/src/main/java/akka/japi/pf/FI.java b/akka-actor/src/main/java/akka/japi/pf/FI.java index b52b51a3f3..beacf08511 100644 --- a/akka-actor/src/main/java/akka/japi/pf/FI.java +++ b/akka-actor/src/main/java/akka/japi/pf/FI.java @@ -25,7 +25,7 @@ public final class FI { * @param i an instance that the application is performed on * @return the result of the application */ - public abstract R apply(I i); + public R apply(I i) throws Exception; } /** @@ -43,7 +43,7 @@ public final class FI { * @param i2 an instance that the application is performed on * @return the result of the application */ - public abstract R apply(I1 i1, I2 i2); + public R apply(I1 i1, I2 i2) throws Exception; } /** @@ -58,7 +58,7 @@ public final class FI { * @param t an instance that the predicate is evaluated on. * @return the result of the predicate */ - public abstract boolean defined(T t); + public boolean defined(T t); } /** @@ -72,7 +72,7 @@ public final class FI { * * @param i an instance that the application is performed on */ - public abstract void apply(I i); + public void apply(I i) throws Exception; } /** @@ -88,7 +88,7 @@ public final class FI { * @param i1 an instance that the application is performed on * @param i2 an instance that the application is performed on */ - public abstract void apply(I1 i1, I2 i2); + public void apply(I1 i1, I2 i2) throws Exception; } /** @@ -106,7 +106,7 @@ public final class FI { * @param i2 an instance that the application is performed on * @param i3 an instance that the application is performed on */ - public abstract void apply(I1 i1, I2 i2, I3 i3); + public void apply(I1 i1, I2 i2, I3 i3) throws Exception; } /** @@ -116,7 +116,7 @@ public final class FI { /** * The application to perform. */ - public abstract void apply(); + public void apply() throws Exception; } /** @@ -129,7 +129,7 @@ public final class FI { * @param o an instance that the predicate is evaluated on. * @return the result of the predicate */ - public abstract boolean defined(Object o); + public boolean defined(Object o); } diff --git a/akka-actor/src/main/java/akka/japi/pf/FSMStateFunctionBuilder.java b/akka-actor/src/main/java/akka/japi/pf/FSMStateFunctionBuilder.java index d6e900cb37..2d94ba7b4d 100644 --- a/akka-actor/src/main/java/akka/japi/pf/FSMStateFunctionBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/FSMStateFunctionBuilder.java @@ -7,6 +7,7 @@ package akka.japi.pf; import akka.actor.FSM; import scala.PartialFunction; +import java.util.Arrays; import java.util.List; /** @@ -42,7 +43,7 @@ public class FSMStateFunctionBuilder { } }, new FI.Apply>() { - public FSM.State apply(FSM.Event e) { + public FSM.State apply(FSM.Event e) throws Exception { @SuppressWarnings("unchecked") P p = (P) e.event(); @SuppressWarnings("unchecked") @@ -91,7 +92,7 @@ public class FSMStateFunctionBuilder { } }, new FI.Apply>() { - public FSM.State apply(FSM.Event e) { + public FSM.State apply(FSM.Event e) throws Exception { @SuppressWarnings("unchecked") Q q = (Q) e.stateData(); return apply.apply(q); @@ -102,6 +103,74 @@ public class FSMStateFunctionBuilder { return this; } + /** + * Add a case statement that matches on the data type and if the event compares equal. + * + * @param event an event to compare equal against + * @param dataType the data type to match on + * @param apply an action to apply to the event and state data if there is a match + * @param the data type to match on + * @return the builder with the case statement added + */ + public FSMStateFunctionBuilder eventEquals(final Object event, + final Class dataType, + final FI.Apply> apply) { + return event(Arrays.asList(event), dataType, apply); + } + + /** + * Add a case statement that matches if any of the event types in the list match or + * any of the event instances in the list compares equal. + * + * @param eventMatches a list of types or instances to match against + * @param apply an action to apply to the event and state data if there is a match + * @return the builder with the case statement added + */ + public FSMStateFunctionBuilder event(final List eventMatches, + final FI.Apply> apply) { + builder.match(FSM.Event.class, + new FI.TypedPredicate() { + @Override + public boolean defined(FSM.Event e) { + boolean emMatch = false; + Object event = e.event(); + for (Object em : eventMatches) { + if (em instanceof Class) { + Class emc = (Class) em; + emMatch = emc.isInstance(event); + } else { + emMatch = event.equals(em); + } + if (emMatch) + break; + } + return emMatch; + } + }, + new FI.Apply>() { + public FSM.State apply(FSM.Event e) throws Exception { + @SuppressWarnings("unchecked") + D d = (D) e.stateData(); + return apply.apply(d); + } + } + ); + + return this; + } + + /** + * Add a case statement that matches if event compares equal. + * + * @param event an event to compare equal against + * @param apply an action to apply to the event and state data if there is a match + * @return the builder with the case statement added + */ + public FSMStateFunctionBuilder eventEquals(final Object event, + final FI.Apply> apply) { + return event(Arrays.asList(event), apply); + } + /** * Add a case statement that matches on any type of event. * @@ -111,7 +180,7 @@ public class FSMStateFunctionBuilder { public FSMStateFunctionBuilder anyEvent(final FI.Apply2> apply) { builder.match(FSM.Event.class, new FI.Apply>() { - public FSM.State apply(FSM.Event e) { + public FSM.State apply(FSM.Event e) throws Exception { @SuppressWarnings("unchecked") D d = (D) e.stateData(); return apply.apply(e.event(), d); diff --git a/akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java b/akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java index ffa77e26d2..50affc24b8 100644 --- a/akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java @@ -36,7 +36,7 @@ public class FSMStopBuilder { } }, new FI.UnitApply() { - public void apply(FSM.StopEvent e) { + public void apply(FSM.StopEvent e) throws Exception { @SuppressWarnings("unchecked") S s = (S) e.currentState(); @SuppressWarnings("unchecked") @@ -94,7 +94,7 @@ public class FSMStopBuilder { } }, new FI.UnitApply() { - public void apply(FSM.StopEvent e) { + public void apply(FSM.StopEvent e) throws Exception { @SuppressWarnings("unchecked") P p = (P) e.reason(); @SuppressWarnings("unchecked") diff --git a/akka-actor/src/main/java/akka/japi/pf/FSMTransitionHandlerBuilder.java b/akka-actor/src/main/java/akka/japi/pf/FSMTransitionHandlerBuilder.java index 8bc377e8ad..118131c367 100644 --- a/akka-actor/src/main/java/akka/japi/pf/FSMTransitionHandlerBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/FSMTransitionHandlerBuilder.java @@ -21,8 +21,8 @@ public class FSMTransitionHandlerBuilder { /** * Add a case statement that matches on a from state and a to state. * - * @param fromState the from state to match on - * @param toState the to state to match on + * @param fromState the from state to match on, or null for any + * @param toState the to state to match on, or null for any * @param apply an action to apply when the states match * @return the builder with the case statement added */ @@ -33,12 +33,13 @@ public class FSMTransitionHandlerBuilder { new FI.TypedPredicate() { @Override public boolean defined(Tuple2 t) { - return fromState.equals(t._1()) && toState.equals(t._2()); + return (fromState == null || fromState.equals(t._1())) + && (toState == null || toState.equals(t._2())); } }, new FI.UnitApply() { @Override - public void apply(Tuple2 t) { + public void apply(Tuple2 t) throws Exception { apply.apply(); } } @@ -46,6 +47,39 @@ public class FSMTransitionHandlerBuilder { return this; } + /** + * Add a case statement that matches on a from state and a to state. + * + * @param fromState the from state to match on, or null for any + * @param toState the to state to match on, or null for any + * @param apply an action to apply when the states match + * @return the builder with the case statement added + */ + public FSMTransitionHandlerBuilder state(final S fromState, + final S toState, + final FI.UnitApply2 apply) { + builder.match(Tuple2.class, + new FI.TypedPredicate() { + @Override + public boolean defined(Tuple2 t) { + return (fromState == null || fromState.equals(t._1())) + && (toState == null || toState.equals(t._2())); + } + }, + new FI.UnitApply() { + @Override + public void apply(Tuple2 t) throws Exception { + @SuppressWarnings("unchecked") + S sf = (S) t._1(); + @SuppressWarnings("unchecked") + S st = (S) t._2(); + apply.apply(sf, st); + } + } + ); + return this; + } + /** * Build a {@link scala.PartialFunction} from this builder. * After this call the builder will be reset. diff --git a/akka-actor/src/main/java/akka/japi/pf/Match.java b/akka-actor/src/main/java/akka/japi/pf/Match.java index 3bd8838285..1cfffae15d 100644 --- a/akka-actor/src/main/java/akka/japi/pf/Match.java +++ b/akka-actor/src/main/java/akka/japi/pf/Match.java @@ -46,6 +46,20 @@ public class Match extends AbstractMatch { return new PFBuilder().match(type, predicate, apply); } + /** + * Convenience function to create a {@link PFBuilder} with the first + * case statement added. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + * @see PFBuilder#matchEquals(Object, FI.Apply) + */ + public static PFBuilder matchEquals(final P object, + final FI.Apply apply) { + return new PFBuilder().matchEquals(object, apply); + } + /** * Create a {@link Match} from the builder. * diff --git a/akka-actor/src/main/java/akka/japi/pf/PFBuilder.java b/akka-actor/src/main/java/akka/japi/pf/PFBuilder.java index 60b72a92d6..ab7e0bd41d 100644 --- a/akka-actor/src/main/java/akka/japi/pf/PFBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/PFBuilder.java @@ -63,6 +63,25 @@ public final class PFBuilder extends AbstractPFBuilder { return this; } + /** + * Add a new case statement to this builder. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + */ + public

PFBuilder matchEquals(final P object, + final FI.Apply apply) { + addStatement(new CaseStatement( + new FI.Predicate() { + @Override + public boolean defined(Object o) { + return object.equals(o); + } + }, apply)); + return this; + } + /** * Add a new case statement to this builder, that matches any argument. * @param apply an action to apply to the argument diff --git a/akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java b/akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java index 4dbdad747b..7004611212 100644 --- a/akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java @@ -9,7 +9,9 @@ package akka.japi.pf; * * There is both a match on type only, and a match on type and predicate. * - * Inside an actor you can use it like this with Java 8 to define your receive method: + * Inside an actor you can use it like this with Java 8 to define your receive method. + *

+ * Example: *

  * @Override
  * public PartialFunction receive() {
@@ -51,8 +53,30 @@ public class ReceiveBuilder {
    * @return           a builder with the case statement added
    */
   public static 

UnitPFBuilder match(final Class

type, - FI.TypedPredicate

predicate, - FI.UnitApply

apply) { + FI.TypedPredicate

predicate, + FI.UnitApply

apply) { return UnitMatch.match(type, predicate, apply); } + + /** + * Return a new {@link UnitPFBuilder} with a case statement added. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + */ + public static

UnitPFBuilder matchEquals(P object, FI.UnitApply

apply) { + return UnitMatch.matchEquals(object, apply); + } + + /** + * Return a new {@link UnitPFBuilder} with a case statement added. + * + * @param apply an action to apply to the argument + * @return a builder with the case statement added + */ + public static UnitPFBuilder matchAny(FI.UnitApply apply) { + return UnitMatch.matchAny(apply); + } + } diff --git a/akka-actor/src/main/java/akka/japi/pf/UnitMatch.java b/akka-actor/src/main/java/akka/japi/pf/UnitMatch.java index 8afb742a83..6c23143552 100644 --- a/akka-actor/src/main/java/akka/japi/pf/UnitMatch.java +++ b/akka-actor/src/main/java/akka/japi/pf/UnitMatch.java @@ -47,6 +47,32 @@ public class UnitMatch extends AbstractMatch { return new UnitPFBuilder().match(type, predicate, apply); } + /** + * Convenience function to create a {@link UnitPFBuilder} with the first + * case statement added. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + * @see UnitPFBuilder#matchEquals(Object, FI.UnitApply) + */ + public static UnitPFBuilder matchEquals(final P object, + final FI.UnitApply

apply) { + return new UnitPFBuilder().matchEquals(object, apply); + } + + /** + * Convenience function to create a {@link UnitPFBuilder} with the first + * case statement added. + * + * @param apply an action to apply to the argument + * @return a builder with the case statement added + * @see UnitPFBuilder#matchAny(FI.UnitApply) + */ + public static UnitPFBuilder matchAny(final FI.UnitApply apply) { + return new UnitPFBuilder().matchAny(apply); + } + /** * Create a {@link UnitMatch} from the builder. * diff --git a/akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java b/akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java index c5b20a4aa6..1347b0ef5b 100644 --- a/akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java +++ b/akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java @@ -67,6 +67,24 @@ public final class UnitPFBuilder extends AbstractPFBuilder { return this; } + /** + * Add a new case statement to this builder. + * + * @param object the object to compare equals with + * @param apply an action to apply to the argument if the object compares equal + * @return a builder with the case statement added + */ + public

UnitPFBuilder matchEquals(final P object, + final FI.UnitApply

apply) { + addStatement(new UnitCaseStatement( + new FI.Predicate() { + @Override + public boolean defined(Object o) { + return object.equals(o); + } + }, apply)); + return this; + } /** * Add a new case statement to this builder, that matches any argument. * @param apply an action to apply to the argument diff --git a/akka-actor/src/main/scala/akka/actor/AbstractActor.scala b/akka-actor/src/main/scala/akka/actor/AbstractActor.scala new file mode 100644 index 0000000000..f5f71b6877 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/AbstractActor.scala @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.actor + +object AbstractActor { + /** + * emptyBehavior is a Receive-expression that matches no messages at all, ever. + */ + final val emptyBehavior = Actor.emptyBehavior +} + +/** + * Actor base class that should be extended to create Java actors that use lambdas. + *

+ * Example: + *

+ * public class MyActor extends AbstractActor {
+ *   int count = 0;
+ *   @Override
+ *   public PartialFunction receive() {
+ *     return ReceiveBuilder.
+ *       match(Double.class, d -> {
+ *         sender().tell(d.isNaN() ? 0 : d, self());
+ *       }).
+ *       match(Integer.class, i -> {
+ *         sender().tell(i * 10, self());
+ *       }).
+ *       match(String.class, s -> s.startsWith("foo"), s -> {
+ *         sender().tell(s.toUpperCase(), self());
+ *       }).build();
+ *   }
+ * }
+ * 
+ */ +abstract class AbstractActor extends Actor { + /** + * Returns this AbstractActor's AbstractActorContext + * The AbstractActorContext is not thread safe so do not expose it outside of the + * AbstractActor. + */ + def getContext(): AbstractActorContext = context.asInstanceOf[AbstractActorContext] +} + +/** + * Actor base class that should be extended to create an actor with a stash. + * + * The stash enables an actor to temporarily stash away messages that can not or + * should not be handled using the actor's current behavior. + *

+ * Example: + *

+ * public class MyActorWithStash extends AbstractActorWithStash {
+ *   int count = 0;
+ *   @Override
+ *   public PartialFunction receive() {
+ *     return ReceiveBuilder.match(String.class, s -> {
+ *       if (count < 0) {
+ *         sender().tell(new Integer(s.length()), self());
+ *       } else if (count == 2) {
+ *         count = -1;
+ *         unstashAll();
+ *       } else {
+ *         count += 1;
+ *         stash();
+ *       }
+ *     }).build();
+ *   }
+ * }
+ * 
+ * Note that the subclasses of `AbstractActorWithStash` by default request a Deque based mailbox since this class + * implements the `RequiresMessageQueue` marker interface. + * You can override the default mailbox provided when `DequeBasedMessageQueueSemantics` are requested via config: + *
+ *   akka.actor.mailbox.requirements {
+ *     "akka.dispatch.BoundedDequeBasedMessageQueueSemantics" = your-custom-mailbox
+ *   }
+ * 
+ * Alternatively, you can add your own requirement marker to the actor and configure a mailbox type to be used + * for your marker. + * + * For a `Stash` based actor that enforces unbounded deques see [[akka.actor.AbstractActorWithUnboundedStash]]. + * There is also an unrestricted version [[akka.actor.AbstractActorWithUnrestrictedStash]] that does not + * enforce the mailbox type. + */ +abstract class AbstractActorWithStash extends AbstractActor with Stash + +/** + * Actor base class with `Stash` that enforces an unbounded deque for the actor. The proper mailbox has to be configured + * manually, and the mailbox should extend the [[akka.dispatch.DequeBasedMessageQueueSemantics]] marker trait. + * See [[akka.actor.AbstractActorWithStash]] for details on how `Stash` works. + */ +abstract class AbstractActorWithUnboundedStash extends AbstractActor with UnboundedStash + +/** + * Actor base class with `Stash` that does not enforce any mailbox type. The mailbox of the actor has to be configured + * manually. See [[akka.actor.AbstractActorWithStash]] for details on how `Stash` works. + */ +abstract class AbstractActorWithUnrestrictedStash extends AbstractActor with UnrestrictedStash diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 357fb2747f..55c7e2a3d0 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -563,11 +563,3 @@ trait Actor { } } } - -/** - * Java API - * - * Abstract base class for Java Actors. - * - */ -abstract class AbstractActor extends akka.actor.Actor diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 0196fd6aa5..69b5d720f1 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -70,6 +70,12 @@ trait ActorContext extends ActorRefFactory { */ def setReceiveTimeout(timeout: Duration): Unit + /** + * Changes the Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler. + * Replaces the current behavior on the top of the behavior stack. + */ + def become(behavior: Actor.Receive): Unit = become(behavior, true) + /** * Changes the Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler. * This method acts upon the behavior stack as follows: @@ -77,14 +83,14 @@ trait ActorContext extends ActorRefFactory { * - if `discardOld = true` it will replace the top element (i.e. the current behavior) * - if `discardOld = false` it will keep the current behavior and push the given one atop * - * The default of replacing the current behavior has been chosen to avoid memory leaks in - * case client code is written without consulting this documentation first (i.e. always pushing - * new closures and never issuing an `unbecome()`) + * The default of replacing the current behavior on the stack has been chosen to avoid memory + * leaks in case client code is written without consulting this documentation first (i.e. + * always pushing new behaviors and never issuing an `unbecome()`) */ - def become(behavior: Actor.Receive, discardOld: Boolean = true): Unit + def become(behavior: Actor.Receive, discardOld: Boolean): Unit /** - * Reverts the Actor behavior to the previous one in the hotswap stack. + * Reverts the Actor behavior to the previous one on the behavior stack. */ def unbecome(): Unit @@ -149,6 +155,25 @@ trait ActorContext extends ActorRefFactory { throw new NotSerializableException("ActorContext is not serializable!") } +/** + * AbstractActorContext is the AbstractActor equivalent of ActorContext, + * containing the Java API + */ +trait AbstractActorContext extends ActorContext { + + /** + * Returns an unmodifiable Java Collection containing the linked actors, + * please note that the backing map is thread-safe but not immutable + */ + def getChildren(): java.lang.Iterable[ActorRef] + + /** + * Returns a reference to the named child or null if no child with + * that name exists. + */ + def getChild(name: String): ActorRef +} + /** * UntypedActorContext is the UntypedActor equivalent of ActorContext, * containing the Java API @@ -169,7 +194,7 @@ trait UntypedActorContext extends ActorContext { /** * Changes the Actor's behavior to become the new 'Procedure' handler. - * Replaces the current behavior at the top of the hotswap stack. + * Replaces the current behavior on the top of the behavior stack. */ def become(behavior: Procedure[Any]): Unit @@ -180,9 +205,9 @@ trait UntypedActorContext extends ActorContext { * - if `discardOld = true` it will replace the top element (i.e. the current behavior) * - if `discardOld = false` it will keep the current behavior and push the given one atop * - * The default of replacing the current behavior has been chosen to avoid memory leaks in - * case client code is written without consulting this documentation first (i.e. always pushing - * new closures and never issuing an `unbecome()`) + * The default of replacing the current behavior on the stack has been chosen to avoid memory + * leaks in case client code is written without consulting this documentation first (i.e. + * always pushing new behaviors and never issuing an `unbecome()`) */ def become(behavior: Procedure[Any], discardOld: Boolean): Unit @@ -347,7 +372,7 @@ private[akka] class ActorCell( final val props: Props, // Must be final so that it can be properly cleared in clearActorCellFields val dispatcher: MessageDispatcher, val parent: InternalActorRef) - extends UntypedActorContext with Cell + extends UntypedActorContext with AbstractActorContext with Cell with dungeon.ReceiveTimeout with dungeon.Children with dungeon.Dispatch diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index fe1284113a..9bba9ee5fa 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -266,12 +266,11 @@ trait FSM[S, D] extends Actor with Listeners with ActorLogging { type Timeout = Option[FiniteDuration] type TransitionHandler = PartialFunction[(S, S), Unit] - val Event: FSM.Event.type = FSM.Event - val StopEvent: FSM.StopEvent.type = FSM.StopEvent - /* * “import” so that these are visible without an import */ + val Event: FSM.Event.type = FSM.Event + val StopEvent: FSM.StopEvent.type = FSM.StopEvent /** * This extractor is just convenience for matching a (S, S) pair, including a @@ -755,6 +754,21 @@ trait LoggingFSM[S, D] extends FSM[S, D] { this: Actor ⇒ } +/** + * Java API + */ +object AbstractFSM { + /** + * A partial function value which does not match anything and can be used to + * “reset” `whenUnhandled` and `onTermination` handlers. + * + * {{{ + * onTermination(FSM.NullFunction()) + * }}} + */ + def NullFunction[S, D]: PartialFunction[S, D] = FSM.NullFunction +} + /** * Java API * @@ -834,6 +848,16 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] { final def onTransition(transitionHandlerBuilder: FSMTransitionHandlerBuilder[S]): Unit = onTransition(transitionHandlerBuilder.build().asInstanceOf[TransitionHandler]) + /** + * Add a handler which is called upon each state transition, i.e. not when + * staying in the same state. + * + * Multiple handlers may be installed, and every one of them will be + * called, not only the first one matching. + */ + final def onTransition(transitionHandler: UnitApply2[S, S]): Unit = + onTransition(transitionHandler) + /** * Set handler which is called upon reception of unhandled messages. Calling * this method again will overwrite the previous contents. @@ -877,6 +901,44 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] { final def matchEvent[DT <: D](eventMatches: JList[AnyRef], dataType: Class[DT], apply: Apply[DT, State]): FSMStateFunctionBuilder[S, D] = new FSMStateFunctionBuilder[S, D]().event(eventMatches, dataType, apply); + /** + * Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set. + * + * A case statement that matches on the data type and if the event compares equal. + * + * @param event an event to compare equal against + * @param dataType the data type to match on + * @param apply an action to apply to the event and state data if there is a match + * @return the builder with the case statement added + */ + final def matchEventEquals[DT <: D](event: AnyRef, dataType: Class[DT], apply: Apply[DT, State]): FSMStateFunctionBuilder[S, D] = + new FSMStateFunctionBuilder[S, D]().eventEquals(event, dataType, apply); + + /** + * Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set. + * + * A case statement that matches if any of the event types in the list match or any + * of the event instances in the list compares equal. + * + * @param eventMatches a list of types or instances to match against + * @param apply an action to apply to the event and state data if there is a match + * @return the builder with the case statement added + */ + final def matchEvent(eventMatches: JList[AnyRef], apply: Apply[D, State]): FSMStateFunctionBuilder[S, D] = + new FSMStateFunctionBuilder[S, D]().event(eventMatches, apply); + + /** + * Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set. + * + * A case statement that matches if the event compares equal. + * + * @param event an event to compare equal against + * @param apply an action to apply to the event and state data if there is a match + * @return the builder with the case statement added + */ + final def matchEventEquals(event: AnyRef, apply: Apply[D, State]): FSMStateFunctionBuilder[S, D] = + new FSMStateFunctionBuilder[S, D]().eventEquals(event, apply); + /** * Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set. * @@ -901,6 +963,19 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] { final def matchState(fromState: S, toState: S, apply: UnitApplyVoid): FSMTransitionHandlerBuilder[S] = new FSMTransitionHandlerBuilder[S]().state(fromState, toState, apply) + /** + * Create an [[akka.japi.pf.FSMTransitionHandlerBuilder]] with the first case statement set. + * + * A case statement that matches on a from state and a to state. + * + * @param fromState the from state to match on + * @param toState the to state to match on + * @param apply an action to apply when the states match + * @return the builder with the case statement added + */ + final def matchState(fromState: S, toState: S, apply: UnitApply2[S, S]): FSMTransitionHandlerBuilder[S] = + new FSMTransitionHandlerBuilder[S]().state(fromState, toState, apply) + /** * Create an [[akka.japi.pf.FSMStopBuilder]] with the first case statement set. * @@ -968,6 +1043,18 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] { */ final def goTo(nextStateName: S): State = goto(nextStateName) + /** + * Schedule named timer to deliver message after given delay, possibly repeating. + * Any existing timer with the same name will automatically be canceled before + * adding the new timer. + * @param name identifier to be used with cancelTimer() + * @param msg message to be delivered + * @param timeout delay of first message delivery and between subsequent messages + * @return current state descriptor + */ + final def setTimer(name: String, msg: Any, timeout: FiniteDuration): Unit = + setTimer(name, msg, timeout, false); + /** * Default reason if calling `stop()`. */ @@ -978,23 +1065,6 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] { * also applies to `Stop` supervision directive. */ val Shutdown: FSM.Reason = FSM.Shutdown - - /** - * Signifies that the [[akka.actor.FSM]] is shutting itself down because of - * an error, e.g. if the state to transition into does not exist. You can use - * this to match on a Failure in the [[akka.japi.pf.FSMStopBuilder]]. - */ - def Failure: Class[_ <: FSM.Reason] = classOf[FSM.Failure] - - /** - * A partial function value which does not match anything and can be used to - * “reset” `whenUnhandled` and `onTermination` handlers. - * - * {{{ - * onTermination(FSM.NullFunction) - * }}} - */ - val NullFunction: PartialFunction[Any, Nothing] = FSM.NullFunction } /** diff --git a/akka-docs/rst/java/code/docs/actor/MyReceiveTimeoutUntypedActor.java b/akka-docs/rst/java/code/docs/actor/MyReceiveTimeoutUntypedActor.java index e3359da933..4fbfb1c0b9 100644 --- a/akka-docs/rst/java/code/docs/actor/MyReceiveTimeoutUntypedActor.java +++ b/akka-docs/rst/java/code/docs/actor/MyReceiveTimeoutUntypedActor.java @@ -10,8 +10,9 @@ import akka.actor.UntypedActor; import scala.concurrent.duration.Duration; public class MyReceiveTimeoutUntypedActor extends UntypedActor { - + //#receive-timeout ActorRef target = getContext().system().deadLetters(); + //#receive-timeout public MyReceiveTimeoutUntypedActor() { // To set an initial delay @@ -22,12 +23,16 @@ public class MyReceiveTimeoutUntypedActor extends UntypedActor { if (message.equals("Hello")) { // To set in a response to a message getContext().setReceiveTimeout(Duration.create("1 second")); + //#receive-timeout target = getSender(); target.tell("Hello world", getSelf()); + //#receive-timeout } else if (message instanceof ReceiveTimeout) { // To turn it off getContext().setReceiveTimeout(Duration.Undefined()); + //#receive-timeout target.tell("timeout", getSelf()); + //#receive-timeout } else { unhandled(message); } diff --git a/akka-docs/rst/java/lambda-actors.rst b/akka-docs/rst/java/lambda-actors.rst index 841762581b..a88c04e4f3 100644 --- a/akka-docs/rst/java/lambda-actors.rst +++ b/akka-docs/rst/java/lambda-actors.rst @@ -1,14 +1,37 @@ .. _lambda-actors-java: ################################### - Actors (Java8 with Lambda Support) + Actors (Java with Lambda Support) ################################### +The `Actor Model`_ provides a higher level of abstraction for writing concurrent +and distributed systems. It alleviates the developer from having to deal with +explicit locking and thread management, making it easier to write correct +concurrent and parallel systems. Actors were defined in the 1973 paper by Carl +Hewitt but have been popularized by the Erlang language, and used for example at +Ericsson with great success to build highly concurrent and reliable telecom +systems. + +The API of Akka’s Actors is similar to Scala Actors which has borrowed some of +its syntax from Erlang. + +.. _Actor Model: http://en.wikipedia.org/wiki/Actor_model + + +Creating Actors +=============== + +.. note:: + + Since Akka enforces parental supervision every actor is supervised and + (potentially) the supervisor of its children, it is advisable that you + familiarize yourself with :ref:`actor-systems` and :ref:`supervision` and it + may also help to read :ref:`addressing`. Defining an Actor class ----------------------- -Actor classes are implemented by extending the :class:AbstractActor class and implementing +Actor classes are implemented by extending the :class:`AbstractActor` class and implementing the :meth:`receive` method. The :meth:`receive` method should define a series of match statements (which has the type ``PartialFunction``) that defines which messages your Actor can handle, along with the implementation of how the @@ -19,7 +42,7 @@ function there is a builder named ``ReceiveBuilder`` that you can use. Here is an example: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/MyActor.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java :include: imports,my-actor Please note that the Akka Actor ``receive`` message loop is exhaustive, which @@ -27,20 +50,891 @@ is different compared to Erlang and the late Scala Actors. This means that you need to provide a pattern match for all messages that it can accept and if you want to be able to handle unknown messages then you need to have a default case as in the example above. Otherwise an ``akka.actor.UnhandledMessage(message, -sender, recipient)`` will be published to the ``ActorSystem``'s ``EventStream``. +sender, recipient)`` will be published to the ``ActorSystem``'s +``EventStream``. Note further that the return type of the behavior defined above is ``Unit``; if the actor shall reply to the received message then this must be done explicitly as explained below. The result of the :meth:`receive` method is a partial function object, which is -stored within the actor as its “initial behavior”. +stored within the actor as its “initial behavior”, see `Become/Unbecome`_ for +further information on changing the behavior of an actor after its +construction. -Here is s slightly bigger example: - -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/SampleActor.java - -TODO: +Props ----- -Lots of doc missing here +:class:`Props` is a configuration class to specify options for the creation +of actors, think of it as an immutable and thus freely shareable recipe for +creating an actor including associated deployment information (e.g. which +dispatcher to use, see more below). Here are some examples of how to create a +:class:`Props` instance. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#import-props +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#creating-props + +The second variant shows how to pass constructor arguments to the +:class:`Actor` being created, but it should only be used outside of actors as +explained below. + +The last line shows a possibility to pass constructor arguments regardless of +the context it is being used in. The presence of a matching constructor is +verified during construction of the :class:`Props` object, resulting in an +:class:`IllegalArgumentEception` if no or multiple matching constructors are +found. + +Dangerous Variants +^^^^^^^^^^^^^^^^^^ + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#creating-props-deprecated + +This method is not recommended to be used within another actor because it +encourages to close over the enclosing scope, resulting in non-serializable +:class:`Props` and possibly race conditions (breaking the actor encapsulation). +On the other hand using this variant in a :class:`Props` factory in the actor’s +companion object as documented under “Recommended Practices” below is completely +fine. + +There were two use-cases for these methods: passing constructor arguments to +the actor—which is solved by the newly introduced +:meth:`Props.create(clazz, args)` method above or the recommended practice +below—and creating actors “on the spot” as anonymous classes. The latter should +be solved by making these actors named classes instead (if they are not +declared within a top-level ``object`` then the enclosing instance’s ``this`` +reference needs to be passed as the first argument). + +.. warning:: + + Declaring one actor within another is very dangerous and breaks actor + encapsulation. Never pass an actor’s ``this`` reference into :class:`Props`! + +Recommended Practices +^^^^^^^^^^^^^^^^^^^^^ + +It is a good idea to provide factory methods on the companion object of each +:class:`Actor` which help keeping the creation of suitable :class:`Props` as +close to the actor definition as possible. This also avoids the pitfalls +associated with using the ``Props.create(...)`` method which takes a by-name +argument, since within a companion object the given code block will not retain +a reference to its enclosing scope: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#props-factory + +Creating Actors with Props +-------------------------- + +Actors are created by passing a :class:`Props` instance into the +:meth:`actorOf` factory method which is available on :class:`ActorSystem` and +:class:`ActorContext`. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#import-actorRef +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#system-actorOf + +Using the :class:`ActorSystem` will create top-level actors, supervised by the +actor system’s provided guardian actor, while using an actor’s context will +create a child actor. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#context-actorOf + :exclude: plus-some-behavior + +It is recommended to create a hierarchy of children, grand-children and so on +such that it fits the logical failure-handling structure of the application, +see :ref:`actor-systems`. + +The call to :meth:`actorOf` returns an instance of :class:`ActorRef`. This is a +handle to the actor instance and the only way to interact with it. The +:class:`ActorRef` is immutable and has a one to one relationship with the Actor +it represents. The :class:`ActorRef` is also serializable and network-aware. +This means that you can serialize it, send it over the wire and use it on a +remote host and it will still be representing the same Actor on the original +node, across the network. + +The name parameter is optional, but you should preferably name your actors, +since that is used in log messages and for identifying actors. The name must +not be empty or start with ``$``, but it may contain URL encoded characters +(eg. ``%20`` for a blank space). If the given name is already in use by +another child to the same parent an `InvalidActorNameException` is thrown. + +Actors are automatically started asynchronously when created. + +.. _actor-create-factory-lambda: + +Dependency Injection +-------------------- + +If your UntypedActor has a constructor that takes parameters then those need to +be part of the :class:`Props` as well, as described `above`__. But there +are cases when a factory method must be used, for example when the actual +constructor arguments are determined by a dependency injection framework. + +__ Props_ + +.. includecode:: code/docs/actor/UntypedActorDocTest.java#import-indirect +.. includecode:: code/docs/actor/UntypedActorDocTest.java + :include: creating-indirectly + :exclude: obtain-fresh-Actor-instance-from-DI-framework + +.. warning:: + + You might be tempted at times to offer an :class:`IndirectActorProducer` + which always returns the same instance, e.g. by using a static field. This is + not supported, as it goes against the meaning of an actor restart, which is + described here: :ref:`supervision-restart`. + + When using a dependency injection framework, actor beans *MUST NOT* have + singleton scope. + +Techniques for dependency injection and integration with dependency injection frameworks +are described in more depth in the +`Using Akka with Dependency Injection `_ +guideline and the `Akka Java Spring `_ tutorial +in Typesafe Activator. + +The Inbox +--------- + +When writing code outside of actors which shall communicate with actors, the +``ask`` pattern can be a solution (see below), but there are two thing it +cannot do: receiving multiple replies (e.g. by subscribing an :class:`ActorRef` +to a notification service) and watching other actors’ lifecycle. For these +purposes there is the :class:`Inbox` class: + +.. includecode:: code/docs/actor/InboxDocTest.java#inbox + +The :meth:`send` method wraps a normal :meth:`tell` and supplies the internal +actor’s reference as the sender. This allows the reply to be received on the +last line. Watching an actor is quite simple as well: + +.. includecode:: code/docs/actor/InboxDocTest.java#watch + +Actor API +========= + +The :class:`AbstractActor` class defines only one abstract method, the above mentioned +:meth:`receive`, which implements the behavior of the actor. + +If the current actor behavior does not match a received message, +:meth:`unhandled` is called, which by default publishes an +``akka.actor.UnhandledMessage(message, sender, recipient)`` on the actor +system’s event stream (set configuration item +``akka.actor.debug.unhandled`` to ``on`` to have them converted into +actual Debug messages). + +In addition, it offers: + +* :meth:`self()` reference to the :class:`ActorRef` of the actor + +* :meth:`sender()` reference sender Actor of the last received message, typically used as described in :ref:`LambdaActor.Reply` + +* :meth:`supervisorStrategy()` user overridable definition the strategy to use for supervising child actors + + This strategy is typically declared inside the actor in order to have access + to the actor’s internal state within the decider function: since failure is + communicated as a message sent to the supervisor and processed like other + messages (albeit outside of the normal behavior), all values and variables + within the actor are available, as is the ``sender`` reference (which will + be the immediate child reporting the failure; if the original failure + occurred within a distant descendant it is still reported one level up at a + time). + +* :meth:`context()` exposes contextual information for the actor and the current message, such as: + + * factory methods to create child actors (:meth:`actorOf`) + * system that the actor belongs to + * parent supervisor + * supervised children + * lifecycle monitoring + * hotswap behavior stack as described in :ref:`actor-hotswap-lambda` + +The remaining visible methods are user-overridable life-cycle hooks which are +described in the following: + +.. includecode:: code/docs/actor/UntypedActorDocTest.java#lifecycle-callbacks + +The implementations shown above are the defaults provided by the :class:`AbstractActor` +class. + +Actor Lifecycle +--------------- + +.. image:: ../images/actor_lifecycle.png + :align: center + :width: 680 + +A path in an actor system represents a "place" which might be occupied +by a living actor. Initially (apart from system initialized actors) a path is +empty. When ``actorOf()`` is called it assigns an *incarnation* of the actor +described by the passed ``Props`` to the given path. An actor incarnation is +identified by the path *and a UID*. A restart only swaps the ``Actor`` +instance defined by the ``Props`` but the incarnation and hence the UID remains +the same. + +The lifecycle of an incarnation ends when the actor is stopped. At +that point the appropriate lifecycle events are called and watching actors +are notified of the termination. After the incarnation is stopped, the path can +be reused again by creating an actor with ``actorOf()``. In this case the +name of the new incarnation will be the same as the previous one but the +UIDs will differ. + +An ``ActorRef`` always represents an incarnation (path and UID) not just a +given path. Therefore if an actor is stopped and a new one with the same +name is created an ``ActorRef`` of the old incarnation will not point +to the new one. + +``ActorSelection`` on the other hand points to the path (or multiple paths +if wildcards are used) and is completely oblivious to which incarnation is currently +occupying it. ``ActorSelection`` cannot be watched for this reason. It is +possible to resolve the current incarnation's ``ActorRef`` living under the +path by sending an ``Identify`` message to the ``ActorSelection`` which +will be replied to with an ``ActorIdentity`` containing the correct reference +(see :ref:`actorSelection-lambda`). This can also be done with the ``resolveOne`` +method of the :class:`ActorSelection`, which returns a ``Future`` of the matching +:class:`ActorRef`. + +.. _deathwatch-lambda: + +Lifecycle Monitoring aka DeathWatch +----------------------------------- + +In order to be notified when another actor terminates (i.e. stops permanently, +not temporary failure and restart), an actor may register itself for reception +of the :class:`Terminated` message dispatched by the other actor upon +termination (see `Stopping Actors`_). This service is provided by the +:class:`DeathWatch` component of the actor system. + +Registering a monitor is easy: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#watch + +It should be noted that the :class:`Terminated` message is generated +independent of the order in which registration and termination occur. +In particular, the watching actor will receive a :class:`Terminated` message +even if the watched actor has already been terminated at the time of registration. + +Registering multiple times does not necessarily lead to multiple messages being +generated, but there is no guarantee that only exactly one such message is +received: if termination of the watched actor has generated and queued the +message, and another registration is done before this message has been +processed, then a second message will be queued, because registering for +monitoring of an already terminated actor leads to the immediate generation of +the :class:`Terminated` message. + +It is also possible to deregister from watching another actor’s liveliness +using ``context.unwatch(target)``. This works even if the :class:`Terminated` +message has already been enqueued in the mailbox; after calling :meth:`unwatch` +no :class:`Terminated` message for that actor will be processed anymore. + +.. _start-hook-lambda: + +Start Hook +---------- + +Right after starting the actor, its :meth:`preStart` method is invoked. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#preStart + +This method is called when the actor is first created. During restarts it is +called by the default implementation of :meth:`postRestart`, which means that +by overriding that method you can choose whether the initialization code in +this method is called only exactly once for this actor or for every restart. +Initialization code which is part of the actor’s constructor will always be +called when an instance of the actor class is created, which happens at every +restart. + +.. _restart-hook-lambda: + +Restart Hooks +------------- + +All actors are supervised, i.e. linked to another actor with a fault +handling strategy. Actors may be restarted in case an exception is thrown while +processing a message (see :ref:`supervision`). This restart involves the hooks +mentioned above: + +1. The old actor is informed by calling :meth:`preRestart` with the exception + which caused the restart and the message which triggered that exception; the + latter may be ``None`` if the restart was not caused by processing a + message, e.g. when a supervisor does not trap the exception and is restarted + in turn by its supervisor, or if an actor is restarted due to a sibling’s + failure. If the message is available, then that message’s sender is also + accessible in the usual way (i.e. by calling ``sender``). + + This method is the best place for cleaning up, preparing hand-over to the + fresh actor instance, etc. By default it stops all children and calls + :meth:`postStop`. + +2. The initial factory from the ``actorOf`` call is used + to produce the fresh instance. + +3. The new actor’s :meth:`postRestart` method is invoked with the exception + which caused the restart. By default the :meth:`preStart` + is called, just as in the normal start-up case. + +An actor restart replaces only the actual actor object; the contents of the +mailbox is unaffected by the restart, so processing of messages will resume +after the :meth:`postRestart` hook returns. The message +that triggered the exception will not be received again. Any message +sent to an actor while it is being restarted will be queued to its mailbox as +usual. + +.. warning:: + + Be aware that the ordering of failure notifications relative to user messages + is not deterministic. In particular, a parent might restart its child before + it has processed the last messages sent by the child before the failure. + See :ref:`message-ordering` for details. + +.. _stop-hook-lambda: + +Stop Hook +--------- + +After stopping an actor, its :meth:`postStop` hook is called, which may be used +e.g. for deregistering this actor from other services. This hook is guaranteed +to run after message queuing has been disabled for this actor, i.e. messages +sent to a stopped actor will be redirected to the :obj:`deadLetters` of the +:obj:`ActorSystem`. + +.. _actorSelection-lambda: + +Identifying Actors via Actor Selection +====================================== + +As described in :ref:`addressing`, each actor has a unique logical path, which +is obtained by following the chain of actors from child to parent until +reaching the root of the actor system, and it has a physical path, which may +differ if the supervision chain includes any remote supervisors. These paths +are used by the system to look up actors, e.g. when a remote message is +received and the recipient is searched, but they are also useful more directly: +actors may look up other actors by specifying absolute or relative +paths—logical or physical—and receive back an :class:`ActorSelection` with the +result: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#selection-local + +The supplied path is parsed as a :class:`java.net.URI`, which basically means +that it is split on ``/`` into path elements. If the path starts with ``/``, it +is absolute and the look-up starts at the root guardian (which is the parent of +``"/user"``); otherwise it starts at the current actor. If a path element equals +``..``, the look-up will take a step “up” towards the supervisor of the +currently traversed actor, otherwise it will step “down” to the named child. +It should be noted that the ``..`` in actor paths here always means the logical +structure, i.e. the supervisor. + +The path elements of an actor selection may contain wildcard patterns allowing for +broadcasting of messages to that section: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#selection-wildcard + +Messages can be sent via the :class:`ActorSelection` and the path of the +:class:`ActorSelection` is looked up when delivering each message. If the selection +does not match any actors the message will be dropped. + +To acquire an :class:`ActorRef` for an :class:`ActorSelection` you need to send +a message to the selection and use the ``sender()`` reference of the reply from +the actor. There is a built-in ``Identify`` message that all Actors will +understand and automatically reply to with a ``ActorIdentity`` message +containing the :class:`ActorRef`. This message is handled specially by the +actors which are traversed in the sense that if a concrete name lookup fails +(i.e. a non-wildcard path element does not correspond to a live actor) then a +negative result is generated. Please note that this does not mean that delivery +of that reply is guaranteed, it still is a normal message. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#import-identify +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#identify + +You can also acquire an :class:`ActorRef` for an :class:`ActorSelection` with +the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` +of the matching :class:`ActorRef` if such an actor exists. It is completed with +failure [[akka.actor.ActorNotFound]] if no such actor exists or the identification +didn't complete within the supplied `timeout`. + +Remote actor addresses may also be looked up, if :ref:`remoting ` is enabled: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#selection-remote + +An example demonstrating actor look-up is given in :ref:`remote-sample-java`. + +.. note:: + + ``actorFor`` is deprecated in favor of ``actorSelection`` because actor references + acquired with ``actorFor`` behaves different for local and remote actors. + In the case of a local actor reference, the named actor needs to exist before the + lookup, or else the acquired reference will be an :class:`EmptyLocalActorRef`. + This will be true even if an actor with that exact path is created after acquiring + the actor reference. For remote actor references acquired with `actorFor` the + behaviour is different and sending messages to such a reference will under the hood + look up the actor by path on the remote system for every message send. + +Messages and immutability +========================= + +**IMPORTANT**: Messages can be any kind of object but have to be +immutable. Akka can’t enforce immutability (yet) so this has to be by +convention. + +Here is an example of an immutable message: + +.. includecode:: code/docs/actor/ImmutableMessage.java#immutable-message + +Send messages +============= + +Messages are sent to an Actor through one of the following methods. + +* ``tell`` means “fire-and-forget”, e.g. send a message asynchronously and return + immediately. +* ``ask`` sends a message asynchronously and returns a :class:`Future` + representing a possible reply. + +Message ordering is guaranteed on a per-sender basis. + +.. note:: + + There are performance implications of using ``ask`` since something needs to + keep track of when it times out, there needs to be something that bridges + a ``Promise`` into an ``ActorRef`` and it also needs to be reachable through + remoting. So always prefer ``tell`` for performance, and only ``ask`` if you must. + +In all these methods you have the option of passing along your own ``ActorRef``. +Make it a practice of doing so because it will allow the receiver actors to be able to respond +to your message, since the sender reference is sent along with the message. + +.. _actors-tell-sender-lambda: + +Tell: Fire-forget +----------------- + +This is the preferred way of sending messages. No blocking waiting for a +message. This gives the best concurrency and scalability characteristics. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#tell + +The sender reference is passed along with the message and available within the +receiving actor via its :meth:`sender()` method while processing this +message. Inside of an actor it is usually :meth:`self()` who shall be the +sender, but there can be cases where replies shall be routed to some other +actor—e.g. the parent—in which the second argument to :meth:`tell` would be a +different one. Outside of an actor and if no reply is needed the second +argument can be ``null``; if a reply is needed outside of an actor you can use +the ask-pattern described next.. + +Ask: Send-And-Receive-Future +---------------------------- + +The ``ask`` pattern involves actors as well as futures, hence it is offered as +a use pattern rather than a method on :class:`ActorRef`: + +.. includecode:: code/docs/actor/UntypedActorDocTest.java#import-ask +.. includecode:: code/docs/actor/UntypedActorDocTest.java#ask-pipe + +This example demonstrates ``ask`` together with the ``pipe`` pattern on +futures, because this is likely to be a common combination. Please note that +all of the above is completely non-blocking and asynchronous: ``ask`` produces +a :class:`Future`, two of which are composed into a new future using the +:meth:`Futures.sequence` and :meth:`map` methods and then ``pipe`` installs +an ``onComplete``-handler on the future to effect the submission of the +aggregated :class:`Result` to another actor. + +Using ``ask`` will send a message to the receiving Actor as with ``tell``, and +the receiving actor must reply with ``sender().tell(reply, self())`` in order to +complete the returned :class:`Future` with a value. The ``ask`` operation +involves creating an internal actor for handling this reply, which needs to +have a timeout after which it is destroyed in order not to leak resources; see +more below. + +.. warning:: + + To complete the future with an exception you need send a Failure message to the sender. + This is *not done automatically* when an actor throws an exception while processing a message. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#reply-exception + +If the actor does not complete the future, it will expire after the timeout period, +specified as parameter to the ``ask`` method; this will complete the +:class:`Future` with an :class:`AskTimeoutException`. + +See :ref:`futures-java` for more information on how to await or query a +future. + +The ``onComplete``, ``onSuccess``, or ``onFailure`` methods of the ``Future`` can be +used to register a callback to get a notification when the Future completes. +Gives you a way to avoid blocking. + +.. warning:: + + When using future callbacks, inside actors you need to carefully avoid closing over + the containing actor’s reference, i.e. do not call methods or access mutable state + on the enclosing actor from within the callback. This would break the actor + encapsulation and may introduce synchronization bugs and race conditions because + the callback will be scheduled concurrently to the enclosing actor. Unfortunately + there is not yet a way to detect these illegal accesses at compile time. See also: + :ref:`jmm-shared-state` + +Forward message +--------------- + +You can forward a message from one actor to another. This means that the +original sender address/reference is maintained even though the message is going +through a 'mediator'. This can be useful when writing actors that work as +routers, load-balancers, replicators etc. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#forward + +Receive messages +================ + +An Actor has to implement the ``receive`` method to receive messages: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#receive + +The :meth:`receive` method should define a series of match statements (which has the type +``PartialFunction``) that defines which messages your Actor can handle, +along with the implementation of how the messages should be processed. + +Don't let the type signature scare you. To allow you to easily build up a partial +function there is a builder named ``ReceiveBuilder`` that you can use. + +Here is an example: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java + :include: imports,my-actor + +.. _LambdaActor.Reply: + +Reply to messages +================= + +If you want to have a handle for replying to a message, you can use +``sender()``, which gives you an ActorRef. You can reply by sending to +that ActorRef with ``sender().tell(replyMsg, self())``. You can also store the ActorRef +for replying later, or passing on to other actors. If there is no sender (a +message was sent without an actor or future context) then the sender +defaults to a 'dead-letter' actor ref. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java#reply + + +Receive timeout +=============== + +The `ActorContext` :meth:`setReceiveTimeout` defines the inactivity timeout after which +the sending of a `ReceiveTimeout` message is triggered. +When specified, the receive function should be able to handle an `akka.actor.ReceiveTimeout` message. +1 millisecond is the minimum supported timeout. + +Please note that the receive timeout might fire and enqueue the `ReceiveTimeout` message right after +another message was enqueued; hence it is **not guaranteed** that upon reception of the receive +timeout there must have been an idle period beforehand as configured via this method. + +Once set, the receive timeout stays in effect (i.e. continues firing repeatedly after inactivity +periods). Pass in `Duration.Undefined` to switch off this feature. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#receive-timeout + +.. _stopping-actors-lambda: + +Stopping actors +=============== + +Actors are stopped by invoking the :meth:`stop` method of a ``ActorRefFactory``, +i.e. ``ActorContext`` or ``ActorSystem``. Typically the context is used for stopping +child actors and the system for stopping top level actors. The actual termination of +the actor is performed asynchronously, i.e. :meth:`stop` may return before the actor is +stopped. + +Processing of the current message, if any, will continue before the actor is stopped, +but additional messages in the mailbox will not be processed. By default these +messages are sent to the :obj:`deadLetters` of the :obj:`ActorSystem`, but that +depends on the mailbox implementation. + +Termination of an actor proceeds in two steps: first the actor suspends its +mailbox processing and sends a stop command to all its children, then it keeps +processing the internal termination notifications from its children until the last one is +gone, finally terminating itself (invoking :meth:`postStop`, dumping mailbox, +publishing :class:`Terminated` on the :ref:`DeathWatch `, telling +its supervisor). This procedure ensures that actor system sub-trees terminate +in an orderly fashion, propagating the stop command to the leaves and +collecting their confirmation back to the stopped supervisor. If one of the +actors does not respond (i.e. processing a message for extended periods of time +and therefore not receiving the stop command), this whole process will be +stuck. + +Upon :meth:`ActorSystem.shutdown()`, the system guardian actors will be +stopped, and the aforementioned process will ensure proper termination of the +whole system. + +The :meth:`postStop()` hook is invoked after an actor is fully stopped. This +enables cleaning up of resources: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#postStop + :exclude: clean-up-some-resources + +.. note:: + + Since stopping an actor is asynchronous, you cannot immediately reuse the + name of the child you just stopped; this will result in an + :class:`InvalidActorNameException`. Instead, :meth:`watch()` the terminating + actor and create its replacement in response to the :class:`Terminated` + message which will eventually arrive. + +.. _poison-pill-lambda: + +PoisonPill +---------- + +You can also send an actor the ``akka.actor.PoisonPill`` message, which will +stop the actor when the message is processed. ``PoisonPill`` is enqueued as +ordinary messages and will be handled after messages that were already queued +in the mailbox. + +Graceful Stop +------------- + +:meth:`gracefulStop` is useful if you need to wait for termination or compose ordered +termination of several actors: + +.. includecode:: code/docs/actor/UntypedActorDocTest.java#import-gracefulStop + +.. includecode:: code/docs/actor/UntypedActorDocTest.java#gracefulStop + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#gracefulStop-actor + +When ``gracefulStop()`` returns successfully, the actor’s ``postStop()`` hook +will have been executed: there exists a happens-before edge between the end of +``postStop()`` and the return of ``gracefulStop()``. + +In the above example a custom ``Manager.Shutdown`` message is sent to the target +actor to initiate the process of stopping the actor. You can use ``PoisonPill`` for +this, but then you have limited possibilities to perform interactions with other actors +before stopping the target actor. Simple cleanup tasks can be handled in ``postStop``. + +.. warning:: + + Keep in mind that an actor stopping and its name being deregistered are + separate events which happen asynchronously from each other. Therefore it may + be that you will find the name still in use after ``gracefulStop()`` + returned. In order to guarantee proper deregistration, only reuse names from + within a supervisor you control and only in response to a :class:`Terminated` + message, i.e. not for top-level actors. + +.. _actor-hotswap-lambda: + +Become/Unbecome +=============== + +Upgrade +------- + +Akka supports hotswapping the Actor’s message loop (e.g. its implementation) at +runtime: invoke the ``context.become`` method from within the Actor. +:meth:`become` takes a ``PartialFunction`` that implements the new +message handler. The hotswapped code is kept in a Stack which can be pushed and +popped. + +.. warning:: + + Please note that the actor will revert to its original behavior when restarted by its Supervisor. + +To hotswap the Actor behavior using ``become``: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#hot-swap-actor + +This variant of the :meth:`become` method is useful for many different things, +such as to implement a Finite State Machine (FSM, for an example see `Dining +Hakkers`_). It will replace the current behavior (i.e. the top of the behavior +stack), which means that you do not use :meth:`unbecome`, instead always the +next behavior is explicitly installed. + +.. _Dining Hakkers: http://typesafe.com/activator/template/akka-sample-fsm-java-lambda + +The other way of using :meth:`become` does not replace but add to the top of +the behavior stack. In this case care must be taken to ensure that the number +of “pop” operations (i.e. :meth:`unbecome`) matches the number of “push” ones +in the long run, otherwise this amounts to a memory leak (which is why this +behavior is not the default). + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#swapper + + +Stash +===== + +Stash +===== + +The ``AbstractActorWithStash`` class enables an actor to temporarily stash away messages +that can not or should not be handled using the actor's current +behavior. Upon changing the actor's message handler, i.e., right +before invoking ``context().become()`` or ``context().unbecome()``, all +stashed messages can be "unstashed", thereby prepending them to the actor's +mailbox. This way, the stashed messages can be processed in the same +order as they have been received originally. An actor that extends +``AbstractActorWithStash`` will automatically get a deque-based mailbox. + +.. note:: + + The abstract class ``AbstractActorWithStash`` implements the marker + interface ``RequiresMessageQueue`` + which requests the system to automatically choose a deque based + mailbox implementation for the actor. If you want more + control over the mailbox, see the documentation on mailboxes: :ref:`mailboxes-java`. + +Here is an example of the ``AbstractActorWithStash`` class in action: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/ActorDocTest.java#stash + +Invoking ``stash()`` adds the current message (the message that the +actor received last) to the actor's stash. It is typically invoked +when handling the default case in the actor's message handler to stash +messages that aren't handled by the other cases. It is illegal to +stash the same message twice; to do so results in an +``IllegalStateException`` being thrown. The stash may also be bounded +in which case invoking ``stash()`` may lead to a capacity violation, +which results in a ``StashOverflowException``. The capacity of the +stash can be configured using the ``stash-capacity`` setting (an ``Int``) of the +mailbox's configuration. + +Invoking ``unstashAll()`` enqueues messages from the stash to the +actor's mailbox until the capacity of the mailbox (if any) has been +reached (note that messages from the stash are prepended to the +mailbox). In case a bounded mailbox overflows, a +``MessageQueueAppendFailedException`` is thrown. +The stash is guaranteed to be empty after calling ``unstashAll()``. + +The stash is backed by a ``scala.collection.immutable.Vector``. As a +result, even a very large number of messages may be stashed without a +major impact on performance. + +Note that the stash is part of the ephemeral actor state, unlike the +mailbox. Therefore, it should be managed like other parts of the +actor's state which have the same property. The :class:`AbstractActorWithStash` +implementation of :meth:`preRestart` will call ``unstashAll()``, which is +usually the desired behavior. + +.. note:: + + If you want to enforce that your actor can only work with an unbounded stash, + then you should use the ``AbstractActorWithUnboundedStash`` class instead. + + +.. _killing-actors-lambda: + +Killing an Actor +================ + +You can kill an actor by sending a ``Kill`` message. This will cause the actor +to throw a :class:`ActorKilledException`, triggering a failure. The actor will +suspend operation and its supervisor will be asked how to handle the failure, +which may mean resuming the actor, restarting it or terminating it completely. +See :ref:`supervision-directives` for more information. + +Use ``Kill`` like this: + +.. includecode:: code/docs/actor/UntypedActorDocTest.java + :include: kill + +Actors and exceptions +===================== + +It can happen that while a message is being processed by an actor, that some +kind of exception is thrown, e.g. a database exception. + +What happens to the Message +--------------------------- + +If an exception is thrown while a message is being processed (i.e. taken out of +its mailbox and handed over to the current behavior), then this message will be +lost. It is important to understand that it is not put back on the mailbox. So +if you want to retry processing of a message, you need to deal with it yourself +by catching the exception and retry your flow. Make sure that you put a bound +on the number of retries since you don't want a system to livelock (so +consuming a lot of cpu cycles without making progress). Another possibility +would be to have a look at the :ref:`PeekMailbox pattern `. + +What happens to the mailbox +--------------------------- + +If an exception is thrown while a message is being processed, nothing happens to +the mailbox. If the actor is restarted, the same mailbox will be there. So all +messages on that mailbox will be there as well. + +What happens to the actor +------------------------- + +If code within an actor throws an exception, that actor is suspended and the +supervision process is started (see :ref:`supervision`). Depending on the +supervisor’s decision the actor is resumed (as if nothing happened), restarted +(wiping out its internal state and starting from scratch) or terminated. + +Initialization patterns +======================= + +The rich lifecycle hooks of Actors provide a useful toolkit to implement various initialization patterns. During the +lifetime of an ``ActorRef``, an actor can potentially go through several restarts, where the old instance is replaced by +a fresh one, invisibly to the outside observer who only sees the ``ActorRef``. + +One may think about the new instances as "incarnations". Initialization might be necessary for every incarnation +of an actor, but sometimes one needs initialization to happen only at the birth of the first instance when the +``ActorRef`` is created. The following sections provide patterns for different initialization needs. + +Initialization via constructor +------------------------------ + +Using the constructor for initialization has various benefits. First of all, it makes it possible to use ``val`` fields to store +any state that does not change during the life of the actor instance, making the implementation of the actor more robust. +The constructor is invoked for every incarnation of the actor, therefore the internals of the actor can always assume +that proper initialization happened. This is also the drawback of this approach, as there are cases when one would +like to avoid reinitializing internals on restart. For example, it is often useful to preserve child actors across +restarts. The following section provides a pattern for this case. + +Initialization via preStart +--------------------------- + +The method ``preStart()`` of an actor is only called once directly during the initialization of the first instance, that +is, at creation of its ``ActorRef``. In the case of restarts, ``preStart()`` is called from ``postRestart()``, therefore +if not overridden, ``preStart()`` is called on every incarnation. However, overriding ``postRestart()`` one can disable +this behavior, and ensure that there is only one call to ``preStart()``. + +One useful usage of this pattern is to disable creation of new ``ActorRefs`` for children during restarts. This can be +achieved by overriding ``preRestart()``: + +.. includecode:: code/docs/actor/InitializationDocSpecJava.java#preStartInit + +Please note, that the child actors are *still restarted*, but no new ``ActorRef`` is created. One can recursively apply +the same principles for the children, ensuring that their ``preStart()`` method is called only at the creation of their +refs. + +For more information see :ref:`supervision-restart`. + +Initialization via message passing +---------------------------------- + +There are cases when it is impossible to pass all the information needed for actor initialization in the constructor, +for example in the presence of circular dependencies. In this case the actor should listen for an initialization message, +and use ``become()`` or a finite state-machine state transition to encode the initialized and uninitialized states +of the actor. + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/InitializationDocTest.java#messageInit + +If the actor may receive messages before it has been initialized, a useful tool can be the ``Stash`` to save messages +until the initialization finishes, and replaying them after the actor became initialized. + +.. warning:: + + This pattern should be used with care, and applied only when none of the patterns above are applicable. One of + the potential issues is that messages might be lost when sent to remote actors. Also, publishing an ``ActorRef`` in + an uninitialized state might lead to the condition that it receives a user message before the initialization has been + done. + +.. _actor-performance-lambda: + +Lambdas and Performance +======================= + +There is one big difference between the optimized partial functions created by the Scala compiler and the ones created by the +``ReceiveBuilder``. The partial functions created by the ``ReceiveBuilder`` consist of multiple lambda expressions for every match +statement, where each lambda is an object referencing the code to be run. This is something that the JVM can have problems +optimizing and the resulting code might not be as performant as the Scala equivalent or the corresponding +:ref:`untyped actor ` version. diff --git a/akka-docs/rst/java/lambda-fsm.rst b/akka-docs/rst/java/lambda-fsm.rst index 5c3bb4cf1b..161744c767 100644 --- a/akka-docs/rst/java/lambda-fsm.rst +++ b/akka-docs/rst/java/lambda-fsm.rst @@ -1,7 +1,7 @@ -.. _lambda-fsm-java: +.. _lambda-fsm: ################################ -FSM (Java 8 with Lambda Support) + FSM (Java with Lambda Support) ################################ @@ -30,11 +30,11 @@ send them on after the burst ended or a flush request is received. First, consider all of the below to use these import statements: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#simple-imports +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java#simple-imports The contract of our “Buncher” actor is that it accepts or produces the following messages: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Events.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Events.java :include: simple-events :exclude: boilerplate @@ -42,11 +42,10 @@ The contract of our “Buncher” actor is that it accepts or produces the follo ``Batches`` to be passed on; ``Queue`` will add to the internal queue while ``Flush`` will mark the end of a burst. - The actor can be in two states: no message queued (aka ``Idle``) or some message queued (aka ``Active``). The states and the state data is defined like this: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java :include: simple-state :exclude: boilerplate @@ -57,9 +56,9 @@ reference to send the batches to and the actual queue of messages. Now let’s take a look at the skeleton for our FSM actor: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java :include: simple-fsm - :exclude: transition-elided,unhandled-elided,termination-elided + :exclude: transition-elided,unhandled-elided The basic strategy is to declare the actor, by inheriting the :class:`AbstractFSM` class and specifying the possible states and data values as type parameters. Within @@ -86,7 +85,7 @@ shall work identically in both states, we make use of the fact that any event which is not handled by the ``when()`` block is passed to the ``whenUnhandled()`` block: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#unhandled-elided +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java#unhandled-elided The first case handled here is adding ``Queue()`` requests to the internal queue and going to the ``Active`` state (this does the obvious thing of staying @@ -100,17 +99,18 @@ target, for which we use the ``onTransition`` mechanism: you can declare multiple such blocks and all of them will be tried for matching behavior in case a state transition occurs (i.e. only when the state actually changes). -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#transition-elided +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java#transition-elided The transition callback is a partial function which takes as input a pair of states—the current and the next state. During the state change, the old state data is available via ``stateData`` as shown, and the new state data would be available as ``nextStateData``. -TODO ----- +To verify that this buncher actually works, it is quite easy to write a test +using the :ref:`akka-testkit`, here using JUnit as an example: -Add the test here +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/BuncherTest.java + :include: test-code Reference ========= @@ -121,7 +121,7 @@ The AbstractFSM Class The :class:`AbstractFSM` abstract class is the base class used to implement an FSM. It implements Actor since an Actor is created to drive the FSM. -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java :include: simple-fsm :exclude: fsm-body @@ -170,9 +170,10 @@ may be changed during action processing with e.g. via external message. The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`, -which is conveniently given using state function builder syntax as demonstrated below: +which is conveniently given using the state function builder syntax as +demonstrated below: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java :include: when-syntax .. warning:: @@ -184,14 +185,14 @@ It is recommended practice to declare the states as an enum and then verify that ``when`` clause for each of the states. If you want to leave the handling of a state “unhandled” (more below), it still needs to be declared like this: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#NullFunction +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java#NullFunction Defining the Initial State -------------------------- Each FSM needs a starting point, which is declared using - :func:`startWith(state, data[, timeout]);` + :func:`startWith(state, data[, timeout])` The optionally given timeout argument overrides any specification given for the desired initial state. If you want to cancel a default timeout, use @@ -204,7 +205,7 @@ If a state doesn't handle a received event a warning is logged. If you want to do something else in this case you can specify that with :func:`whenUnhandled(stateFunction)`: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java :include: unhandled-syntax Within this handler the state of the FSM may be queried using the @@ -246,9 +247,9 @@ of the modifiers described in the following: This modifier sends a reply to the currently processed message and otherwise does not modify the state transition. -All modifier can be chained to achieve a nice and concise description: +All modifiers can be chained to achieve a nice and concise description: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java :include: modifier-syntax The parentheses are not actually needed in all cases, but they visually @@ -285,14 +286,15 @@ The handler is a partial function which takes a pair of states as input; no resulting state is needed as it is not possible to modify the transition in progress. -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java :include: transition-syntax -The convenience extractor :obj:`->` enables decomposition of the pair of states -with a clear visual reminder of the transition's direction. As usual in pattern -matches, an underscore may be used for irrelevant parts; alternatively you -could bind the unconstrained state to a variable, e.g. for logging as shown in -the last case. +It is also possible to pass a function object accepting two states to +:func:`onTransition`, in case your transition handling logic is implemented as +a method: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java + :include: alt-transition-syntax The handlers registered with this method are stacked, so you can intersperse :func:`onTransition` blocks with :func:`when` blocks as suits your design. It @@ -367,14 +369,14 @@ state data which is available during termination handling. the same way as a state transition (but note that the ``return`` statement may not be used within a :meth:`when` block). -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java :include: stop-syntax You can use :func:`onTermination(handler)` to specify custom code that is executed when the FSM is stopped. The handler is a partial function which takes a :class:`StopEvent(reason, stateName, stateData)` as argument: -.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java :include: termination-syntax As for the :func:`whenUnhandled` case, this handler is not stacked, so each @@ -398,5 +400,57 @@ implementation by the :class:`AbstractFSM` class is to execute the Testing and Debugging Finite State Machines =========================================== -TODO ----- \ No newline at end of file +During development and for trouble shooting FSMs need care just as any other +actor. There are specialized tools available as described in :ref:`TestFSMRef` +and in the following. + +Event Tracing +------------- + +The setting ``akka.actor.debug.fsm`` in :ref:`configuration` enables logging of an +event trace by :class:`LoggingFSM` instances: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java + :include: logging-fsm + :exclude: body-elided + +This FSM will log at DEBUG level: + + * all processed events, including :obj:`StateTimeout` and scheduled timer + messages + * every setting and cancellation of named timers + * all state transitions + +Life cycle changes and special messages can be logged as described for +:ref:`Actors `. + +Rolling Event Log +----------------- + +The :class:`AbstractLoggingFSM` class adds one more feature to the FSM: a rolling event +log which may be used during debugging (for tracing how the FSM entered a +certain failure state) or for other creative uses: + +.. includecode:: ../../../akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java + :include: logging-fsm + +The :meth:`logDepth` defaults to zero, which turns off the event log. + +.. warning:: + + The log buffer is allocated during actor creation, which is why the + configuration is done using a virtual method call. If you want to override + with a ``val``, make sure that its initialization happens before the + initializer of :class:`LoggingFSM` runs, and do not change the value returned + by ``logDepth`` after the buffer has been allocated. + +The contents of the event log are available using method :meth:`getLog`, which +returns an :class:`IndexedSeq[LogEntry]` where the oldest entry is at index +zero. + +Examples +======== + +A bigger FSM example contrasted with Actor's :meth:`become`/:meth:`unbecome` can be found in +the `Typesafe Activator `_ template named +`Akka FSM in Scala `_ diff --git a/akka-docs/rst/java/untyped-actors.rst b/akka-docs/rst/java/untyped-actors.rst index ed6b2299a1..3fa1b8754b 100644 --- a/akka-docs/rst/java/untyped-actors.rst +++ b/akka-docs/rst/java/untyped-actors.rst @@ -149,8 +149,8 @@ __ Props_ singleton scope. Techniques for dependency injection and integration with dependency injection frameworks -are described in more depth in the -`Using Akka with Dependency Injection `_ +are described in more depth in the +`Using Akka with Dependency Injection `_ guideline and the `Akka Java Spring `_ tutorial in Typesafe Activator. @@ -185,11 +185,11 @@ to have them converted into actual Debug messages). In addition, it offers: -* :obj:`getSelf()` reference to the :class:`ActorRef` of the actor +* :meth:`getSelf()` reference to the :class:`ActorRef` of the actor -* :obj:`getSender()` reference sender Actor of the last received message, typically used as described in :ref:`UntypedActor.Reply` +* :meth:`getSender()` reference sender Actor of the last received message, typically used as described in :ref:`UntypedActor.Reply` -* :obj:`supervisorStrategy()` user overridable definition the strategy to use for supervising child actors +* :meth:`supervisorStrategy()` user overridable definition the strategy to use for supervising child actors This strategy is typically declared inside the actor in order to have access to the actor’s internal state within the decider function: since failure is @@ -200,7 +200,7 @@ In addition, it offers: occurred within a distant descendant it is still reported one level up at a time). -* :obj:`getContext()` exposes contextual information for the actor and the current message, such as: +* :meth:`getContext()` exposes contextual information for the actor and the current message, such as: * factory methods to create child actors (:meth:`actorOf`) * system that the actor belongs to @@ -250,7 +250,7 @@ occupying it. ``ActorSelection`` cannot be watched for this reason. It is possible to resolve the current incarnation's ``ActorRef`` living under the path by sending an ``Identify`` message to the ``ActorSelection`` which will be replied to with an ``ActorIdentity`` containing the correct reference -(see :ref:`actorSelection-java`). This can also be done with the ``resolveOne`` +(see :ref:`actorSelection-java`). This can also be done with the ``resolveOne`` method of the :class:`ActorSelection`, which returns a ``Future`` of the matching :class:`ActorRef`. @@ -273,8 +273,8 @@ the whole functionality): It should be noted that the :class:`Terminated` message is generated independent of the order in which registration and termination occur. -In particular, the watching actor will receive a :class:`Terminated` message even if the -watched actor has already been terminated at the time of registration. +In particular, the watching actor will receive a :class:`Terminated` message +even if the watched actor has already been terminated at the time of registration. Registering multiple times does not necessarily lead to multiple messages being generated, but there is no guarantee that only exactly one such message is @@ -402,12 +402,12 @@ actors which are traversed in the sense that if a concrete name lookup fails negative result is generated. Please note that this does not mean that delivery of that reply is guaranteed, it still is a normal message. -.. includecode:: code/docs/actor/UntypedActorDocTest.java - :include: import-identify,identify +.. includecode:: code/docs/actor/UntypedActorDocTest.java#import-identify +.. includecode:: code/docs/actor/UntypedActorDocTest.java#identify You can also acquire an :class:`ActorRef` for an :class:`ActorSelection` with -the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` -of the matching :class:`ActorRef` if such an actor exists. It is completed with +the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` +of the matching :class:`ActorRef` if such an actor exists. It is completed with failure [[akka.actor.ActorNotFound]] if no such actor exists or the identification didn't complete within the supplied `timeout`. @@ -772,7 +772,7 @@ major impact on performance. Note that the stash is part of the ephemeral actor state, unlike the mailbox. Therefore, it should be managed like other parts of the -actor's state which have the same property. The :class:`Stash` trait’s +actor's state which have the same property. The :class:`UntypedActorWithStash` implementation of :meth:`preRestart` will call ``unstashAll()``, which is usually the desired behavior. diff --git a/akka-docs/rst/scala/code/docs/actor/ActorDocSpec.scala b/akka-docs/rst/scala/code/docs/actor/ActorDocSpec.scala index 14a98cab76..02c07ae704 100644 --- a/akka-docs/rst/scala/code/docs/actor/ActorDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/actor/ActorDocSpec.scala @@ -82,23 +82,6 @@ class DemoActorWrapper extends Actor { def receive = Actor.emptyBehavior } -class AnonymousActor extends Actor { - //#anonymous-actor - def receive = { - case m: DoIt => - context.actorOf(Props(new Actor { - def receive = { - case DoIt(msg) => - val replyMsg = doSomeDangerousWork(msg) - sender() ! replyMsg - context.stop(self) - } - def doSomeDangerousWork(msg: ImmutableMessage): String = { "done" } - })) forward m - } - //#anonymous-actor -} - class Hook extends Actor { var child: ActorRef = _ //#preStart @@ -283,15 +266,6 @@ class ActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) { system.stop(myActor) } - "creating actor with constructor" in { - //#creating-constructor - // allows passing in arguments to the MyActor constructor - val myActor = system.actorOf(Props[MyActor], name = "myactor") - //#creating-constructor - - system.stop(myActor) - } - "creating a Props config" in { //#creating-props import akka.actor.Props diff --git a/akka-docs/rst/scala/fsm.rst b/akka-docs/rst/scala/fsm.rst index c6c891e50c..da40dfceb0 100644 --- a/akka-docs/rst/scala/fsm.rst +++ b/akka-docs/rst/scala/fsm.rst @@ -251,7 +251,7 @@ of the modifiers described in the following: This modifier sends a reply to the currently processed message and otherwise does not modify the state transition. -All modifier can be chained to achieve a nice and concise description: +All modifiers can be chained to achieve a nice and concise description: .. includecode:: code/docs/actor/FSMDocSpec.scala :include: modifier-syntax diff --git a/akka-samples/akka-sample-java8/build.sbt b/akka-samples/akka-docs-java-lambda/build.sbt similarity index 89% rename from akka-samples/akka-sample-java8/build.sbt rename to akka-samples/akka-docs-java-lambda/build.sbt index 11a3f028e1..d304633775 100644 --- a/akka-samples/akka-sample-java8/build.sbt +++ b/akka-samples/akka-docs-java-lambda/build.sbt @@ -1,6 +1,6 @@ -name := "akka-sample-java8" +name := "akka-docs-java-lambda" -version := "0.0.1-SNAPSHOT" +version := "2.3-SNAPSHOT" scalaVersion := "2.10.3" diff --git a/akka-samples/akka-sample-java8/pom.xml b/akka-samples/akka-docs-java-lambda/pom.xml similarity index 94% rename from akka-samples/akka-sample-java8/pom.xml rename to akka-samples/akka-docs-java-lambda/pom.xml index 85e566b9ea..f3ab3a8a12 100644 --- a/akka-samples/akka-sample-java8/pom.xml +++ b/akka-samples/akka-docs-java-lambda/pom.xml @@ -1,4 +1,3 @@ - receive() { + return ReceiveBuilder. + matchAny(x -> { + sender().tell(x, self()); + }).build(); + } + //#plus-some-behavior + } + //#context-actorOf + + static public abstract class ReceiveActor extends AbstractActor { + @Override + //#receive + public abstract PartialFunction receive(); + //#receive + } + + static public class ActorWithArgs extends AbstractActor { + private final String args; + + ActorWithArgs(String args) { + this.args = args; + } + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchAny(x -> { }).build(); + } + } + + static + //#props-factory + public class DemoActor extends AbstractActor { + /** + * Create Props for an actor of this type. + * @param magicNumber The magic number to be passed to this actor’s constructor. + * @return a Props for creating this actor, which can then be further configured + * (e.g. calling `.withDispatcher()` on it) + */ + static Props props(Integer magicNumber) { + // You need to specify the actual type of the returned actor + // since Java 8 lambdas have some runtime type information erased + return Props.create(DemoActor.class, () -> new DemoActor(magicNumber)); + } + + private final Integer magicNumber; + + DemoActor(Integer magicNumber) { + this.magicNumber = magicNumber; + } + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + match(Integer.class, i -> { + sender().tell(i + magicNumber, self()); + }).build(); + } + } + + //#props-factory + static + //#props-factory + public class SomeOtherActor extends AbstractActor { + // Props(new DemoActor(42)) would not be safe + ActorRef demoActor = context().actorOf(DemoActor.props(42), "demo"); + // ... + //#props-factory + @Override + public PartialFunction receive() { + return emptyBehavior(); + } + //#props-factory + } + //#props-factory + + public static class Hook extends AbstractActor { + ActorRef target = null; + //#preStart + @Override + public void preStart() { + target = context().actorOf(Props.create(MyActor.class, "target")); + } + //#preStart + public PartialFunction receive() { + return emptyBehavior(); + } + //#postStop + @Override + public void postStop() { + //#clean-up-some-resources + final String message = "stopped"; + //#tell + // don’t forget to think about who is the sender (2nd argument) + target.tell(message, self()); + //#tell + final Object result = ""; + //#forward + target.forward(result, context()); + //#forward + target = null; + //#clean-up-some-resources + } + //#postStop + + // compilation test only + public void compileSelections() { + //#selection-local + // will look up this absolute path + context().actorSelection("/user/serviceA/actor"); + // will look up sibling beneath same supervisor + context().actorSelection("../joe"); + //#selection-local + + //#selection-wildcard + // will look all children to serviceB with names starting with worker + context().actorSelection("/user/serviceB/worker*"); + // will look up all siblings beneath same supervisor + context().actorSelection("../*"); + //#selection-wildcard + + //#selection-remote + context().actorSelection("akka.tcp://app@otherhost:1234/user/serviceB"); + //#selection-remote + } + } + + public static class ReplyException extends AbstractActor { + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchAny(x -> { + //#reply-exception + try { + String result = operation(); + sender().tell(result, self()); + } catch (Exception e) { + sender().tell(new akka.actor.Status.Failure(e), self()); + throw e; + } + //#reply-exception + }).build(); + } + + private String operation() { + return "Hi"; + } + } + + static + //#gracefulStop-actor + public class Manager extends AbstractActor { + private static enum Shutdown { + Shutdown + } + public static final Shutdown SHUTDOWN = Shutdown.Shutdown; + + private ActorRef worker = + context().watch(context().actorOf(Props.create(Cruncher.class), "worker")); + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("job", s -> { + worker.tell("crunch", self()); + }). + matchEquals(SHUTDOWN, x -> { + worker.tell(PoisonPill.getInstance(), self()); + context().become(shuttingDown); + }).build(); + } + + public PartialFunction shuttingDown = + ReceiveBuilder. + matchEquals("job", s -> { + sender().tell("service unavailable, shutting down", self()); + }). + match(Terminated.class, t -> t.actor().equals(worker), t -> { + context().stop(self()); + }).build(); + } + //#gracefulStop-actor + + @Test + public void usePatternsGracefulStop() throws Exception { + ActorRef actorRef = system.actorOf(Props.create(Manager.class)); + //#gracefulStop + try { + Future stopped = + gracefulStop(actorRef, Duration.create(5, TimeUnit.SECONDS), Manager.SHUTDOWN); + Await.result(stopped, Duration.create(6, TimeUnit.SECONDS)); + // the actor has been stopped + } catch (AskTimeoutException e) { + // the actor wasn't stopped within 5 seconds + } + //#gracefulStop + } + + + public static class Cruncher extends AbstractActor { + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("crunch", s -> { }).build(); + } + } + + static + //#swapper + public class Swapper extends AbstractActor { + final LoggingAdapter log = Logging.getLogger(context().system(), this); + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals(Swap, s -> { + log.info("Hi"); + context().become(ReceiveBuilder. + matchEquals(Swap, x -> { + log.info("Ho"); + context().unbecome(); // resets the latest 'become' (just for fun) + }).build(), false); // push on top instead of replace + }).build(); + } + } + + //#swapper + static + //#swapper + public class SwapperApp { + public static void main(String[] args) { + ActorSystem system = ActorSystem.create("SwapperSystem"); + ActorRef swapper = system.actorOf(Props.create(Swapper.class), "swapper"); + swapper.tell(Swap, ActorRef.noSender()); // logs Hi + swapper.tell(Swap, ActorRef.noSender()); // logs Ho + swapper.tell(Swap, ActorRef.noSender()); // logs Hi + swapper.tell(Swap, ActorRef.noSender()); // logs Ho + swapper.tell(Swap, ActorRef.noSender()); // logs Hi + swapper.tell(Swap, ActorRef.noSender()); // logs Ho + system.shutdown(); + } + } + //#swapper + + + @Test + public void creatingActorWithSystemActorOf() { + //#system-actorOf + // ActorSystem is a heavy object: create only one per application + final ActorSystem system = ActorSystem.create("MySystem"); + final ActorRef myActor = system.actorOf(Props.create(MyActor.class), "myactor"); + //#system-actorOf + try { + new JavaTestKit(system) { + { + myActor.tell("hello", getRef()); + expectMsgEquals("hello"); + } + }; + } finally { + JavaTestKit.shutdownActorSystem(system); + } + } + + @Test + public void creatingPropsConfig() { + //#creating-props + Props props1 = Props.create(MyActor.class); + Props props2 = Props.create(ActorWithArgs.class, + () -> new ActorWithArgs("arg")); // careful, see below + Props props3 = Props.create(ActorWithArgs.class, "arg"); + //#creating-props + + //#creating-props-deprecated + // NOT RECOMMENDED within another actor: + // encourages to close over enclosing class + Props props7 = Props.create(ActorWithArgs.class, + () -> new ActorWithArgs("arg")); + //#creating-props-deprecated + } + + @Test(expected=IllegalArgumentException.class) + public void creatingPropsIllegal() { + //#creating-props-illegal + // This will throw an IllegalArgumentException since some runtime + // type information of the lambda is erased. + // Use Props.create(actorClass, Creator) instead. + Props props = Props.create(() -> new ActorWithArgs("arg")); + //#creating-props-illegal + } + + static + //#receive-timeout + public class ReceiveTimeoutActor extends AbstractActor { + public ReceiveTimeoutActor() { + // To set an initial delay + context().setReceiveTimeout(Duration.create("10 seconds")); + } + //#receive-timeout + ActorRef target = context().system().deadLetters(); + //#receive-timeout + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("Hello", s -> { + // To set in a response to a message + context().setReceiveTimeout(Duration.create("1 second")); + //#receive-timeout + target = sender(); + target.tell("Hello world", self()); + //#receive-timeout + }). + match(ReceiveTimeout.class, r -> { + // To turn it off + context().setReceiveTimeout(Duration.Undefined()); + //#receive-timeout + target.tell("timeout", self()); + //#receive-timeout + }).build(); + } + } + //#receive-timeout + + @Test + public void using_receiveTimeout() { + final ActorRef myActor = system.actorOf(Props.create(ReceiveTimeoutActor.class)); + new JavaTestKit(system) { + { + myActor.tell("Hello", getRef()); + expectMsgEquals("Hello world"); + expectMsgEquals("timeout"); + } + }; + } + + static + //#hot-swap-actor + public class HotSwapActor extends AbstractActor { + private PartialFunction angry; + private PartialFunction happy; + + { + angry = + ReceiveBuilder. + matchEquals("foo", s -> { + sender().tell("I am already angry?", self()); + }). + matchEquals("bar", s -> { + context().become(happy); + }).build(); + + happy = ReceiveBuilder. + matchEquals("bar", s -> { + sender().tell("I am already happy :-)", self()); + }). + matchEquals("foo", s -> { + context().become(angry); + }).build(); + } + + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("foo", s -> { + context().become(angry); + }). + matchEquals("bar", s -> { + context().become(happy); + }).build(); + } + } + //#hot-swap-actor + + @Test + public void using_hot_swap() { + final ActorRef actor = system.actorOf(Props.create(HotSwapActor.class), "hot"); + new JavaTestKit(system) { + { + actor.tell("foo", getRef()); + actor.tell("foo", getRef()); + expectMsgEquals("I am already angry?"); + actor.tell("bar", getRef()); + actor.tell("bar", getRef()); + expectMsgEquals("I am already happy :-)"); + actor.tell("foo", getRef()); + actor.tell("foo", getRef()); + expectMsgEquals("I am already angry?"); + expectNoMsg(Duration.create(1, TimeUnit.SECONDS)); + } + }; + } + + + static + //#stash + public class ActorWithProtocol extends AbstractActorWithStash { + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("open", s -> { + context().become(ReceiveBuilder. + matchEquals("write", ws -> { /* do writing */ }). + matchEquals("close", cs -> { + unstashAll(); + context().unbecome(); + }). + matchAny(msg -> stash()).build(), false); + }). + matchAny(msg -> stash()).build(); + } + } + //#stash + + @Test + public void using_Stash() { + final ActorRef actor = system.actorOf(Props.create(ActorWithProtocol.class), "stash"); + } + + static + //#watch + public class WatchActor extends AbstractActor { + private final ActorRef child = context().actorOf(Props.empty(), "target"); + { + context().watch(child); // <-- this is the only call needed for registration + } + private ActorRef lastSender = system.deadLetters(); + + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("kill", s -> { + context().stop(child); + lastSender = sender(); + }). + match(Terminated.class, t -> t.actor().equals(child), t -> { + lastSender.tell("finished", self()); + }).build(); + } + } + //#watch + + @Test + public void using_watch() { + ActorRef actor = system.actorOf(Props.create(WatchActor.class)); + + new JavaTestKit(system) { + { + actor.tell("kill", getRef()); + expectMsgEquals("finished"); + } + }; + } + + static + //#identify + public class Follower extends AbstractActor { + final Integer identifyId = 1; + + { + ActorSelection selection = context().actorSelection("/user/another"); + selection.tell(new Identify(identifyId), self()); + } + + public PartialFunction receive() { + return ReceiveBuilder. + match(ActorIdentity.class, id -> id.getRef() != null, id -> { + ActorRef ref = id.getRef(); + context().watch(ref); + context().become(active(ref)); + }). + match(ActorIdentity.class, id -> id.getRef() == null, id -> { + context().stop(self()); + }).build(); + } + + final PartialFunction active(final ActorRef another) { + return ReceiveBuilder. + match(Terminated.class, t -> t.actor().equals(another), t -> { + context().stop(self()); + }).build(); + } + } + //#identify + + @Test + public void using_Identify() { + ActorRef a = system.actorOf(Props.empty()); + ActorRef b = system.actorOf(Props.create(Follower.class)); + + new JavaTestKit(system) { + { + watch(b); + system.stop(a); + assertEquals(expectMsgClass(Duration.create(2, TimeUnit.SECONDS), Terminated.class).actor(), b); + } + }; + } +} diff --git a/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/InitializationDocTest.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/InitializationDocTest.java new file mode 100644 index 0000000000..ee1d777271 --- /dev/null +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/InitializationDocTest.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor; + +import akka.actor.*; +import akka.japi.pf.ReceiveBuilder; +import akka.testkit.JavaTestKit; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import scala.PartialFunction; +import scala.concurrent.duration.Duration; +import scala.runtime.BoxedUnit; + +import java.util.concurrent.TimeUnit; + +public class InitializationDocTest { + + static ActorSystem system = null; + + @BeforeClass + public static void beforeClass() { + system = ActorSystem.create("InitializationDocTest"); + } + + @AfterClass + public static void afterClass() { + system.shutdown(); + system.awaitTermination(Duration.create("5 seconds")); + } + + public static class MessageInitExample extends AbstractActor { + //#messageInit + private String initializeMe = null; + + @Override + public PartialFunction receive() { + return ReceiveBuilder. + matchEquals("init", m1 -> { + initializeMe = "Up and running"; + context().become(ReceiveBuilder. + matchEquals("U OK?", m2 -> { + sender().tell(initializeMe, self()); + }).build()); + }).build(); + } + //#messageInit + } + + @Test + public void testIt() { + + new JavaTestKit(system) {{ + ActorRef testactor = system.actorOf(Props.create(MessageInitExample.class), "testactor"); + String msg = "U OK?"; + + testactor.tell(msg, getRef()); + expectNoMsg(Duration.create(1, TimeUnit.SECONDS)); + + testactor.tell("init", getRef()); + testactor.tell(msg, getRef()); + expectMsgEquals("Up and running"); + }}; + } +} diff --git a/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/Messages.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/Messages.java new file mode 100644 index 0000000000..150b65d9fc --- /dev/null +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/Messages.java @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Messages { + static + //#immutable-message + public class ImmutableMessage { + private final int sequenceNumber; + private final List values; + + public ImmutableMessage(int sequenceNumber, List values) { + this.sequenceNumber = sequenceNumber; + this.values = Collections.unmodifiableList(new ArrayList(values)); + } + + public int getSequenceNumber() { + return sequenceNumber; + } + + public List getValues() { + return values; + } + } + //#immutable-message + + public static class DoIt { + private final ImmutableMessage msg; + + DoIt(ImmutableMessage msg) { + this.msg = msg; + } + + public ImmutableMessage getMsg() { + return msg; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DoIt doIt = (DoIt) o; + + if (!msg.equals(doIt.msg)) return false; + + return true; + } + + @Override + public int hashCode() { + return msg.hashCode(); + } + + @Override + public String toString() { + return "DoIt{" + + "msg=" + msg + + '}'; + } + } + + public static class Message { + final String str; + + Message(String str) { + this.str = str; + } + + public String getStr() { + return str; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Message message = (Message) o; + + if (!str.equals(message.str)) return false; + + return true; + } + + @Override + public int hashCode() { + return str.hashCode(); + } + + @Override + public String toString() { + return "Message{" + + "str='" + str + '\'' + + '}'; + } + } + + public static enum Swap { + Swap + } + + public static class Result { + final String x; + final String s; + + public Result(String x, String s) { + this.x = x; + this.s = s; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((s == null) ? 0 : s.hashCode()); + result = prime * result + ((x == null) ? 0 : x.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Result other = (Result) obj; + if (s == null) { + if (other.s != null) + return false; + } else if (!s.equals(other.s)) + return false; + if (x == null) { + if (other.x != null) + return false; + } else if (!x.equals(other.x)) + return false; + return true; + } + } +} diff --git a/akka-samples/akka-sample-java8/src/main/java/sample/java8/MyActor.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java similarity index 64% rename from akka-samples/akka-sample-java8/src/main/java/sample/java8/MyActor.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java index 3bd62a6212..b82795f4f3 100644 --- a/akka-samples/akka-sample-java8/src/main/java/sample/java8/MyActor.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/MyActor.java @@ -1,4 +1,8 @@ -package sample.java8; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor; //#imports import akka.actor.AbstractActor; @@ -17,7 +21,14 @@ public class MyActor extends AbstractActor { @Override public PartialFunction receive() { return ReceiveBuilder. - match(String.class, s -> s.equals("test"), s -> log.info("received test")). + match(String.class, s -> { + log.info("Received String message: {}", s); + //#my-actor + //#reply + sender().tell(s, self()); + //#reply + //#my-actor + }). matchAny(o -> log.info("received unknown message")).build(); } } diff --git a/akka-samples/akka-sample-java8/src/main/java/sample/java8/SampleActor.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActor.java similarity index 90% rename from akka-samples/akka-sample-java8/src/main/java/sample/java8/SampleActor.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActor.java index 1d9e7f9ef6..8f607331fb 100644 --- a/akka-samples/akka-sample-java8/src/main/java/sample/java8/SampleActor.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActor.java @@ -1,4 +1,8 @@ -package sample.java8; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor; //#sample-actor import akka.actor.AbstractActor; diff --git a/akka-samples/akka-sample-java8/src/test/java/sample/SampleActorTest.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActorTest.java similarity index 93% rename from akka-samples/akka-sample-java8/src/test/java/sample/SampleActorTest.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActorTest.java index ce037f5057..5dff74f6e7 100644 --- a/akka-samples/akka-sample-java8/src/test/java/sample/SampleActorTest.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/SampleActorTest.java @@ -1,10 +1,13 @@ -package sample; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import akka.testkit.JavaTestKit; -import sample.java8.SampleActor; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; diff --git a/akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java similarity index 73% rename from akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java index 4be0ad2701..af1e7bd6ea 100644 --- a/akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Buncher.java @@ -1,4 +1,8 @@ -package sample.java8.buncher; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor.fsm; //#simple-imports import akka.actor.AbstractFSM; @@ -10,15 +14,14 @@ import java.util.List; import scala.concurrent.duration.Duration; //#simple-imports -import static sample.java8.buncher.Buncher.State; -import static sample.java8.buncher.Buncher.Data; -import static sample.java8.buncher.Buncher.State.*; -import static sample.java8.buncher.Buncher.Uninitialized.*; -import static sample.java8.buncher.Events.*; +import static docs.actor.fsm.Buncher.Data; +import static docs.actor.fsm.Buncher.State.*; +import static docs.actor.fsm.Buncher.State; +import static docs.actor.fsm.Buncher.Uninitialized.*; +import static docs.actor.fsm.Events.*; //#simple-fsm public class Buncher extends AbstractFSM { - { //#fsm-body startWith(Idle, Uninitialized); @@ -26,7 +29,8 @@ public class Buncher extends AbstractFSM { //#when-syntax when(Idle, matchEvent(SetTarget.class, Uninitialized.class, - (setTarget, uninitialized) -> stay().using(new Todo(setTarget.getRef(), new LinkedList<>())))); + (setTarget, uninitialized) -> + stay().using(new Todo(setTarget.getRef(), new LinkedList<>())))); //#when-syntax //#transition-elided @@ -50,25 +54,14 @@ public class Buncher extends AbstractFSM { matchEvent(Queue.class, Todo.class, (queue, todo) -> goTo(Active).using(todo.addElement(queue.getObj()))). anyEvent((event, state) -> { - log().warning("received unhandled request {} in state {}/{}", event, stateName(), state); + log().warning("received unhandled request {} in state {}/{}", + event, stateName(), state); return stay(); })); //#unhandled-elided - //#termination-elided - //#termination-syntax - onTermination( - matchStop(Normal(), - (state, data) -> {/* Do something here */}). - stop(Shutdown(), - (state, data) -> {/* Do something here */}). - stop(Failure(), - (reason, state, data) -> {/* Do something here */})); - //#termination-syntax - //#termination-elided - //#fsm-body - initialize(); + //#fsm-body } //#simple-fsm @@ -141,15 +134,3 @@ public class Buncher extends AbstractFSM { //#simple-fsm } //#simple-fsm - -//#NullFunction -//#unhandled-syntax -//#modifier-syntax -//#transition-syntax -//#stop-syntax -// TODO add code example here -//#stop-syntax -//#transition-syntax -//#modifier-syntax -//#unhandled-syntax -//#NullFunction diff --git a/akka-samples/akka-sample-java8/src/test/java/sample/BuncherTest.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/BuncherTest.java similarity index 61% rename from akka-samples/akka-sample-java8/src/test/java/sample/BuncherTest.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/BuncherTest.java index ad3dab275d..66fc24bc94 100644 --- a/akka-samples/akka-sample-java8/src/test/java/sample/BuncherTest.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/BuncherTest.java @@ -1,4 +1,8 @@ -package sample; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor.fsm; import akka.actor.ActorRef; import akka.actor.ActorSystem; @@ -9,12 +13,13 @@ import org.junit.BeforeClass; import org.junit.Test; import java.util.LinkedList; -import sample.java8.buncher.*; -import static sample.java8.buncher.Events.Batch; -import static sample.java8.buncher.Events.Queue; -import static sample.java8.buncher.Events.SetTarget; -import static sample.java8.buncher.Events.Flush.Flush; +import docs.actor.fsm.*; +import static docs.actor.fsm.Events.Batch; +import static docs.actor.fsm.Events.Queue; +import static docs.actor.fsm.Events.SetTarget; +import static docs.actor.fsm.Events.Flush.Flush; +//#test-code public class BuncherTest { static ActorSystem system; @@ -31,10 +36,11 @@ public class BuncherTest { } @Test - public void testBuncherActor() + public void testBuncherActorBatchesCorrectly() { new JavaTestKit(system) {{ - final ActorRef buncher = system.actorOf(Props.create(Buncher.class), "buncher-actor"); + final ActorRef buncher = + system.actorOf(Props.create(Buncher.class)); final ActorRef probe = getRef(); buncher.tell(new SetTarget(probe), probe); @@ -56,4 +62,19 @@ public class BuncherTest { system.stop(buncher); }}; } + + @Test + public void testBuncherActorDoesntBatchUninitialized() + { + new JavaTestKit(system) {{ + final ActorRef buncher = + system.actorOf(Props.create(Buncher.class)); + final ActorRef probe = getRef(); + + buncher.tell(new Queue(42), probe); + expectNoMsg(); + system.stop(buncher); + }}; + } } +//#test-code diff --git a/akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Events.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Events.java similarity index 94% rename from akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Events.java rename to akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Events.java index 77b13c0b68..a50d18d590 100644 --- a/akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Events.java +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/Events.java @@ -1,4 +1,8 @@ -package sample.java8.buncher; +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor.fsm; import akka.actor.ActorRef; import java.util.List; diff --git a/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java new file mode 100644 index 0000000000..2626982efd --- /dev/null +++ b/akka-samples/akka-docs-java-lambda/src/test/java/docs/actor/fsm/FSMDocTest.java @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package docs.actor.fsm; + +import akka.actor.*; +import akka.testkit.JavaTestKit; +import org.hamcrest.CoreMatchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import scala.concurrent.duration.Duration; + +import static org.junit.Assert.*; + +import static docs.actor.fsm.FSMDocTest.StateType.*; +import static docs.actor.fsm.FSMDocTest.Messages.*; +import static java.util.concurrent.TimeUnit.*; + +public class FSMDocTest { + static ActorSystem system; + + @BeforeClass + public static void setup() { + system = ActorSystem.create("FSMDocTest"); + } + + @AfterClass + public static void tearDown() { + JavaTestKit.shutdownActorSystem(system); + system = null; + } + + public static enum StateType { + SomeState, + Processing, + Idle, + Active, + Error + } + + public static enum Messages { + WillDo, + Tick + } + + public static enum Data { + Foo, + Bar + }; + + public static interface X {}; + + public static class DummyFSM extends AbstractFSM { + Integer newData = 42; + //#alt-transition-syntax + public void handler(StateType from, StateType to) { + // handle transition here + } + + //#alt-transition-syntax + { + //#modifier-syntax + when(SomeState, matchAnyEvent((msg, data) -> { + return goTo(Processing).using(newData). + forMax(Duration.create(5, SECONDS)).replying(WillDo); + })); + //#modifier-syntax + + //#NullFunction + when(SomeState, AbstractFSM.NullFunction()); + //#NullFunction + + //#transition-syntax + onTransition( + matchState(Active, Idle, () -> setTimer("timeout", + Tick, Duration.create(1, SECONDS), true)). + state(Active, null, () -> cancelTimer("timeout")). + state(null, Idle, (f, t) -> log().info("entering Idle from " + f))); + //#transition-syntax + + //#alt-transition-syntax + onTransition(this::handler); + //#alt-transition-syntax + + //#stop-syntax + when(Error, matchEventEquals("stop", (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, null, (x, data) -> { + log().info("Received unhandled event: " + x); + return stay(); + }). + anyEvent((event, data) -> { + log().warning("Received unknown event: " + event); + return goTo(Error); + })); + } + //#unhandled-syntax + } + + static + //#logging-fsm + public class MyFSM extends AbstractLoggingFSM { + //#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(), self()); + target.tell(state, self()); + target.tell(data, self()); + target.tell(lastEvents, self()); + //#logging-fsm + }) + ); + //... + //#logging-fsm + startWith(SomeState, Data.Foo); + when(SomeState, matchEvent(ActorRef.class, Data.class, (ref, data) -> { + target = ref; + target.tell("going active", self()); + return goTo(Active); + })); + when(Active, matchEventEquals("stop", (data) -> { + target.tell("stopping", self()); + 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 JavaTestKit(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); + assertThat(msg, CoreMatchers.startsWith("LogEntry(SomeState,Foo,Actor[akka://FSMDocTest/system/")); + }}; + } +} diff --git a/akka-samples/akka-sample-java8/LICENSE b/akka-samples/akka-sample-java8/LICENSE deleted file mode 100644 index 6c42406c7b..0000000000 --- a/akka-samples/akka-sample-java8/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014 Typesafe, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/scripts/build/extra-build-steps.sh b/scripts/build/extra-build-steps.sh index 161c7fab58..dc9e90051c 100755 --- a/scripts/build/extra-build-steps.sh +++ b/scripts/build/extra-build-steps.sh @@ -88,7 +88,7 @@ check "$java8_path" check mvn # now do some work -tmp="$script_dir/../../akka-samples/akka-sample-java8" +tmp="$script_dir/../../akka-samples/akka-docs-java-lambda" try cd "$tmp" "can't step into project directory: $tmp" export JAVA_HOME="$java8_home" -try mvn clean test "mvn execution in akka-sample-java8 failed" +try mvn clean test "mvn execution in akka-docs-java-lambda failed"