From 022082ebbfdf2f29cd6a7fd4cf056313715e2845 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 7 Mar 2024 21:08:45 -0300 Subject: [PATCH 01/19] Initial implementatio of new api --- .../api/actors/ActionArgumentFunction.java | 9 ++ .../spawn/api/actors/ActionEmptyFunction.java | 3 + .../api/actors/ActionNoArgumentFunction.java | 7 ++ .../eigr/spawn/api/actors/ActorBehavior.java | 84 +++++++++++++++++++ .../api/actors/StatefulActorBehavior.java | 13 +++ .../api/actors/StatelessActorBehavior.java | 6 ++ .../eigr/spawn/ActorBehaviorTestEntity.java | 30 +++++++ 7 files changed, 152 insertions(+) create mode 100644 src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java create mode 100644 src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java b/src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java new file mode 100644 index 0000000..c2d9361 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java @@ -0,0 +1,9 @@ +package io.eigr.spawn.api.actors; + +import com.google.protobuf.MessageOrBuilder; + +@FunctionalInterface +public interface ActionArgumentFunction extends ActionEmptyFunction { + + Value handle(ActorContext context, A argument); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java b/src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java new file mode 100644 index 0000000..3288508 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java @@ -0,0 +1,3 @@ +package io.eigr.spawn.api.actors; + +public interface ActionEmptyFunction {} diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java b/src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java new file mode 100644 index 0000000..68a5e75 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java @@ -0,0 +1,7 @@ +package io.eigr.spawn.api.actors; + +@FunctionalInterface +public interface ActionNoArgumentFunction extends ActionEmptyFunction { + + Value handle(ActorContext context); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java new file mode 100644 index 0000000..706606e --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java @@ -0,0 +1,84 @@ +package io.eigr.spawn.api.actors; + +import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.MessageOrBuilder; +import io.eigr.spawn.api.exceptions.ActorNotFoundException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Stream; + +public class ActorBehavior { + public interface ActorOption extends Consumer {} + + private String name; + private String system; + + Class stateType; + + private long deactivatedTimeout = 60000; + + private long snapshotTimeout = 50000; + + private Map actions = new HashMap<>(); + + public ActorBehavior(ActorOption... options){ + Optional.ofNullable(options) + .map(Stream::of) + .orElseGet(Stream::empty) + .forEach(option -> option.accept(this)); + } + + public Class getStateType() { + return this.stateType; + } + + public void setStateType(Class stateType) { + this.stateType = stateType; + } + + public static ActorOption name(String actorName) { + return model -> model.name = actorName; + } + + public static ActorOption system(String actorSystem) { + return model -> model.system = actorSystem; + } + + public static ActorOption deactivated(long timeout) { + return model -> model.deactivatedTimeout = timeout; + } + + public static ActorOption snapshot(long timeout) { + return model -> model.snapshotTimeout = timeout; + } + + public static ActorOption action(String name, ActionNoArgumentFunction action) { + return model -> model.actions.put(name, action); + } + + public static ActorOption action(String name, ActionArgumentFunction action) { + return model -> model.actions.put(name, action); + } + + public Value call(String action, ActorContext context) throws ActorNotFoundException { + if (this.actions.containsKey(action)) { + ((ActionNoArgumentFunction) this.actions.get(action)).handle(context); + } + + throw new ActorNotFoundException( + String.format("Action [%s] not found for Actor [%s::%s]", action, system, name)); + } + + public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { + if (this.actions.containsKey(action)) { + ((ActionArgumentFunction) this.actions.get(action)).handle(context, argument); + } + + throw new ActorNotFoundException( + String.format("Action [%s] not found for Actor [%s::%s]", action, system, name)); + } + +} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java new file mode 100644 index 0000000..18023e4 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java @@ -0,0 +1,13 @@ +package io.eigr.spawn.api.actors; + +import java.lang.reflect.ParameterizedType; + +public abstract class StatefulActorBehavior { + + + public Class getStateType() { + return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } + + public abstract ActorBehavior setup(); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java new file mode 100644 index 0000000..9af228e --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java @@ -0,0 +1,6 @@ +package io.eigr.spawn.api.actors; + +public abstract class StatelessActorBehavior { + + public abstract ActorBehavior setup(); +} diff --git a/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java b/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java new file mode 100644 index 0000000..5f157b8 --- /dev/null +++ b/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java @@ -0,0 +1,30 @@ +package io.eigr.spawn; + +import io.eigr.spawn.api.actors.ActorBehavior; +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatelessActorBehavior; +import io.eigr.spawn.api.actors.Value; + +import io.eigr.spawn.java.test.domain.Actor.*; + +import static io.eigr.spawn.api.actors.ActorBehavior.*; + +public class ActorBehaviorTestEntity extends StatelessActorBehavior { + + @Override + public ActorBehavior setup() { + return new ActorBehavior( + name("test"), + system("spawn-system"), + deactivated(30000), + snapshot(10000), + action("hi", (ctx) -> Value.at().noReply()), + action("SayHello", this::sayHello), + action("SayHelloTwo", (ctx, arg) -> Value.at().noReply()) + ); + } + + public Value sayHello(ActorContext context, Request req) { + return Value.at().noReply(); + } +} From c5b6dc9aac026cda550d4a4a179b61c45723f4d7 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Fri, 8 Mar 2024 18:05:51 -0300 Subject: [PATCH 02/19] Adjusts --- .../eigr/spawn/api/actors/ActorBehavior.java | 44 ++++++++++++++----- .../eigr/spawn/api/actors/StatefulActor.java | 13 ++++++ .../api/actors/StatefulActorBehavior.java | 13 ------ .../eigr/spawn/api/actors/StatelessActor.java | 6 +++ .../api/actors/StatelessActorBehavior.java | 6 --- .../ActionArgumentFunction.java | 4 +- .../ActionEmptyFunction.java | 2 +- .../ActionNoArgumentFunction.java | 5 ++- .../eigr/spawn/ActorBehaviorTestEntity.java | 30 ------------- .../java/io/eigr/spawn/MyTestActorEntity.java | 39 ++++++++++++++++ 10 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 src/main/java/io/eigr/spawn/api/actors/StatefulActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/StatelessActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java rename src/main/java/io/eigr/spawn/{api/actors => internal}/ActionArgumentFunction.java (65%) rename src/main/java/io/eigr/spawn/{api/actors => internal}/ActionEmptyFunction.java (54%) rename src/main/java/io/eigr/spawn/{api/actors => internal}/ActionNoArgumentFunction.java (53%) delete mode 100644 src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java create mode 100644 src/test/java/io/eigr/spawn/MyTestActorEntity.java diff --git a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java index 706606e..642d829 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java @@ -3,6 +3,9 @@ import com.google.protobuf.GeneratedMessageV3; import com.google.protobuf.MessageOrBuilder; import io.eigr.spawn.api.exceptions.ActorNotFoundException; +import io.eigr.spawn.internal.ActionArgumentFunction; +import io.eigr.spawn.internal.ActionEmptyFunction; +import io.eigr.spawn.internal.ActionNoArgumentFunction; import java.util.HashMap; import java.util.Map; @@ -14,7 +17,8 @@ public class ActorBehavior { public interface ActorOption extends Consumer {} private String name; - private String system; + + private String channel; Class stateType; @@ -31,6 +35,22 @@ public ActorBehavior(ActorOption... options){ .forEach(option -> option.accept(this)); } + public String getName() { + return name; + } + + public String getChannel() { + return channel; + } + + public long getDeactivatedTimeout() { + return deactivatedTimeout; + } + + public long getSnapshotTimeout() { + return snapshotTimeout; + } + public Class getStateType() { return this.stateType; } @@ -40,27 +60,31 @@ public void setStateType(Class stateType) { } public static ActorOption name(String actorName) { - return model -> model.name = actorName; + return instance -> instance.name = actorName; } - public static ActorOption system(String actorSystem) { - return model -> model.system = actorSystem; + public static ActorOption channel(String channel) { + return instance -> instance.channel = channel; } public static ActorOption deactivated(long timeout) { - return model -> model.deactivatedTimeout = timeout; + return instance -> instance.deactivatedTimeout = timeout; } public static ActorOption snapshot(long timeout) { - return model -> model.snapshotTimeout = timeout; + return instance -> instance.snapshotTimeout = timeout; } public static ActorOption action(String name, ActionNoArgumentFunction action) { - return model -> model.actions.put(name, action); + return instance -> instance.actions.put(name, action); } public static ActorOption action(String name, ActionArgumentFunction action) { - return model -> model.actions.put(name, action); + return instance -> instance.actions.put(name, action); + } + + public static ActorOption init(ActionNoArgumentFunction action) { + return instance -> instance.actions.put("Init", action); } public Value call(String action, ActorContext context) throws ActorNotFoundException { @@ -69,7 +93,7 @@ public Value call(String action, ActorContext conte } throw new ActorNotFoundException( - String.format("Action [%s] not found for Actor [%s::%s]", action, system, name)); + String.format("Action [%s] not found for Actor [%s]", action, name)); } public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { @@ -78,7 +102,7 @@ public Value call(String action, ActorContext conte } throw new ActorNotFoundException( - String.format("Action [%s] not found for Actor [%s::%s]", action, system, name)); + String.format("Action [%s] not found for Actor [%s]", action, name)); } } diff --git a/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java new file mode 100644 index 0000000..b7af65d --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java @@ -0,0 +1,13 @@ +package io.eigr.spawn.api.actors; + +import java.lang.reflect.ParameterizedType; + +public abstract class StatefulActor { + public Class getStateType() { + return (Class) ((ParameterizedType) + getClass().getGenericSuperclass()) + .getActualTypeArguments()[0]; + } + + public abstract ActorBehavior configure(); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java deleted file mode 100644 index 18023e4..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/StatefulActorBehavior.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.eigr.spawn.api.actors; - -import java.lang.reflect.ParameterizedType; - -public abstract class StatefulActorBehavior { - - - public Class getStateType() { - return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - } - - public abstract ActorBehavior setup(); -} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java new file mode 100644 index 0000000..46cb11e --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java @@ -0,0 +1,6 @@ +package io.eigr.spawn.api.actors; + +public abstract class StatelessActor { + + public abstract ActorBehavior configure(); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java deleted file mode 100644 index 9af228e..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/StatelessActorBehavior.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.eigr.spawn.api.actors; - -public abstract class StatelessActorBehavior { - - public abstract ActorBehavior setup(); -} diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java similarity index 65% rename from src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java rename to src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java index c2d9361..67ca6f3 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActionArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java @@ -1,6 +1,8 @@ -package io.eigr.spawn.api.actors; +package io.eigr.spawn.internal; import com.google.protobuf.MessageOrBuilder; +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.Value; @FunctionalInterface public interface ActionArgumentFunction extends ActionEmptyFunction { diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java b/src/main/java/io/eigr/spawn/internal/ActionEmptyFunction.java similarity index 54% rename from src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java rename to src/main/java/io/eigr/spawn/internal/ActionEmptyFunction.java index 3288508..a41522e 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActionEmptyFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionEmptyFunction.java @@ -1,3 +1,3 @@ -package io.eigr.spawn.api.actors; +package io.eigr.spawn.internal; public interface ActionEmptyFunction {} diff --git a/src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java similarity index 53% rename from src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java rename to src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java index 68a5e75..1cbe57a 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActionNoArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java @@ -1,4 +1,7 @@ -package io.eigr.spawn.api.actors; +package io.eigr.spawn.internal; + +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.Value; @FunctionalInterface public interface ActionNoArgumentFunction extends ActionEmptyFunction { diff --git a/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java b/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java deleted file mode 100644 index 5f157b8..0000000 --- a/src/test/java/io/eigr/spawn/ActorBehaviorTestEntity.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.eigr.spawn; - -import io.eigr.spawn.api.actors.ActorBehavior; -import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.StatelessActorBehavior; -import io.eigr.spawn.api.actors.Value; - -import io.eigr.spawn.java.test.domain.Actor.*; - -import static io.eigr.spawn.api.actors.ActorBehavior.*; - -public class ActorBehaviorTestEntity extends StatelessActorBehavior { - - @Override - public ActorBehavior setup() { - return new ActorBehavior( - name("test"), - system("spawn-system"), - deactivated(30000), - snapshot(10000), - action("hi", (ctx) -> Value.at().noReply()), - action("SayHello", this::sayHello), - action("SayHelloTwo", (ctx, arg) -> Value.at().noReply()) - ); - } - - public Value sayHello(ActorContext context, Request req) { - return Value.at().noReply(); - } -} diff --git a/src/test/java/io/eigr/spawn/MyTestActorEntity.java b/src/test/java/io/eigr/spawn/MyTestActorEntity.java new file mode 100644 index 0000000..01c4ccc --- /dev/null +++ b/src/test/java/io/eigr/spawn/MyTestActorEntity.java @@ -0,0 +1,39 @@ +package io.eigr.spawn; + +import io.eigr.spawn.api.actors.*; + +import io.eigr.spawn.java.test.domain.Actor.*; + +import java.util.Optional; + +import static io.eigr.spawn.api.actors.ActorBehavior.*; + +public class MyTestActorEntity extends StatefulActor { + + @Override + public ActorBehavior configure() { + return new ActorBehavior( + name("test"), + deactivated(30000), + snapshot(10000), + init(ctx -> { + Optional> maybeState = ctx.getState(); + if (maybeState.isPresent()) { + State state = maybeState.get().getState().get(); + return Value.at() + .state(state) + .noReply(); + } + return Value.at() + .state(State.newBuilder().addLanguages("Java").build()) + .noReply(); + }), + action("Hi", ctx -> Value.at().noReply()), + action("SayHello", this::sayHello), + action("SayHelloTwo", (ctx, arg) -> Value.at().noReply()) + ); + } + public Value sayHello(ActorContext ctx, Request req) { + return Value.at().response(Request.newBuilder().setLanguage("Java").build()).reply(); + } +} From c50ebc77c8d5744932ac527952703893b8477ace Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Fri, 8 Mar 2024 18:12:44 -0300 Subject: [PATCH 03/19] Make final --- src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java index 642d829..a1cdcd4 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java @@ -13,7 +13,7 @@ import java.util.function.Consumer; import java.util.stream.Stream; -public class ActorBehavior { +public final class ActorBehavior { public interface ActorOption extends Consumer {} private String name; From 10dd16bb851380709bb0c9f1553b3c3c76792b0a Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 14 Mar 2024 17:35:29 -0300 Subject: [PATCH 04/19] Create named and unnamed behaviors --- .../eigr/spawn/api/actors/StatefulActor.java | 2 ++ .../eigr/spawn/api/actors/StatelessActor.java | 2 ++ .../actors/{ => behaviors}/ActorBehavior.java | 17 ++++++---- .../actors/behaviors/NamedActorBehavior.java | 15 +++++++++ .../behaviors/UnNamedActorBehavior.java | 15 +++++++++ .../java/io/eigr/spawn/MyTestActorEntity.java | 32 +++++++++++-------- 6 files changed, 63 insertions(+), 20 deletions(-) rename src/main/java/io/eigr/spawn/api/actors/{ => behaviors}/ActorBehavior.java (92%) create mode 100644 src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java diff --git a/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java index b7af65d..53ebea7 100644 --- a/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java +++ b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java @@ -1,5 +1,7 @@ package io.eigr.spawn.api.actors; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; + import java.lang.reflect.ParameterizedType; public abstract class StatefulActor { diff --git a/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java index 46cb11e..f14772a 100644 --- a/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java +++ b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java @@ -1,5 +1,7 @@ package io.eigr.spawn.api.actors; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; + public abstract class StatelessActor { public abstract ActorBehavior configure(); diff --git a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java similarity index 92% rename from src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java rename to src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index a1cdcd4..7e22139 100644 --- a/src/main/java/io/eigr/spawn/api/actors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -1,11 +1,14 @@ -package io.eigr.spawn.api.actors; +package io.eigr.spawn.api.actors.behaviors; import com.google.protobuf.GeneratedMessageV3; import com.google.protobuf.MessageOrBuilder; +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.exceptions.ActorNotFoundException; import io.eigr.spawn.internal.ActionArgumentFunction; import io.eigr.spawn.internal.ActionEmptyFunction; import io.eigr.spawn.internal.ActionNoArgumentFunction; +import io.eigr.spawn.internal.ActorKind; import java.util.HashMap; import java.util.Map; @@ -13,7 +16,7 @@ import java.util.function.Consumer; import java.util.stream.Stream; -public final class ActorBehavior { +public abstract class ActorBehavior { public interface ActorOption extends Consumer {} private String name; @@ -35,6 +38,8 @@ public ActorBehavior(ActorOption... options){ .forEach(option -> option.accept(this)); } + protected abstract ActorKind getActorType(); + public String getName() { return name; } @@ -75,6 +80,10 @@ public static ActorOption snapshot(long timeout) { return instance -> instance.snapshotTimeout = timeout; } + public static ActorOption init(ActionNoArgumentFunction action) { + return instance -> instance.actions.put("Init", action); + } + public static ActorOption action(String name, ActionNoArgumentFunction action) { return instance -> instance.actions.put(name, action); } @@ -83,10 +92,6 @@ public static ActorOption action(String name, Actio return instance -> instance.actions.put(name, action); } - public static ActorOption init(ActionNoArgumentFunction action) { - return instance -> instance.actions.put("Init", action); - } - public Value call(String action, ActorContext context) throws ActorNotFoundException { if (this.actions.containsKey(action)) { ((ActionNoArgumentFunction) this.actions.get(action)).handle(context); diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java new file mode 100644 index 0000000..7ed9df8 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java @@ -0,0 +1,15 @@ +package io.eigr.spawn.api.actors.behaviors; + +import io.eigr.spawn.internal.ActorKind; + +public final class NamedActorBehavior extends ActorBehavior { + + public NamedActorBehavior(ActorOption... options) { + super(options); + } + + @Override + protected ActorKind getActorType() { + return ActorKind.NAMED; + } +} diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java new file mode 100644 index 0000000..7bb2a7d --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java @@ -0,0 +1,15 @@ +package io.eigr.spawn.api.actors.behaviors; + +import io.eigr.spawn.internal.ActorKind; + +public final class UnNamedActorBehavior extends ActorBehavior { + + public UnNamedActorBehavior(ActorOption... options) { + super(options); + } + + @Override + protected ActorKind getActorType() { + return ActorKind.UNNAMED; + } +} diff --git a/src/test/java/io/eigr/spawn/MyTestActorEntity.java b/src/test/java/io/eigr/spawn/MyTestActorEntity.java index 01c4ccc..e9a7c18 100644 --- a/src/test/java/io/eigr/spawn/MyTestActorEntity.java +++ b/src/test/java/io/eigr/spawn/MyTestActorEntity.java @@ -2,32 +2,23 @@ import io.eigr.spawn.api.actors.*; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; import io.eigr.spawn.java.test.domain.Actor.*; import java.util.Optional; -import static io.eigr.spawn.api.actors.ActorBehavior.*; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; public class MyTestActorEntity extends StatefulActor { @Override public ActorBehavior configure() { - return new ActorBehavior( + return new NamedActorBehavior( name("test"), deactivated(30000), snapshot(10000), - init(ctx -> { - Optional> maybeState = ctx.getState(); - if (maybeState.isPresent()) { - State state = maybeState.get().getState().get(); - return Value.at() - .state(state) - .noReply(); - } - return Value.at() - .state(State.newBuilder().addLanguages("Java").build()) - .noReply(); - }), + init(this::handleInit), action("Hi", ctx -> Value.at().noReply()), action("SayHello", this::sayHello), action("SayHelloTwo", (ctx, arg) -> Value.at().noReply()) @@ -36,4 +27,17 @@ public ActorBehavior configure() { public Value sayHello(ActorContext ctx, Request req) { return Value.at().response(Request.newBuilder().setLanguage("Java").build()).reply(); } + + private Value handleInit(ActorContext ctx) { + Optional> maybeState = ctx.getState(); + if (maybeState.isPresent()) { + State state = maybeState.get().getState().get(); + return Value.at() + .state(state) + .noReply(); + } + return Value.at() + .state(State.newBuilder().addLanguages("Java").build()) + .noReply(); + } } From 41fe608c47b66087124f99e586e825bee8a32267 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Tue, 23 Jul 2024 12:41:33 -0300 Subject: [PATCH 05/19] New api --- src/main/java/io/eigr/spawn/api/Spawn.java | 99 +++++++------------ .../io/eigr/spawn/api/actors/BaseActor.java | 11 +++ .../eigr/spawn/api/actors/StatefulActor.java | 9 +- .../eigr/spawn/api/actors/StatelessActor.java | 10 +- .../api/actors/behaviors/ActorBehavior.java | 7 +- .../api/actors/behaviors/BehaviorCtx.java | 28 ++++++ .../internal/ActionArgumentFunction.java | 6 +- .../internal/ActionNoArgumentFunction.java | 3 +- .../java/io/eigr/spawn/internal/Entity.java | 11 +++ .../java/io/eigr/spawn/MyTestActorEntity.java | 14 +-- src/test/java/io/eigr/spawn/SpawnTest.java | 4 +- .../test/actors/ActorWithConstructor.java | 40 +++++--- .../io/eigr/spawn/test/actors/JoeActor.java | 38 ++++--- 13 files changed, 162 insertions(+), 118 deletions(-) create mode 100644 src/main/java/io/eigr/spawn/api/actors/BaseActor.java create mode 100644 src/main/java/io/eigr/spawn/api/actors/behaviors/BehaviorCtx.java diff --git a/src/main/java/io/eigr/spawn/api/Spawn.java b/src/main/java/io/eigr/spawn/api/Spawn.java index a034ba4..8cf3168 100644 --- a/src/main/java/io/eigr/spawn/api/Spawn.java +++ b/src/main/java/io/eigr/spawn/api/Spawn.java @@ -6,16 +6,21 @@ import io.eigr.functions.protocol.Protocol; import io.eigr.functions.protocol.actors.ActorOuterClass; import io.eigr.spawn.api.actors.ActorFactory; +import io.eigr.spawn.api.actors.BaseActor; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.StatelessActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulPooledActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulUnNamedActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessPooledActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessUnNamedActor; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.exceptions.ActorCreationException; import io.eigr.spawn.api.exceptions.ActorRegistrationException; import io.eigr.spawn.api.exceptions.SpawnException; import io.eigr.spawn.api.exceptions.SpawnFailureException; +import io.eigr.spawn.api.extensions.DependencyInjector; import io.eigr.spawn.internal.Entity; import io.eigr.spawn.internal.transport.client.OkHttpSpawnClient; import io.eigr.spawn.internal.transport.client.SpawnClient; @@ -48,6 +53,8 @@ public final class Spawn { private final String proxyHost; private final int proxyPort; private final String system; + + private BehaviorCtx ctx; private final List entities; private final String host; private final Executor executor; @@ -55,6 +62,7 @@ public final class Spawn { private Spawn(SpawnSystem builder) { this.system = builder.system; + this.ctx = builder.ctx; this.entities = builder.entities; this.port = builder.transportOpts.getPort(); this.host = builder.transportOpts.getHost(); @@ -82,6 +90,10 @@ public String getSystem() { return system; } + public BehaviorCtx getBehaviorCtx() { + return ctx; + } + public int getTerminationGracePeriodSeconds() { return terminationGracePeriodSeconds; } @@ -267,7 +279,9 @@ public static final class SpawnSystem { private Cache actorIdCache; private SpawnClient client; - private String system = "spawn-system"; + private String system; + + private BehaviorCtx ctx; private int terminationGracePeriodSeconds = 30; @@ -283,6 +297,13 @@ public static final class SpawnSystem { */ public SpawnSystem create(String system) { this.system = system; + this.ctx = BehaviorCtx.create(); + return this; + } + + public SpawnSystem create(String system, DependencyInjector injector) { + this.system = system; + this.ctx = BehaviorCtx.create(injector); return this; } @@ -298,36 +319,28 @@ public SpawnSystem createFromEnv() { String system = System.getenv("PROXY_ACTOR_SYSTEM_NAME"); Objects.requireNonNull(system, "To use createFromEnv method it is necessary to have defined the environment variable PROXY_ACTOR_SYSTEM_NAME"); this.system = system; + this.ctx = BehaviorCtx.create(); return this; } - /** - *

Constructor method that adds a new Actor to the Spawn proxy. - *

- * - * @param actorKlass the actor definition class - * @return the SpawnSystem instance - * @since 0.0.1 - */ - public SpawnSystem withActor(Class actorKlass) { - Optional maybeEntity = getEntity(actorKlass); - maybeEntity.ifPresent(this.entities::add); + public SpawnSystem createFromEnv(DependencyInjector injector) { + String system = System.getenv("PROXY_ACTOR_SYSTEM_NAME"); + Objects.requireNonNull(system, "To use createFromEnv method it is necessary to have defined the environment variable PROXY_ACTOR_SYSTEM_NAME"); + this.system = system; + this.ctx = BehaviorCtx.create(injector); return this; } /** *

Constructor method that adds a new Actor to the Spawn proxy. - * Allows options to be passed to the class constructor. The constructor must consist of only one argument *

* * @param actorKlass the actor definition class - * @param arg the object that will be passed as an argument to the constructor via the lambda fabric - * @param factory a lambda that constructs the instance of the Actor object * @return the SpawnSystem instance * @since 0.0.1 */ - public SpawnSystem withActor(Class actorKlass, Object arg, ActorFactory factory) { - Optional maybeEntity = getEntity(actorKlass, arg, factory); + public SpawnSystem withActor(Class actorKlass) { + Optional maybeEntity = getEntity(actorKlass); maybeEntity.ifPresent(this.entities::add); return this; } @@ -360,62 +373,22 @@ public Spawn build() { } private Optional getEntity(Class actorKlass) { - Optional maybeEntity = getStatefulEntity(actorKlass, null, null); + Optional maybeEntity = mapEntity(actorKlass); if (maybeEntity.isPresent()) { return maybeEntity; } - maybeEntity = getStatelessEntity(actorKlass, null, null); - if (maybeEntity.isPresent()) { - return maybeEntity; - } - - return Optional.empty(); - } - - private Optional getEntity(Class actorKlass, Object arg, ActorFactory factory) { - Optional maybeEntity = getStatefulEntity(actorKlass, arg, factory); - - if (maybeEntity.isPresent()) { - return maybeEntity; - } - - maybeEntity = getStatelessEntity(actorKlass, arg, factory); - if (maybeEntity.isPresent()) { - return maybeEntity; - } - - return Optional.empty(); - } - - private Optional getStatefulEntity(Class actorKlass, Object arg, ActorFactory factory) { - if (Objects.nonNull(actorKlass.getAnnotation(StatefulNamedActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatefulNamedActor.class), arg, factory)); - } - - if (Objects.nonNull(actorKlass.getAnnotation(StatefulUnNamedActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatefulUnNamedActor.class), arg, factory)); - } - - if (Objects.nonNull(actorKlass.getAnnotation(StatefulPooledActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatefulPooledActor.class), arg, factory)); - } - return Optional.empty(); } - private Optional getStatelessEntity(Class actorKlass, Object arg, ActorFactory factory) { - if (Objects.nonNull(actorKlass.getAnnotation(StatelessNamedActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatelessNamedActor.class), arg, factory)); - } - - if (Objects.nonNull(actorKlass.getAnnotation(StatelessUnNamedActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatelessUnNamedActor.class), arg, factory)); + private Optional mapEntity(Class actorKlass) { + if (actorKlass.isAssignableFrom(StatefulActor.class)) { + return Optional.of(Entity.fromStatefulActorToEntity(ctx, actorKlass)); } - if (Objects.nonNull(actorKlass.getAnnotation(StatelessPooledActor.class))) { - return Optional.of(Entity.fromAnnotationToEntity(actorKlass, actorKlass.getAnnotation(StatelessPooledActor.class), arg, factory)); + if (actorKlass.isAssignableFrom(StatelessActor.class)) { + return Optional.of(Entity.fromStatelessActorToEntity(ctx, actorKlass)); } return Optional.empty(); diff --git a/src/main/java/io/eigr/spawn/api/actors/BaseActor.java b/src/main/java/io/eigr/spawn/api/actors/BaseActor.java new file mode 100644 index 0000000..aac3318 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/BaseActor.java @@ -0,0 +1,11 @@ +package io.eigr.spawn.api.actors; + +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; + +public abstract class BaseActor { + + public abstract ActorBehavior configure(BehaviorCtx ctx); + + public abstract Boolean isStateful(); +} diff --git a/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java index 53ebea7..333440d 100644 --- a/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java +++ b/src/main/java/io/eigr/spawn/api/actors/StatefulActor.java @@ -1,15 +1,16 @@ package io.eigr.spawn.api.actors; -import io.eigr.spawn.api.actors.behaviors.ActorBehavior; - import java.lang.reflect.ParameterizedType; -public abstract class StatefulActor { +public abstract class StatefulActor extends BaseActor { public Class getStateType() { return (Class) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; } - public abstract ActorBehavior configure(); + @Override + public Boolean isStateful() { + return true; + } } diff --git a/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java index f14772a..2ef0272 100644 --- a/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java +++ b/src/main/java/io/eigr/spawn/api/actors/StatelessActor.java @@ -1,8 +1,8 @@ package io.eigr.spawn.api.actors; -import io.eigr.spawn.api.actors.behaviors.ActorBehavior; - -public abstract class StatelessActor { - - public abstract ActorBehavior configure(); +public abstract class StatelessActor extends BaseActor { + @Override + public Boolean isStateful() { + return false; + } } diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index 7e22139..7cd3172 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -1,7 +1,6 @@ package io.eigr.spawn.api.actors.behaviors; import com.google.protobuf.GeneratedMessageV3; -import com.google.protobuf.MessageOrBuilder; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.exceptions.ActorNotFoundException; @@ -88,11 +87,11 @@ public static ActorOption action(String name, ActionNoArgumentFunction action) { return instance -> instance.actions.put(name, action); } - public static ActorOption action(String name, ActionArgumentFunction action) { + public static ActorOption action(String name, ActionArgumentFunction action) { return instance -> instance.actions.put(name, action); } - public
Value call(String action, ActorContext context) throws ActorNotFoundException { + public Value call(String action, ActorContext context) throws ActorNotFoundException { if (this.actions.containsKey(action)) { ((ActionNoArgumentFunction) this.actions.get(action)).handle(context); } @@ -101,7 +100,7 @@ public Value call(String action, ActorContext conte String.format("Action [%s] not found for Actor [%s]", action, name)); } - public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { + public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { if (this.actions.containsKey(action)) { ((ActionArgumentFunction) this.actions.get(action)).handle(context, argument); } diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/BehaviorCtx.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/BehaviorCtx.java new file mode 100644 index 0000000..fd0ccf3 --- /dev/null +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/BehaviorCtx.java @@ -0,0 +1,28 @@ +package io.eigr.spawn.api.actors.behaviors; + +import io.eigr.spawn.api.extensions.DependencyInjector; +import io.eigr.spawn.api.extensions.SimpleDependencyInjector; + +public final class BehaviorCtx { + private DependencyInjector injector; + + private BehaviorCtx() { + this.injector = SimpleDependencyInjector.createInjector(); + } + + private BehaviorCtx(DependencyInjector injector) { + this.injector = injector; + } + + public static BehaviorCtx create() { + return new BehaviorCtx(); + } + + public static BehaviorCtx create(DependencyInjector injector) { + return new BehaviorCtx(injector); + } + + public DependencyInjector getInjector() { + return injector; + } +} diff --git a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java index 67ca6f3..abe24a3 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java @@ -1,11 +1,11 @@ package io.eigr.spawn.internal; -import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.GeneratedMessageV3; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; @FunctionalInterface -public interface ActionArgumentFunction extends ActionEmptyFunction { +public interface ActionArgumentFunction extends ActionEmptyFunction { - Value handle(ActorContext context, A argument); + Value handle(ActorContext context, A argument); } diff --git a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java index 1cbe57a..e74941c 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java @@ -1,10 +1,11 @@ package io.eigr.spawn.internal; +import com.google.protobuf.GeneratedMessageV3; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; @FunctionalInterface -public interface ActionNoArgumentFunction extends ActionEmptyFunction { +public interface ActionNoArgumentFunction extends ActionEmptyFunction { Value handle(ActorContext context); } diff --git a/src/main/java/io/eigr/spawn/internal/Entity.java b/src/main/java/io/eigr/spawn/internal/Entity.java index 6c1d191..a76527e 100644 --- a/src/main/java/io/eigr/spawn/internal/Entity.java +++ b/src/main/java/io/eigr/spawn/internal/Entity.java @@ -3,6 +3,7 @@ import io.eigr.functions.protocol.actors.ActorOuterClass; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.ActorFactory; +import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.annotations.*; import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulPooledActor; @@ -10,6 +11,7 @@ import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessPooledActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessUnNamedActor; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,6 +82,7 @@ public Entity( + public String getActorName() { return actorName; } @@ -234,6 +237,14 @@ public String toString() { return sb.toString(); } + public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class entity) { + return null; + } + + public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class entity) { + return null; + } + public static Entity fromAnnotationToEntity(Class entity, StatefulNamedActor actor, Object arg, ActorFactory factory) { String actorBeanName = entity.getSimpleName(); String actorName; diff --git a/src/test/java/io/eigr/spawn/MyTestActorEntity.java b/src/test/java/io/eigr/spawn/MyTestActorEntity.java index e9a7c18..004366f 100644 --- a/src/test/java/io/eigr/spawn/MyTestActorEntity.java +++ b/src/test/java/io/eigr/spawn/MyTestActorEntity.java @@ -3,6 +3,7 @@ import io.eigr.spawn.api.actors.*; import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; import io.eigr.spawn.java.test.domain.Actor.*; @@ -13,20 +14,17 @@ public class MyTestActorEntity extends StatefulActor { @Override - public ActorBehavior configure() { + public ActorBehavior configure(BehaviorCtx context) { return new NamedActorBehavior( name("test"), deactivated(30000), snapshot(10000), init(this::handleInit), - action("Hi", ctx -> Value.at().noReply()), + action("Hi", actorCtx -> Value.at().noReply()), action("SayHello", this::sayHello), - action("SayHelloTwo", (ctx, arg) -> Value.at().noReply()) + action("SayHelloTwo", (actorCtx, arg) -> Value.at().noReply()) ); } - public Value sayHello(ActorContext ctx, Request req) { - return Value.at().response(Request.newBuilder().setLanguage("Java").build()).reply(); - } private Value handleInit(ActorContext ctx) { Optional> maybeState = ctx.getState(); @@ -40,4 +38,8 @@ private Value handleInit(ActorContext ctx) { .state(State.newBuilder().addLanguages("Java").build()) .noReply(); } + + public Value sayHello(ActorContext ctx, Request req) { + return Value.at().response(Request.newBuilder().setLanguage("Java").build()).reply(); + } } diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 9b8805b..839ee40 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -29,9 +29,9 @@ public void before() throws Exception { injector.bind(String.class, "Hello with Constructor"); spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system") + .create("spawn-system", injector) .withActor(JoeActor.class) - .withActor(ActorWithConstructor.class, injector, arg -> new ActorWithConstructor((DependencyInjector) arg)) + .withActor(ActorWithConstructor.class) .withTransportOptions( TransportOpts.builder() .port(8091) diff --git a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java index 0cbdea8..27b7a09 100644 --- a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java +++ b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java @@ -1,36 +1,44 @@ package io.eigr.spawn.test.actors; import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.Value; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; + import io.eigr.spawn.api.extensions.DependencyInjector; -import io.eigr.spawn.java.test.domain.Actor; -@StatefulNamedActor(name = "test_actor_constructor", stateType = Actor.State.class) -public final class ActorWithConstructor { +import io.eigr.spawn.java.test.domain.Actor.Reply; +import io.eigr.spawn.java.test.domain.Actor.Request; +import io.eigr.spawn.java.test.domain.Actor.State; - private final String defaultMessage; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.action; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.name; +public final class ActorWithConstructor extends StatefulActor { - public ActorWithConstructor(DependencyInjector injector) { - this.defaultMessage = injector.getInstance(String.class); - } + private String defaultMessage; - @Action(inputType = Actor.Request.class) - public Value setLanguage(Actor.Request msg, ActorContext context) { - if (context.getState().isPresent()) { - } + @Override + public ActorBehavior configure(BehaviorCtx context) { + defaultMessage = context.getInjector().getInstance(String.class); + return new NamedActorBehavior( + name("test_actor_constructor"), + action("SetLanguage", this::setLanguage) + ); + } + public Value setLanguage(ActorContext context, Request msg) { return Value.at() - .response(Actor.Reply.newBuilder() + .response(Reply.newBuilder() .setResponse(defaultMessage) .build()) .state(updateState("java")) .reply(); } - private Actor.State updateState(String language) { - return Actor.State.newBuilder() + private State updateState(String language) { + return State.newBuilder() .addLanguages(language) .build(); } diff --git a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java index 0f2c3f4..c04ee15 100644 --- a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java +++ b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java @@ -1,29 +1,39 @@ package io.eigr.spawn.test.actors; -import io.eigr.spawn.api.actors.Value; -import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.workflows.SideEffect; -import io.eigr.spawn.java.test.domain.Actor; - -@StatefulNamedActor(name = "test_joe", stateType = Actor.State.class, channel = "test.channel") -public final class JoeActor { - @Action(inputType = Actor.Request.class) - public Value setLanguage(Actor.Request msg, ActorContext context) { +import io.eigr.spawn.api.actors.*; + +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.java.test.domain.Actor.*; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class JoeActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("test_joe"), + channel("test.channel"), + action("SetLanguage", this::setLanguage) + ); + } + + public Value setLanguage(ActorContext context, Request msg) { if (context.getState().isPresent()) { } return Value.at() - .response(Actor.Reply.newBuilder() + .response(Reply.newBuilder() .setResponse("Hello From Java") .build()) .state(updateState("java")) .reply(); } - private Actor.State updateState(String language) { - return Actor.State.newBuilder() + private State updateState(String language) { + return State.newBuilder() .addLanguages(language) .build(); } From 13ef3c70dbae91ef6099997b205b8cbbfeaf82e6 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Tue, 23 Jul 2024 18:56:53 -0300 Subject: [PATCH 06/19] Registering actors --- src/main/java/io/eigr/spawn/api/Spawn.java | 26 +- .../api/actors/behaviors/ActorBehavior.java | 105 ++-- .../actors/behaviors/NamedActorBehavior.java | 2 +- .../behaviors/UnNamedActorBehavior.java | 2 +- .../spawn/api/actors/workflows/Broadcast.java | 4 +- .../internal/ActionArgumentFunction.java | 4 +- .../spawn/internal/ActionConfiguration.java | 33 ++ .../eigr/spawn/internal/ActionEnvelope.java | 30 ++ .../io/eigr/spawn/internal/ActionKind.java | 6 + .../internal/ActionNoArgumentFunction.java | 4 +- .../java/io/eigr/spawn/internal/Entity.java | 472 +++++++++++++----- .../functions/protocol/actors/actor.proto | 49 +- .../functions/protocol/actors/protocol.proto | 32 +- src/test/java/io/eigr/spawn/SpawnTest.java | 5 +- src/test/java/io/eigr/spawn/WorkflowTest.java | 2 +- 15 files changed, 543 insertions(+), 233 deletions(-) create mode 100644 src/main/java/io/eigr/spawn/internal/ActionConfiguration.java create mode 100644 src/main/java/io/eigr/spawn/internal/ActionEnvelope.java create mode 100644 src/main/java/io/eigr/spawn/internal/ActionKind.java diff --git a/src/main/java/io/eigr/spawn/api/Spawn.java b/src/main/java/io/eigr/spawn/api/Spawn.java index 8cf3168..25e3d76 100644 --- a/src/main/java/io/eigr/spawn/api/Spawn.java +++ b/src/main/java/io/eigr/spawn/api/Spawn.java @@ -176,7 +176,9 @@ private void startServer() throws SpawnException { } private void registerActorSystem() throws ActorRegistrationException { - ActorOuterClass.Registry registry = ActorOuterClass.Registry.newBuilder().putAllActors(getActors(this.entities)).build(); + ActorOuterClass.Registry registry = ActorOuterClass.Registry.newBuilder() + .putAllActors(getActors(this.entities)) + .build(); ActorOuterClass.ActorSystem actorSystem = ActorOuterClass.ActorSystem.newBuilder() .setName(this.system) @@ -197,11 +199,14 @@ private void registerActorSystem() throws ActorRegistrationException { .build(); log.debug("Registering Actors on Proxy. Registry: {}", req); + System.out.println(String.format("Registering Actors on Proxy. Registry: %s", req)); this.client.register(req); } private Map getActors(List entities) { + System.out.println(String.format("Entities %s", entities.size())); return entities.stream().map(actorEntity -> { + System.out.println(String.format("Register Actor Entity %s", actorEntity.getActorName())); ActorOuterClass.ActorSnapshotStrategy snapshotStrategy; if (actorEntity.isStateful()) { snapshotStrategy = ActorOuterClass.ActorSnapshotStrategy.newBuilder() @@ -231,7 +236,10 @@ private Map getActors(List entities) { Map tags = new HashMap<>(); ActorOuterClass.Metadata metadata = ActorOuterClass.Metadata.newBuilder() - .setChannelGroup(actorEntity.getChannel()).putAllTags(tags) + .addChannelGroup( + ActorOuterClass.Channel.newBuilder() + .setTopic(actorEntity.getChannel()) + .build()) .build(); return ActorOuterClass.Actor.newBuilder() @@ -339,7 +347,7 @@ public SpawnSystem createFromEnv(DependencyInjector injector) { * @return the SpawnSystem instance * @since 0.0.1 */ - public SpawnSystem withActor(Class actorKlass) { + public SpawnSystem withActor(Class actorKlass) throws ActorCreationException { Optional maybeEntity = getEntity(actorKlass); maybeEntity.ifPresent(this.entities::add); return this; @@ -372,7 +380,7 @@ public Spawn build() { return new Spawn(this); } - private Optional getEntity(Class actorKlass) { + private Optional getEntity(Class actorKlass) throws ActorCreationException { Optional maybeEntity = mapEntity(actorKlass); if (maybeEntity.isPresent()) { @@ -382,15 +390,19 @@ private Optional getEntity(Class actorKlass) { return Optional.empty(); } - private Optional mapEntity(Class actorKlass) { - if (actorKlass.isAssignableFrom(StatefulActor.class)) { + private Optional mapEntity(Class actorKlass) throws ActorCreationException { + System.out.println(String.format("Klass %s", actorKlass.getSuperclass())); + if (StatefulActor.class.isAssignableFrom(actorKlass)) { + System.out.println("StatefulActor"); return Optional.of(Entity.fromStatefulActorToEntity(ctx, actorKlass)); } - if (actorKlass.isAssignableFrom(StatelessActor.class)) { + if (StatelessActor.class.isAssignableFrom(actorKlass)) { + System.out.println("StatelessActor"); return Optional.of(Entity.fromStatelessActorToEntity(ctx, actorKlass)); } + System.out.println("empty"); return Optional.empty(); } } diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index 7cd3172..3fb83a8 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -1,13 +1,10 @@ package io.eigr.spawn.api.actors.behaviors; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.exceptions.ActorNotFoundException; -import io.eigr.spawn.internal.ActionArgumentFunction; -import io.eigr.spawn.internal.ActionEmptyFunction; -import io.eigr.spawn.internal.ActionNoArgumentFunction; -import io.eigr.spawn.internal.ActorKind; +import io.eigr.spawn.internal.*; import java.util.HashMap; import java.util.Map; @@ -16,97 +13,101 @@ import java.util.stream.Stream; public abstract class ActorBehavior { - public interface ActorOption extends Consumer {} - + Class stateType; private String name; private String channel; - - Class stateType; - private long deactivatedTimeout = 60000; - private long snapshotTimeout = 50000; + private final Map actions = new HashMap<>(); - private Map actions = new HashMap<>(); + public ActorBehavior(ActorOption... options) { + Optional.ofNullable(options).map(Stream::of).orElseGet(Stream::empty).forEach(option -> option.accept(this)); + } - public ActorBehavior(ActorOption... options){ - Optional.ofNullable(options) - .map(Stream::of) - .orElseGet(Stream::empty) - .forEach(option -> option.accept(this)); + public static ActorOption name(String actorName) { + return instance -> instance.name = actorName; } - protected abstract ActorKind getActorType(); + public static ActorOption channel(String channel) { + return instance -> instance.channel = channel; + } - public String getName() { - return name; + public static ActorOption deactivated(long timeout) { + return instance -> instance.deactivatedTimeout = timeout; } - public String getChannel() { - return channel; + public static ActorOption snapshot(long timeout) { + return instance -> instance.snapshotTimeout = timeout; } - public long getDeactivatedTimeout() { - return deactivatedTimeout; + public static ActorOption init(ActionNoArgumentFunction action) { + return instance -> instance.actions.put("Init", new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); } - public long getSnapshotTimeout() { - return snapshotTimeout; + public static ActorOption action(String name, ActionNoArgumentFunction action) { + return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); } - public Class getStateType() { - return this.stateType; + public static ActorOption action(String name, ActionArgumentFunction action) { + return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); } - public void setStateType(Class stateType) { - this.stateType = stateType; + public static ActorOption timerAction(String name, int timer, ActionNoArgumentFunction action) { + return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer))); } - public static ActorOption name(String actorName) { - return instance -> instance.name = actorName; + public static ActorOption timerAction(String name, int timer, ActionArgumentFunction action) { + return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer))); } - public static ActorOption channel(String channel) { - return instance -> instance.channel = channel; + public abstract ActorKind getActorType(); + + public String getName() { + return name; } - public static ActorOption deactivated(long timeout) { - return instance -> instance.deactivatedTimeout = timeout; + public String getChannel() { + return channel; } - public static ActorOption snapshot(long timeout) { - return instance -> instance.snapshotTimeout = timeout; + public long getDeactivatedTimeout() { + return deactivatedTimeout; } - public static ActorOption init(ActionNoArgumentFunction action) { - return instance -> instance.actions.put("Init", action); + public long getSnapshotTimeout() { + return snapshotTimeout; } - public static ActorOption action(String name, ActionNoArgumentFunction action) { - return instance -> instance.actions.put(name, action); + public Class getStateType() { + return this.stateType; + } + + public void setStateType(Class stateType) { + this.stateType = stateType; } - public static ActorOption action(String name, ActionArgumentFunction action) { - return instance -> instance.actions.put(name, action); + public Map getActions() { + return actions; } - public Value call(String action, ActorContext context) throws ActorNotFoundException { + public Value call(String action, ActorContext context) throws ActorNotFoundException { if (this.actions.containsKey(action)) { - ((ActionNoArgumentFunction) this.actions.get(action)).handle(context); + ((ActionNoArgumentFunction) this.actions.get(action).getFunction()).handle(context); } - throw new ActorNotFoundException( - String.format("Action [%s] not found for Actor [%s]", action, name)); + throw new ActorNotFoundException(String.format("Action [%s] not found for Actor [%s]", action, name)); } - public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { + public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { if (this.actions.containsKey(action)) { - ((ActionArgumentFunction) this.actions.get(action)).handle(context, argument); + ((ActionArgumentFunction) this.actions.get(action).getFunction()).handle(context, argument); } - throw new ActorNotFoundException( - String.format("Action [%s] not found for Actor [%s]", action, name)); + throw new ActorNotFoundException(String.format("Action [%s] not found for Actor [%s]", action, name)); + } + + public interface ActorOption extends Consumer { } } diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java index 7ed9df8..77fa69c 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/NamedActorBehavior.java @@ -9,7 +9,7 @@ public NamedActorBehavior(ActorOption... options) { } @Override - protected ActorKind getActorType() { + public ActorKind getActorType() { return ActorKind.NAMED; } } diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java index 7bb2a7d..d872486 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/UnNamedActorBehavior.java @@ -9,7 +9,7 @@ public UnNamedActorBehavior(ActorOption... options) { } @Override - protected ActorKind getActorType() { + public ActorKind getActorType() { return ActorKind.UNNAMED; } } diff --git a/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java b/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java index 69ee689..0e94d78 100644 --- a/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java +++ b/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java @@ -3,6 +3,7 @@ import com.google.protobuf.Any; import com.google.protobuf.GeneratedMessage; import io.eigr.functions.protocol.Protocol; +import io.eigr.functions.protocol.actors.ActorOuterClass; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -44,8 +45,9 @@ public T getPayload() { public Protocol.Broadcast build() { Protocol.Broadcast.Builder builder = Protocol.Broadcast.newBuilder(); + //TODO use Channel object instead of string if (this.action.isPresent()) { - builder.setActionName(this.action.get()); + builder.setChannelGroup(this.action.get()); } if (this.channel.isPresent()) { diff --git a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java index abe24a3..d004bce 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java @@ -1,11 +1,11 @@ package io.eigr.spawn.internal; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; @FunctionalInterface -public interface ActionArgumentFunction extends ActionEmptyFunction { +public interface ActionArgumentFunction extends ActionEmptyFunction { Value handle(ActorContext context, A argument); } diff --git a/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java b/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java new file mode 100644 index 0000000..6ba32c3 --- /dev/null +++ b/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java @@ -0,0 +1,33 @@ +package io.eigr.spawn.internal; + +import java.util.StringJoiner; + +public final class ActionConfiguration { + private final ActionKind kind; + private int timer; + + public ActionConfiguration(ActionKind kind) { + this.kind = kind; + } + + public ActionConfiguration(ActionKind kind, int timer) { + this.kind = kind; + this.timer = timer; + } + + public ActionKind getKind() { + return kind; + } + + public int getTimer() { + return timer; + } + + @Override + public String toString() { + return new StringJoiner(", ", ActionConfiguration.class.getSimpleName() + "[", "]") + .add("kind=" + kind) + .add("timer=" + timer) + .toString(); + } +} diff --git a/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java b/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java new file mode 100644 index 0000000..9a85bf7 --- /dev/null +++ b/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java @@ -0,0 +1,30 @@ +package io.eigr.spawn.internal; + +import java.util.StringJoiner; + +public final class ActionEnvelope { + + private final ActionEmptyFunction function; + private final ActionConfiguration config; + + public ActionEnvelope(ActionEmptyFunction function, ActionConfiguration config) { + this.function = function; + this.config = config; + } + + public ActionEmptyFunction getFunction() { + return function; + } + + public ActionConfiguration getConfig() { + return config; + } + + @Override + public String toString() { + return new StringJoiner(", ", ActionEnvelope.class.getSimpleName() + "[", "]") + .add("function=" + function) + .add("config=" + config) + .toString(); + } +} diff --git a/src/main/java/io/eigr/spawn/internal/ActionKind.java b/src/main/java/io/eigr/spawn/internal/ActionKind.java new file mode 100644 index 0000000..aeef34c --- /dev/null +++ b/src/main/java/io/eigr/spawn/internal/ActionKind.java @@ -0,0 +1,6 @@ +package io.eigr.spawn.internal; + +public enum ActionKind { + NORMAL_DISPATCH, + TIMER_DISPATCH +} diff --git a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java index e74941c..1d90239 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java @@ -1,11 +1,11 @@ package io.eigr.spawn.internal; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; @FunctionalInterface -public interface ActionNoArgumentFunction extends ActionEmptyFunction { +public interface ActionNoArgumentFunction extends ActionEmptyFunction { Value handle(ActorContext context); } diff --git a/src/main/java/io/eigr/spawn/internal/Entity.java b/src/main/java/io/eigr/spawn/internal/Entity.java index a76527e..0e35857 100644 --- a/src/main/java/io/eigr/spawn/internal/Entity.java +++ b/src/main/java/io/eigr/spawn/internal/Entity.java @@ -4,18 +4,26 @@ import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.ActorFactory; import io.eigr.spawn.api.actors.StatefulActor; -import io.eigr.spawn.api.actors.annotations.*; +import io.eigr.spawn.api.actors.StatelessActor; +import io.eigr.spawn.api.actors.annotations.Action; +import io.eigr.spawn.api.actors.annotations.TimerAction; import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulPooledActor; import io.eigr.spawn.api.actors.annotations.stateful.StatefulUnNamedActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessPooledActor; import io.eigr.spawn.api.actors.annotations.stateless.StatelessUnNamedActor; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.api.actors.behaviors.UnNamedActorBehavior; +import io.eigr.spawn.api.exceptions.ActorCreationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.stream.Collectors; @@ -80,170 +88,224 @@ public Entity( this.actorFactory = actorFactory; } + public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class actor) { + return null; + } + public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class actor) throws ActorCreationException { + try { + Constructor constructor = actor.getConstructor(); + StatefulActor stActor = (StatefulActor) constructor.newInstance(); + Class stateType = stActor.getStateType(); + ActorBehavior behavior = stActor.configure(ctx); + if (behavior.getClass().isAssignableFrom(NamedActorBehavior.class)) { + Entity entity = buildNamedActor(stateType, stActor, (NamedActorBehavior) behavior); + System.out.println(String.format("Stateful NamedActorBehavior %s", entity)); + return entity; + } - public String getActorName() { - return actorName; - } + if (behavior.getClass().isAssignableFrom(UnNamedActorBehavior.class)) { + return buildUnNamedActor(stateType, stActor, (UnNamedActorBehavior) behavior); + } - public void setActorName(String actorName) { - this.actorName = actorName; - } + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | + IllegalAccessException e) { + throw new ActorCreationException(); + } - public Class getActorType() { - return actorType; + throw new ActorCreationException(); } - public ActorOuterClass.Kind getKind() { - return kind; - } + private static Entity buildNamedActor(Class stateType, StatefulActor actor, NamedActorBehavior behavior) { + String actorName = behavior.getName(); + ActorKind kind = behavior.getActorType(); + String channel = behavior.getChannel(); + long deactivateTimeout = behavior.getDeactivatedTimeout(); + long snapshotTimeout = behavior.getSnapshotTimeout(); - public Class getStateType() { - return stateType; - } + final Map actions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + ActionEnvelope envelope = entry.getValue(); - public String getActorBeanName() { - return actorBeanName; - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - public boolean isStateful() { - return stateful; - } + final Map timerActions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + int timer = entry.getValue().getConfig().getTimer(); - public long getDeactivateTimeout() { - return deactivateTimeout; - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, timer, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - public long getSnapshotTimeout() { - return snapshotTimeout; - } + Entity entityType = new Entity( + actorName, + actor.getClass(), + getKind(kind), + actor.getStateType(), + actorName, + actor.isStateful(), + deactivateTimeout, + snapshotTimeout, + actions, + timerActions, + 0, + 0, + channel, + Optional.empty(), + Optional.empty()); - public Map getActions() { - return actions; + return entityType; } - public Map getTimerActions() { - return timerActions; - } + private static Entity buildNamedActor(Class stateType, StatelessActor actor, NamedActorBehavior behavior) { + String actorName = behavior.getName(); + ActorKind kind = behavior.getActorType(); + String channel = behavior.getChannel(); + long deactivateTimeout = behavior.getDeactivatedTimeout(); + long snapshotTimeout = behavior.getSnapshotTimeout(); - public int getMinPoolSize() { - return minPoolSize; - } + final Map actions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + ActionEnvelope envelope = entry.getValue(); - public int getMaxPoolSize() { - return maxPoolSize; - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - public String getChannel(){ return channel; } + final Map timerActions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + int timer = entry.getValue().getConfig().getTimer(); - public enum EntityMethodType { - DIRECT, TIMER - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, timer, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - public Optional getActorArg() { - return actorArg; - } + Entity entityType = new Entity( + actorName, + actor.getClass(), + getKind(kind), + null, + actorName, + actor.isStateful(), + deactivateTimeout, + snapshotTimeout, + actions, + timerActions, + 0, + 0, + channel, + Optional.empty(), + Optional.empty()); - public Optional getActorFactory() { - return actorFactory; + return entityType; } - public static final class EntityMethod { - private String name; + private static Entity buildUnNamedActor(Class stateType, StatefulActor actor, UnNamedActorBehavior behavior) { + String actorName = behavior.getName(); + ActorKind kind = behavior.getActorType(); + String channel = behavior.getChannel(); + long deactivateTimeout = behavior.getDeactivatedTimeout(); + long snapshotTimeout = behavior.getSnapshotTimeout(); - private EntityMethodType type; + final Map actions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + ActionEnvelope envelope = entry.getValue(); - private int fixedPeriod; + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - private Method method; - private Class inputType; - private Class outputType; + final Map timerActions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + int timer = entry.getValue().getConfig().getTimer(); - public EntityMethod( - String name, EntityMethodType type, int fixedPeriod, Method method, Class inputType, Class outputType) { - this.name = name; - this.type = type; - this.fixedPeriod = fixedPeriod; - this.method = method; - this.inputType = inputType; - this.outputType = outputType; - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.TIMER, timer, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - public String getName() { - return name; - } + Entity entityType = new Entity( + actorName, + actor.getClass(), + getKind(kind), + actor.getStateType(), + actorName, + actor.isStateful(), + deactivateTimeout, + snapshotTimeout, + actions, + timerActions, + 0, + 0, + channel, + Optional.empty(), + Optional.empty()); - public EntityMethodType getType() { - return type; - } + return entityType; + } - public int getFixedPeriod() { - return fixedPeriod; - } + private static Entity buildUnNamedActor(Class stateType, StatelessActor actor, UnNamedActorBehavior behavior) { + String actorName = behavior.getName(); + ActorKind kind = behavior.getActorType(); + String channel = behavior.getChannel(); + long deactivateTimeout = behavior.getDeactivatedTimeout(); + long snapshotTimeout = behavior.getSnapshotTimeout(); - public Method getMethod() { - return method; - } + final Map actions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + ActionEnvelope envelope = entry.getValue(); - public Class getInputType() { - int arity = method.getParameterTypes().length; + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (arity == 2 && Objects.isNull(inputType)) { - for (Class parameterType : method.getParameterTypes()) { - if (!inputType.isAssignableFrom(ActorContext.class)) { - return parameterType; - } - } - } - return inputType; - } + final Map timerActions = behavior.getActions() + .entrySet() + .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) + .map(entry -> { + String actionName = entry.getKey(); + int timer = entry.getValue().getConfig().getTimer(); - public Class getOutputType() { - return outputType; - } + return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.TIMER, timer, null, null, null)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("EntityMethod{"); - sb.append("name='").append(name).append('\''); - sb.append(", type=").append(type); - sb.append(", fixedPeriod=").append(fixedPeriod); - sb.append(", method=").append(method); - sb.append(", inputType=").append(inputType); - sb.append(", outputType=").append(outputType); - sb.append('}'); - return sb.toString(); - } - } + Entity entityType = new Entity( + actorName, + actor.getClass(), + getKind(kind), + null, + actorName, + actor.isStateful(), + deactivateTimeout, + snapshotTimeout, + actions, + timerActions, + 0, + 0, + channel, + Optional.empty(), + Optional.empty()); - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("Entity{"); - sb.append("actorName='").append(actorName).append('\''); - sb.append(", actorType=").append(actorType); - sb.append(", kind=").append(kind); - sb.append(", stateType=").append(stateType); - sb.append(", actorBeanName='").append(actorBeanName).append('\''); - sb.append(", stateful=").append(stateful); - sb.append(", deactivateTimeout=").append(deactivateTimeout); - sb.append(", snapshotTimeout=").append(snapshotTimeout); - sb.append(", actions=").append(actions); - sb.append(", timerActions=").append(timerActions); - sb.append(", minPoolSize=").append(minPoolSize); - sb.append(", maxPoolSize=").append(maxPoolSize); - sb.append(", channel=").append(channel); - sb.append('}'); - return sb.toString(); + return entityType; } - public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class entity) { - return null; - } - public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class entity) { - return null; - } public static Entity fromAnnotationToEntity(Class entity, StatefulNamedActor actor, Object arg, ActorFactory factory) { String actorBeanName = entity.getSimpleName(); @@ -597,7 +659,7 @@ private static Class getOutputType(Method method, Class private static ActorOuterClass.Kind getKind(ActorKind kind) { switch (kind) { case UNNAMED: - return ActorOuterClass.Kind.UNAMED; + return ActorOuterClass.Kind.UNNAMED; case POOLED: return ActorOuterClass.Kind.POOLED; case PROXY: @@ -606,4 +668,160 @@ private static ActorOuterClass.Kind getKind(ActorKind kind) { return ActorOuterClass.Kind.NAMED; } } + + public String getActorName() { + return actorName; + } + + public void setActorName(String actorName) { + this.actorName = actorName; + } + + public Class getActorType() { + return actorType; + } + + public ActorOuterClass.Kind getKind() { + return kind; + } + + public Class getStateType() { + return stateType; + } + + public String getActorBeanName() { + return actorBeanName; + } + + public boolean isStateful() { + return stateful; + } + + public long getDeactivateTimeout() { + return deactivateTimeout; + } + + public long getSnapshotTimeout() { + return snapshotTimeout; + } + + public Map getActions() { + return actions; + } + + public Map getTimerActions() { + return timerActions; + } + + public int getMinPoolSize() { + return minPoolSize; + } + + public int getMaxPoolSize() { + return maxPoolSize; + } + + public String getChannel() { + return channel; + } + + public Optional getActorArg() { + return actorArg; + } + + public Optional getActorFactory() { + return actorFactory; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Entity{"); + sb.append("actorName='").append(actorName).append('\''); + sb.append(", actorType=").append(actorType); + sb.append(", kind=").append(kind); + sb.append(", stateType=").append(stateType); + sb.append(", actorBeanName='").append(actorBeanName).append('\''); + sb.append(", stateful=").append(stateful); + sb.append(", deactivateTimeout=").append(deactivateTimeout); + sb.append(", snapshotTimeout=").append(snapshotTimeout); + sb.append(", actions=").append(actions); + sb.append(", timerActions=").append(timerActions); + sb.append(", minPoolSize=").append(minPoolSize); + sb.append(", maxPoolSize=").append(maxPoolSize); + sb.append(", channel=").append(channel); + sb.append('}'); + return sb.toString(); + } + + public enum EntityMethodType { + DIRECT, TIMER + } + + public static final class EntityMethod { + private String name; + + private EntityMethodType type; + + private int fixedPeriod; + + private Method method; + private Class inputType; + private Class outputType; + + public EntityMethod( + String name, EntityMethodType type, int fixedPeriod, Method method, Class inputType, Class outputType) { + this.name = name; + this.type = type; + this.fixedPeriod = fixedPeriod; + this.method = method; + this.inputType = inputType; + this.outputType = outputType; + } + + public String getName() { + return name; + } + + public EntityMethodType getType() { + return type; + } + + public int getFixedPeriod() { + return fixedPeriod; + } + + public Method getMethod() { + return method; + } + + public Class getInputType() { + int arity = method.getParameterTypes().length; + + if (arity == 2 && Objects.isNull(inputType)) { + for (Class parameterType : method.getParameterTypes()) { + if (!inputType.isAssignableFrom(ActorContext.class)) { + return parameterType; + } + } + } + return inputType; + } + + public Class getOutputType() { + return outputType; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("EntityMethod{"); + sb.append("name='").append(name).append('\''); + sb.append(", type=").append(type); + sb.append(", fixedPeriod=").append(fixedPeriod); + sb.append(", method=").append(method); + sb.append(", inputType=").append(inputType); + sb.append(", outputType=").append(outputType); + sb.append('}'); + return sb.toString(); + } + } } \ No newline at end of file diff --git a/src/main/proto/eigr/functions/protocol/actors/actor.proto b/src/main/proto/eigr/functions/protocol/actors/actor.proto index 10df44c..e4c2157 100644 --- a/src/main/proto/eigr/functions/protocol/actors/actor.proto +++ b/src/main/proto/eigr/functions/protocol/actors/actor.proto @@ -66,42 +66,51 @@ message ActorState { google.protobuf.Any state = 2; } -// TODO doc here +// Metadata represents a set of key-value pairs that can be used to +// provide additional information about an Actor. message Metadata { - // A channel group represents a way to send actions to various actors - // that belong to a certain semantic group. - string channel_group = 1; + // A channel group represents a way to send actions to various actors + // that belong to a certain semantic group. Following the Pub-Sub pattern. + repeated Channel channel_group = 1; map tags = 2; } +// Represents a Pub-Sub binding, where a actor can be subscribed to a channel +// and map a specific action to a specific topic if necessary +// if the action is not informed, the default action will be "receive". +message Channel { + string topic = 1; + string action = 2; +} + // The type that defines the runtime characteristics of the Actor. -// Regardless of the type of actor it is important that +// Regardless of the type of actor it is important that // all actors are registered during the proxy and host initialization phase. enum Kind { // When no type is informed, the default to be assumed will be the Named pattern. UNKNOW_KIND = 0; - // NAMED actors are used to create children of this based actor at runtime + // NAMED actors as the name suggests have only one real instance of themselves running + // during their entire lifecycle. That is, they are the opposite of the UNNAMED type Actors. NAMED = 1; - // UNAMED actors as the name suggests have only one real instance of themselves running - // during their entire lifecycle. That is, they are the opposite of the NAMED type Actors. - UNAMED = 2; - - // Pooled Actors are similar to Unamed actors, but unlike them, - // their identifying name will always be the one registered at the system initialization stage. - // The great advantage of Pooled actors is that they have multiple instances of themselves + // UNNAMED actors are used to create children of this based actor at runtime + UNNAMED = 2; + + // Pooled Actors are similar to Unnamed actors, but unlike them, + // their identifying name will always be the one registered at the system initialization stage. + // The great advantage of Pooled actors is that they have multiple instances of themselves // acting as a request service pool. - // Pooled actors are also stateless actors, that is, they will not have their - // in-memory state persisted via Statesstore. This is done to avoid problems + // Pooled actors are also stateless actors, that is, they will not have their + // in-memory state persisted via Statesstore. This is done to avoid problems // with the correctness of the stored state. - // Pooled Actors are generally used for tasks where the Actor Model would perform worse - // than other concurrency models and for tasks that do not require state concerns. - // Integration flows, data caching, proxies are good examples of use cases + // Pooled Actors are generally used for tasks where the Actor Model would perform worse + // than other concurrency models and for tasks that do not require state concerns. + // Integration flows, data caching, proxies are good examples of use cases // for this type of Actor. POOLED = 3; - + // Reserved for future use PROXY = 4; } @@ -134,7 +143,7 @@ message ActorId { // Name of a ActorSystem string system = 2; - // When the Actor is of the Unamed type, + // When the Actor is of the Unnamed type, // the name of the parent Actor must be informed here. string parent = 3; } diff --git a/src/main/proto/eigr/functions/protocol/actors/protocol.proto b/src/main/proto/eigr/functions/protocol/actors/protocol.proto index 28ec777..fec7e03 100644 --- a/src/main/proto/eigr/functions/protocol/actors/protocol.proto +++ b/src/main/proto/eigr/functions/protocol/actors/protocol.proto @@ -253,7 +253,7 @@ message ProxyInfo { int32 protocol_minor_version = 2; string proxy_name = 3; - + string proxy_version = 4; } @@ -274,12 +274,9 @@ message SideEffect { // https://www.enterpriseintegrationpatterns.com/patterns/messaging/PublishSubscribeChannel.html // https://www.enterpriseintegrationpatterns.com/patterns/messaging/BroadcastAggregate.html message Broadcast { - // Channel of target Actors + // Target topic or channel string channel_group = 1; - // Action. Only Actors that have this action will run successfully - string action_name = 2; - // Payload oneof payload { google.protobuf.Any value = 3; @@ -291,27 +288,27 @@ message Broadcast { // Useful for handle `pipes` pattern: // https://www.enterpriseintegrationpatterns.com/patterns/messaging/PipesAndFilters.html message Pipe { - // Target Actor - string actor = 1; + // Target Actor + string actor = 1; - // Action. - string action_name = 2; + // Action. + string action_name = 2; } // Sends the input of a action of an Actor to the input of another action of an Actor // Useful for handle `content-basead router` pattern // https://www.enterpriseintegrationpatterns.com/patterns/messaging/ContentBasedRouter.html message Forward { - // Target Actor - string actor = 1; + // Target Actor + string actor = 1; - // Action. - string action_name = 2; + // Action. + string action_name = 2; } -// Container for archicetural message patterns +// Container for archicetural message patterns message Workflow { - + Broadcast broadcast = 2; repeated SideEffect effects = 1; @@ -328,12 +325,13 @@ message Workflow { // * system: See ActorSystem message. // * actor: The target Actor, i.e. the one that the user function is calling to perform some computation. // * caller: The caller Actor -// * action_name: The function or method on the target Actor that will receive this request +// * action_name: The function or method on the target Actor that will receive this request // and perform some useful computation with the sent data. // * value: This is the value sent by the user function to be computed by the request's target Actor action. -// * async: Indicates whether the action should be processed synchronously, where a response should be sent back to the user function, +// * async: Indicates whether the action should be processed synchronously, where a response should be sent back to the user function, // or whether the action should be processed asynchronously, i.e. no response sent to the caller and no waiting. // * metadata: Meta information or headers +// * register_ref: If the invocation should register the specific actor with the given name without having to call register before message InvocationRequest { eigr.functions.protocol.actors.ActorSystem system = 1; diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 839ee40..46c7f1f 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -31,7 +31,7 @@ public void before() throws Exception { spawnSystem = new Spawn.SpawnSystem() .create("spawn-system", injector) .withActor(JoeActor.class) - .withActor(ActorWithConstructor.class) + //.withActor(ActorWithConstructor.class) .withTransportOptions( TransportOpts.builder() .port(8091) @@ -40,6 +40,7 @@ public void before() throws Exception { ) .build(); + System.out.println("Starting ActorSystem..."); spawnSystem.start(); } @@ -55,7 +56,7 @@ public void testApp() throws ActorCreationException, ActorInvocationException { .build(); Optional maybeReply = - joeActor.invoke("setLanguage", msg, Actor.Reply.class); + joeActor.invoke("SetLanguage", msg, Actor.Reply.class); if (maybeReply.isPresent()) { Actor.Reply reply = maybeReply.get(); diff --git a/src/test/java/io/eigr/spawn/WorkflowTest.java b/src/test/java/io/eigr/spawn/WorkflowTest.java index 213ddb4..bb1d3eb 100644 --- a/src/test/java/io/eigr/spawn/WorkflowTest.java +++ b/src/test/java/io/eigr/spawn/WorkflowTest.java @@ -42,7 +42,7 @@ public void before() throws SpawnException { public void testBroadcastBuilder() { Broadcast broadcast = Broadcast.to("test.channel", "hi", Actor.Request.getDefaultInstance()); final Protocol.Broadcast protocolBroadcast = broadcast.build(); - assertEquals("hi", protocolBroadcast.getActionName()); + assertEquals("hi", protocolBroadcast.getChannelGroup()); assertEquals("test.channel", protocolBroadcast.getChannelGroup()); assertNotNull(protocolBroadcast.getValue()); } From 02b08b896aef3132325623da7435e21654e7f82a Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Wed, 24 Jul 2024 18:53:50 -0300 Subject: [PATCH 07/19] Stateful Actors new api --- src/main/java/io/eigr/spawn/api/Spawn.java | 50 +- .../eigr/spawn/api/actors/ActorFactory.java | 6 - .../spawn/api/actors/annotations/Action.java | 35 - .../actors/annotations/SpawnAnnotation.java | 10 - .../api/actors/annotations/TimerAction.java | 37 -- .../stateful/StatefulNamedActor.java | 24 - .../stateful/StatefulPooledActor.java | 28 - .../stateful/StatefulUnNamedActor.java | 24 - .../stateless/StatelessNamedActor.java | 19 - .../stateless/StatelessPooledActor.java | 22 - .../stateless/StatelessUnNamedActor.java | 18 - .../api/actors/behaviors/ActorBehavior.java | 75 ++- .../internal/ActionArgumentFunction.java | 11 - .../eigr/spawn/internal/ActionBindings.java | 37 ++ .../spawn/internal/ActionConfiguration.java | 28 +- .../eigr/spawn/internal/ActionEnvelope.java | 8 +- ...entFunction.java => ActionNoBindings.java} | 3 +- .../java/io/eigr/spawn/internal/Entity.java | 622 ++++-------------- .../transport/server/ActorServiceHandler.java | 45 +- src/test/java/io/eigr/spawn/EntityTest.java | 20 - src/test/java/io/eigr/spawn/SpawnTest.java | 8 +- .../test/actors/ActorWithConstructor.java | 10 +- .../io/eigr/spawn/test/actors/JoeActor.java | 18 +- .../eigr/functions/java/sdk/test/actor.proto | 8 + 24 files changed, 323 insertions(+), 843 deletions(-) delete mode 100644 src/main/java/io/eigr/spawn/api/actors/ActorFactory.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/Action.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/SpawnAnnotation.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/TimerAction.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulNamedActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulPooledActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulUnNamedActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessNamedActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessPooledActor.java delete mode 100644 src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessUnNamedActor.java delete mode 100644 src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java create mode 100644 src/main/java/io/eigr/spawn/internal/ActionBindings.java rename src/main/java/io/eigr/spawn/internal/{ActionNoArgumentFunction.java => ActionNoBindings.java} (55%) delete mode 100644 src/test/java/io/eigr/spawn/EntityTest.java diff --git a/src/main/java/io/eigr/spawn/api/Spawn.java b/src/main/java/io/eigr/spawn/api/Spawn.java index 25e3d76..e641fad 100644 --- a/src/main/java/io/eigr/spawn/api/Spawn.java +++ b/src/main/java/io/eigr/spawn/api/Spawn.java @@ -5,16 +5,9 @@ import com.sun.net.httpserver.HttpServer; import io.eigr.functions.protocol.Protocol; import io.eigr.functions.protocol.actors.ActorOuterClass; -import io.eigr.spawn.api.actors.ActorFactory; import io.eigr.spawn.api.actors.BaseActor; import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.StatelessActor; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulPooledActor; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulUnNamedActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessPooledActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessUnNamedActor; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.exceptions.ActorCreationException; import io.eigr.spawn.api.exceptions.ActorRegistrationException; @@ -199,14 +192,11 @@ private void registerActorSystem() throws ActorRegistrationException { .build(); log.debug("Registering Actors on Proxy. Registry: {}", req); - System.out.println(String.format("Registering Actors on Proxy. Registry: %s", req)); this.client.register(req); } private Map getActors(List entities) { - System.out.println(String.format("Entities %s", entities.size())); return entities.stream().map(actorEntity -> { - System.out.println(String.format("Register Actor Entity %s", actorEntity.getActorName())); ActorOuterClass.ActorSnapshotStrategy snapshotStrategy; if (actorEntity.isStateful()) { snapshotStrategy = ActorOuterClass.ActorSnapshotStrategy.newBuilder() @@ -234,13 +224,17 @@ private Map getActors(List entities) { .setMaxPoolSize(actorEntity.getMaxPoolSize()) .build(); - Map tags = new HashMap<>(); - ActorOuterClass.Metadata metadata = ActorOuterClass.Metadata.newBuilder() - .addChannelGroup( - ActorOuterClass.Channel.newBuilder() - .setTopic(actorEntity.getChannel()) - .build()) - .build(); + final Map tags = new HashMap<>(); + final ActorOuterClass.Metadata.Builder metadataBuilder = ActorOuterClass.Metadata.newBuilder(); + + if (Objects.nonNull(actorEntity.getChannel())){ + metadataBuilder.addChannelGroup(ActorOuterClass.Channel.newBuilder() + .setTopic(actorEntity.getChannel()) + .build()); + } + + metadataBuilder.putAllTags(tags); + final ActorOuterClass.Metadata metadata = metadataBuilder.build(); return ActorOuterClass.Actor.newBuilder() .setId(ActorOuterClass.ActorId.newBuilder() @@ -258,19 +252,23 @@ private Map getActors(List entities) { } private List getActions(Entity actorEntity) { - return actorEntity.getActions().values().stream() - .filter(v -> Entity.EntityMethodType.DIRECT.equals(v.getType())) - .map(action -> ActorOuterClass.Action.newBuilder().setName(action.getName()).build()) + return (List) actorEntity.getActions().values().stream() + .filter(v -> Entity.EntityMethodType.DIRECT.equals(((Entity.EntityMethod)v).getType())) + .map(action -> ActorOuterClass.Action.newBuilder() + .setName(((Entity.EntityMethod)action).getName()) + .build() + ) .collect(Collectors.toList()); } private List getTimerActions(Entity actorEntity) { - List timerActions = actorEntity.getTimerActions().values() - .stream().filter(v -> Entity.EntityMethodType.TIMER.equals(v.getType())) + List timerActions = + (List) actorEntity.getTimerActions().values() + .stream().filter(v -> Entity.EntityMethodType.TIMER.equals(((Entity.EntityMethod)v).getType())) .map(action -> ActorOuterClass.FixedTimerAction.newBuilder() .setAction(ActorOuterClass.Action.newBuilder() - .setName(action.getName()).build()) - .setSeconds(action.getFixedPeriod()).build()) + .setName(((Entity.EntityMethod)action).getName()).build()) + .setSeconds(((Entity.EntityMethod)action).getFixedPeriod()).build()) .collect(Collectors.toList()); log.debug("Actor have TimeActions: {}", timerActions); @@ -391,18 +389,14 @@ private Optional getEntity(Class actorKlass) throws ActorCreationExce } private Optional mapEntity(Class actorKlass) throws ActorCreationException { - System.out.println(String.format("Klass %s", actorKlass.getSuperclass())); if (StatefulActor.class.isAssignableFrom(actorKlass)) { - System.out.println("StatefulActor"); return Optional.of(Entity.fromStatefulActorToEntity(ctx, actorKlass)); } if (StatelessActor.class.isAssignableFrom(actorKlass)) { - System.out.println("StatelessActor"); return Optional.of(Entity.fromStatelessActorToEntity(ctx, actorKlass)); } - System.out.println("empty"); return Optional.empty(); } } diff --git a/src/main/java/io/eigr/spawn/api/actors/ActorFactory.java b/src/main/java/io/eigr/spawn/api/actors/ActorFactory.java deleted file mode 100644 index e208167..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/ActorFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.eigr.spawn.api.actors; - -@FunctionalInterface -public interface ActorFactory { - Object newInstance(Object arg); -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/Action.java b/src/main/java/io/eigr/spawn/api/actors/annotations/Action.java deleted file mode 100644 index 063b8a5..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/Action.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.eigr.spawn.api.actors.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@SpawnAnnotation -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Action { - - /** - * The name of the command to handle. - * - *

If not specified, the name of the method will be used as the command name. - * - * @return The command name. - */ - String name() default ""; - - /** - * * The input type. - * - * The type class of input method parameter. Generally, this will be determined by looking at the parameter of the input type - * handler method, however if the event doesn't need to be passed to the method (for example, - * perhaps it contains no data), then this can be used to indicate which event this handler - * handles. - */ - Class inputType() default Default.class; - - Class outputType() default Default.class; - - class Default {} -} \ No newline at end of file diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/SpawnAnnotation.java b/src/main/java/io/eigr/spawn/api/actors/annotations/SpawnAnnotation.java deleted file mode 100644 index 00eff94..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/SpawnAnnotation.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.eigr.spawn.api.actors.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -@Target(ElementType.ANNOTATION_TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface SpawnAnnotation { -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/TimerAction.java b/src/main/java/io/eigr/spawn/api/actors/annotations/TimerAction.java deleted file mode 100644 index b437ca3..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/TimerAction.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.eigr.spawn.api.actors.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@SpawnAnnotation -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface TimerAction { - - /** - * The name of the action to handle. - * - *

If not specified, the name of the method will be used as the command name. - * - * @return The action name. - */ - String name() default ""; - - int period() default 0; - - /** - * * The input type. - * - * The type class of input method parameter. Generally, this will be determined by looking at the parameter of the input type - * handler method, however if the event doesn't need to be passed to the method (for example, - * perhaps it contains no data), then this can be used to indicate which event this handler - * handles. - */ - Class inputType() default Default.class; - - Class outputType() default Default.class; - - class Default {} -} \ No newline at end of file diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulNamedActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulNamedActor.java deleted file mode 100644 index 20fe253..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulNamedActor.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateful; - -import com.google.protobuf.GeneratedMessage; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatefulNamedActor { - String value() default ""; - - String name() default ""; - - Class stateType(); - - long deactivatedTimeout() default 60000; - - long snapshotTimeout() default 50000; - - String channel() default ""; -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulPooledActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulPooledActor.java deleted file mode 100644 index 017ede0..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulPooledActor.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateful; - -import com.google.protobuf.GeneratedMessage; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatefulPooledActor { - String value() default ""; - - String name() default ""; - - Class stateType(); - - long deactivatedTimeout() default 60000; - - long snapshotTimeout() default 50000; - - String channel() default ""; - - int minPoolSize() default 1; - - int maxPoolSize() default 0; -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulUnNamedActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulUnNamedActor.java deleted file mode 100644 index 681d35f..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateful/StatefulUnNamedActor.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateful; - -import com.google.protobuf.GeneratedMessage; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatefulUnNamedActor { - String value() default ""; - - String name() default ""; - - Class stateType(); - - long deactivatedTimeout() default 60000; - - long snapshotTimeout() default 50000; - - String channel() default ""; -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessNamedActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessNamedActor.java deleted file mode 100644 index 3cf9a10..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessNamedActor.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateless; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatelessNamedActor { - String value() default ""; - - String name() default ""; - - long deactivatedTimeout() default 10000; - - String channel() default ""; - -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessPooledActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessPooledActor.java deleted file mode 100644 index 1426519..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessPooledActor.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateless; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatelessPooledActor { - String value() default ""; - - String name() default ""; - - long deactivatedTimeout() default 10000; - - String channel() default ""; - - int minPoolSize() default 1; - - int maxPoolSize() default 0; -} diff --git a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessUnNamedActor.java b/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessUnNamedActor.java deleted file mode 100644 index a5a0f3d..0000000 --- a/src/main/java/io/eigr/spawn/api/actors/annotations/stateless/StatelessUnNamedActor.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.eigr.spawn.api.actors.annotations.stateless; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface StatelessUnNamedActor { - String value() default ""; - - String name() default ""; - - long deactivatedTimeout() default 10000; - - String channel() default ""; -} diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index 3fb83a8..8d30c21 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -1,11 +1,13 @@ package io.eigr.spawn.api.actors.behaviors; import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Message; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; -import io.eigr.spawn.api.exceptions.ActorNotFoundException; +import io.eigr.spawn.api.exceptions.ActorInvocationException; import io.eigr.spawn.internal.*; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -22,7 +24,10 @@ public abstract class ActorBehavior { private final Map actions = new HashMap<>(); public ActorBehavior(ActorOption... options) { - Optional.ofNullable(options).map(Stream::of).orElseGet(Stream::empty).forEach(option -> option.accept(this)); + Optional.ofNullable(options) + .stream() + .flatMap(Stream::of) + .forEach(option -> option.accept(this)); } public static ActorOption name(String actorName) { @@ -41,24 +46,57 @@ public static ActorOption snapshot(long timeout) { return instance -> instance.snapshotTimeout = timeout; } - public static ActorOption init(ActionNoArgumentFunction action) { - return instance -> instance.actions.put("Init", new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); + public static ActorOption init(ActionNoBindings action) { + return instance -> instance.actions.put( + "Init", + new ActionEnvelope( + action, + new ActionConfiguration(ActionKind.NORMAL_DISPATCH, 0, null, null))); } - public static ActorOption action(String name, ActionNoArgumentFunction action) { - return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); + public static ActorOption action(String name, ActionNoBindings action) { + final Method method = action.getClass().getDeclaredMethods()[0]; + final Class outputType = method.getReturnType(); + return instance -> instance.actions.put( + name, + new ActionEnvelope( + action, + new ActionConfiguration(ActionKind.NORMAL_DISPATCH, 0, null, outputType))); } - public static ActorOption action(String name, ActionArgumentFunction action) { - return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.NORMAL_DISPATCH))); + public static ActorOption action(String name, ActionBindings action) { + final Method method = action.getClass().getDeclaredMethods()[0]; + + final Class inputType = action.getArgumentType(); + final Class outputType = Value.class; + + return instance -> instance.actions.put( + name, + new ActionEnvelope( + action, + new ActionConfiguration(ActionKind.NORMAL_DISPATCH, 1, inputType, outputType))); } - public static ActorOption timerAction(String name, int timer, ActionNoArgumentFunction action) { - return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer))); + public static ActorOption timerAction(String name, int timer, ActionNoBindings action) { + final Method method = action.getClass().getDeclaredMethods()[0]; + final Class outputType = method.getReturnType(); + + return instance -> instance.actions.put( + name, new ActionEnvelope( + action, + new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer, 0, null, outputType))); } - public static ActorOption timerAction(String name, int timer, ActionArgumentFunction action) { - return instance -> instance.actions.put(name, new ActionEnvelope(action, new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer))); + public static ActorOption timerAction(String name, int timer, ActionBindings action) { + final Method method = action.getClass().getDeclaredMethods()[0]; + final Class inputType = method.getParameterTypes()[1]; + final Class outputType = method.getReturnType(); + + return instance -> instance.actions.put( + name, + new ActionEnvelope( + action, + new ActionConfiguration(ActionKind.TIMER_DISPATCH, timer, 1, inputType, outputType))); } public abstract ActorKind getActorType(); @@ -91,23 +129,22 @@ public Map getActions() { return actions; } - public Value call(String action, ActorContext context) throws ActorNotFoundException { + public Value call(String action, ActorContext context) throws ActorInvocationException { if (this.actions.containsKey(action)) { - ((ActionNoArgumentFunction) this.actions.get(action).getFunction()).handle(context); + return ((ActionNoBindings) this.actions.get(action).getFunction()).handle(context); } - throw new ActorNotFoundException(String.format("Action [%s] not found for Actor [%s]", action, name)); + throw new ActorInvocationException(String.format("Action [%s] not found for Actor [%s]", action, name)); } - public Value call(String action, ActorContext context, A argument) throws ActorNotFoundException { + public Value call(String action, ActorContext context, A argument) throws ActorInvocationException { if (this.actions.containsKey(action)) { - ((ActionArgumentFunction) this.actions.get(action).getFunction()).handle(context, argument); + return ((ActionBindings) this.actions.get(action).getFunction()).handle(context, argument); } - throw new ActorNotFoundException(String.format("Action [%s] not found for Actor [%s]", action, name)); + throw new ActorInvocationException(String.format("Action [%s] not found for Actor [%s]", action, name)); } public interface ActorOption extends Consumer { } - } diff --git a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java deleted file mode 100644 index d004bce..0000000 --- a/src/main/java/io/eigr/spawn/internal/ActionArgumentFunction.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.eigr.spawn.internal; - -import com.google.protobuf.GeneratedMessage; -import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.Value; - -@FunctionalInterface -public interface ActionArgumentFunction extends ActionEmptyFunction { - - Value handle(ActorContext context, A argument); -} diff --git a/src/main/java/io/eigr/spawn/internal/ActionBindings.java b/src/main/java/io/eigr/spawn/internal/ActionBindings.java new file mode 100644 index 0000000..b2da5c1 --- /dev/null +++ b/src/main/java/io/eigr/spawn/internal/ActionBindings.java @@ -0,0 +1,37 @@ +package io.eigr.spawn.internal; + +import com.google.protobuf.Message; +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.Value; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +@FunctionalInterface +public interface ActionBindings extends ActionEmptyFunction { + + Value handle(ActorContext context, A argument); + + default Class getArgumentType() { + Type type = ((ParameterizedType) getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; + if (type instanceof Class) { + return (Class) type; + } else { + throw new IllegalStateException("Unable to determine generic type A."); + } + } + + static ActionBindings of(Class type, ActionBindings function) { + return new ActionBindings() { + @Override + public Value handle(ActorContext context, A argument) { + return function.handle(context, argument); + } + + @Override + public Class getArgumentType() { + return type; + } + }; + } +} diff --git a/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java b/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java index 6ba32c3..7666d0d 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java +++ b/src/main/java/io/eigr/spawn/internal/ActionConfiguration.java @@ -6,13 +6,25 @@ public final class ActionConfiguration { private final ActionKind kind; private int timer; - public ActionConfiguration(ActionKind kind) { + private int arity; + + private Class inputType; + + private Class outputType; + + public ActionConfiguration(ActionKind kind, int arity, Class inputType, Class outputType) { this.kind = kind; + this.arity = arity; + this.inputType = inputType; + this.outputType = outputType; } - public ActionConfiguration(ActionKind kind, int timer) { + public ActionConfiguration(ActionKind kind, int timer, int arity, Class inputType, Class outputType) { this.kind = kind; this.timer = timer; + this.arity = arity; + this.inputType = inputType; + this.outputType = outputType; } public ActionKind getKind() { @@ -23,6 +35,14 @@ public int getTimer() { return timer; } + public Class getInputType() { + return inputType; + } + + public Class getOutputType() { + return outputType; + } + @Override public String toString() { return new StringJoiner(", ", ActionConfiguration.class.getSimpleName() + "[", "]") @@ -30,4 +50,8 @@ public String toString() { .add("timer=" + timer) .toString(); } + + public int getArity() { + return this.arity; + } } diff --git a/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java b/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java index 9a85bf7..476cb84 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java +++ b/src/main/java/io/eigr/spawn/internal/ActionEnvelope.java @@ -2,17 +2,17 @@ import java.util.StringJoiner; -public final class ActionEnvelope { +public final class ActionEnvelope { - private final ActionEmptyFunction function; + private final F function; private final ActionConfiguration config; - public ActionEnvelope(ActionEmptyFunction function, ActionConfiguration config) { + public ActionEnvelope(F function, ActionConfiguration config) { this.function = function; this.config = config; } - public ActionEmptyFunction getFunction() { + public F getFunction() { return function; } diff --git a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java b/src/main/java/io/eigr/spawn/internal/ActionNoBindings.java similarity index 55% rename from src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java rename to src/main/java/io/eigr/spawn/internal/ActionNoBindings.java index 1d90239..3afcbea 100644 --- a/src/main/java/io/eigr/spawn/internal/ActionNoArgumentFunction.java +++ b/src/main/java/io/eigr/spawn/internal/ActionNoBindings.java @@ -1,11 +1,10 @@ package io.eigr.spawn.internal; -import com.google.protobuf.GeneratedMessage; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.Value; @FunctionalInterface -public interface ActionNoArgumentFunction extends ActionEmptyFunction { +public interface ActionNoBindings extends ActionEmptyFunction { Value handle(ActorContext context); } diff --git a/src/main/java/io/eigr/spawn/internal/Entity.java b/src/main/java/io/eigr/spawn/internal/Entity.java index 0e35857..609e0a4 100644 --- a/src/main/java/io/eigr/spawn/internal/Entity.java +++ b/src/main/java/io/eigr/spawn/internal/Entity.java @@ -1,18 +1,9 @@ package io.eigr.spawn.internal; import io.eigr.functions.protocol.actors.ActorOuterClass; -import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.ActorFactory; +import io.eigr.spawn.api.actors.BaseActor; import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.StatelessActor; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.TimerAction; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulPooledActor; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulUnNamedActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessPooledActor; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessUnNamedActor; import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; @@ -21,21 +12,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.AbstractMap; +import java.util.Map; import java.util.stream.Collectors; -public final class Entity { +public final class Entity { private static final Logger log = LoggerFactory.getLogger(Entity.class); - private String actorName; - private Class actorType; - private Optional actorArg; + private A actor; - private Optional actorFactory; + private B behavior; + + private BehaviorCtx ctx; + private String actorName; + private Class actorType; private ActorOuterClass.Kind kind; @@ -56,6 +49,9 @@ public final class Entity { private String channel; public Entity( + BehaviorCtx ctx, + A actor, + B behavior, String actorName, Class actorType, ActorOuterClass.Kind kind, @@ -68,9 +64,10 @@ public Entity( Map timerActions, int minPoolSize, int maxPoolSize, - String channel, - Optional actorArg, - Optional actorFactory) { + String channel) { + this.ctx = ctx; + this.actor = actor; + this.behavior = behavior; this.actorName = actorName; this.actorType = actorType; this.kind = kind; @@ -84,8 +81,6 @@ public Entity( this.minPoolSize = minPoolSize; this.maxPoolSize = maxPoolSize; this.channel = channel; - this.actorArg = actorArg; - this.actorFactory = actorFactory; } public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class actor) { @@ -100,13 +95,12 @@ public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class actor) ActorBehavior behavior = stActor.configure(ctx); if (behavior.getClass().isAssignableFrom(NamedActorBehavior.class)) { - Entity entity = buildNamedActor(stateType, stActor, (NamedActorBehavior) behavior); - System.out.println(String.format("Stateful NamedActorBehavior %s", entity)); + Entity entity = buildNamedActor(stateType, stActor, (NamedActorBehavior) behavior, ctx); return entity; } if (behavior.getClass().isAssignableFrom(UnNamedActorBehavior.class)) { - return buildUnNamedActor(stateType, stActor, (UnNamedActorBehavior) behavior); + return buildUnNamedActor(stateType, stActor, (UnNamedActorBehavior) behavior, ctx); } } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | @@ -117,38 +111,68 @@ public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class actor) throw new ActorCreationException(); } - private static Entity buildNamedActor(Class stateType, StatefulActor actor, NamedActorBehavior behavior) { - String actorName = behavior.getName(); - ActorKind kind = behavior.getActorType(); - String channel = behavior.getChannel(); - long deactivateTimeout = behavior.getDeactivatedTimeout(); - long snapshotTimeout = behavior.getSnapshotTimeout(); - - final Map actions = behavior.getActions() + private static Map getActions(Map actions) { + return actions .entrySet() .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) .map(entry -> { - String actionName = entry.getKey(); - ActionEnvelope envelope = entry.getValue(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + final String actionName = entry.getKey(); + final ActionEnvelope envelope = entry.getValue(); + final ActionConfiguration config = envelope.getConfig(); + + return new AbstractMap.SimpleEntry<>( + actionName, + new Entity.EntityMethod( + actionName, + EntityMethodType.DIRECT, + config.getArity(), + 0, + config.getInputType(), + config.getOutputType())); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } - final Map timerActions = behavior.getActions() + private static Map getTimerActions(Map actions) { + return actions .entrySet() .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) .map(entry -> { - String actionName = entry.getKey(); - int timer = entry.getValue().getConfig().getTimer(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, timer, null, null, null)); + final String actionName = entry.getKey(); + final ActionEnvelope envelope = entry.getValue(); + final ActionConfiguration config = envelope.getConfig(); + final int arity = config.getArity(); + final int timer = config.getTimer(); + + return new AbstractMap.SimpleEntry<>( + actionName, + new Entity.EntityMethod( + actionName, + EntityMethodType.DIRECT, + config.getArity(), + timer, + config.getInputType(), + config.getOutputType())); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private static Entity buildNamedActor(Class stateType, StatefulActor actor, NamedActorBehavior behavior, BehaviorCtx ctx) { + final String actorName = behavior.getName(); + final ActorKind kind = behavior.getActorType(); + final String channel = behavior.getChannel(); + final long deactivateTimeout = behavior.getDeactivatedTimeout(); + final long snapshotTimeout = behavior.getSnapshotTimeout(); + final Map envelopeActions = behavior.getActions(); + final Map actions = getActions(envelopeActions); + final Map timerActions = getTimerActions(envelopeActions); Entity entityType = new Entity( + ctx, + actor, + behavior, actorName, actor.getClass(), getKind(kind), - actor.getStateType(), + stateType, actorName, actor.isStateful(), deactivateTimeout, @@ -157,42 +181,23 @@ private static Entity buildNamedActor(Class stateType, StatefulActor actor, N timerActions, 0, 0, - channel, - Optional.empty(), - Optional.empty()); + channel); return entityType; } - private static Entity buildNamedActor(Class stateType, StatelessActor actor, NamedActorBehavior behavior) { - String actorName = behavior.getName(); - ActorKind kind = behavior.getActorType(); - String channel = behavior.getChannel(); - long deactivateTimeout = behavior.getDeactivatedTimeout(); - long snapshotTimeout = behavior.getSnapshotTimeout(); - - final Map actions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - ActionEnvelope envelope = entry.getValue(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - final Map timerActions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - int timer = entry.getValue().getConfig().getTimer(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, timer, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + private static Entity buildNamedActor(Class stateType, StatelessActor actor, NamedActorBehavior behavior, BehaviorCtx ctx) { + final String actorName = behavior.getName(); + final ActorKind kind = behavior.getActorType(); + final String channel = behavior.getChannel(); + final long deactivateTimeout = behavior.getDeactivatedTimeout(); + final long snapshotTimeout = behavior.getSnapshotTimeout(); + final Map envelopeActions = behavior.getActions(); + final Map actions = getActions(envelopeActions); + final Map timerActions = getTimerActions(envelopeActions); Entity entityType = new Entity( - actorName, + ctx, actor, behavior, actorName, actor.getClass(), getKind(kind), null, @@ -204,45 +209,29 @@ private static Entity buildNamedActor(Class stateType, StatelessActor actor, timerActions, 0, 0, - channel, - Optional.empty(), - Optional.empty()); + channel); return entityType; } - private static Entity buildUnNamedActor(Class stateType, StatefulActor actor, UnNamedActorBehavior behavior) { - String actorName = behavior.getName(); - ActorKind kind = behavior.getActorType(); - String channel = behavior.getChannel(); + private static Entity buildUnNamedActor(Class stateType, StatefulActor actor, UnNamedActorBehavior behavior, BehaviorCtx ctx) { + final String actorName = behavior.getName(); + final ActorKind kind = behavior.getActorType(); + final String channel = behavior.getChannel(); long deactivateTimeout = behavior.getDeactivatedTimeout(); long snapshotTimeout = behavior.getSnapshotTimeout(); - - final Map actions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - ActionEnvelope envelope = entry.getValue(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - final Map timerActions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - int timer = entry.getValue().getConfig().getTimer(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.TIMER, timer, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + final Map envelopeActions = behavior.getActions(); + final Map actions = getActions(envelopeActions); + final Map timerActions = getTimerActions(envelopeActions); Entity entityType = new Entity( + ctx, + actor, + behavior, actorName, actor.getClass(), getKind(kind), - actor.getStateType(), + stateType, actorName, actor.isStateful(), deactivateTimeout, @@ -251,41 +240,25 @@ private static Entity buildUnNamedActor(Class stateType, StatefulActor actor, timerActions, 0, 0, - channel, - Optional.empty(), - Optional.empty()); + channel); return entityType; } - private static Entity buildUnNamedActor(Class stateType, StatelessActor actor, UnNamedActorBehavior behavior) { - String actorName = behavior.getName(); - ActorKind kind = behavior.getActorType(); - String channel = behavior.getChannel(); + private static Entity buildUnNamedActor(Class stateType, StatelessActor actor, UnNamedActorBehavior behavior, BehaviorCtx ctx) { + final String actorName = behavior.getName(); + final ActorKind kind = behavior.getActorType(); + final String channel = behavior.getChannel(); long deactivateTimeout = behavior.getDeactivatedTimeout(); long snapshotTimeout = behavior.getSnapshotTimeout(); - - final Map actions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.NORMAL_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - ActionEnvelope envelope = entry.getValue(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.DIRECT, 0, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - final Map timerActions = behavior.getActions() - .entrySet() - .stream().filter(entry -> entry.getValue().getConfig().getKind().equals(ActionKind.TIMER_DISPATCH)) - .map(entry -> { - String actionName = entry.getKey(); - int timer = entry.getValue().getConfig().getTimer(); - - return new AbstractMap.SimpleEntry<>(actionName, new Entity.EntityMethod(actionName, EntityMethodType.TIMER, timer, null, null, null)); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + final Map envelopeActions = behavior.getActions(); + final Map actions = getActions(envelopeActions); + final Map timerActions = getTimerActions(envelopeActions); Entity entityType = new Entity( + ctx, + actor, + behavior, actorName, actor.getClass(), getKind(kind), @@ -298,333 +271,12 @@ private static Entity buildUnNamedActor(Class stateType, StatelessActor actor timerActions, 0, 0, - channel, - Optional.empty(), - Optional.empty()); - - return entityType; - } - - - - public static Entity fromAnnotationToEntity(Class entity, StatefulNamedActor actor, Object arg, ActorFactory factory) { - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.NAMED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final long snapshotTimeout = actor.snapshotTimeout(); - final boolean isStateful = true; - final Class stateType = actor.stateType(); - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); - - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - stateType, - actorBeanName, - isStateful, - deactivateTimeout, - snapshotTimeout, - actions, - timerActions, - 0, - 0, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering NamedActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); - return entityType; - } - - public static Entity fromAnnotationToEntity(Class entity, StatefulUnNamedActor actor, Object arg, ActorFactory factory) { - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.UNNAMED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final long snapshotTimeout = actor.snapshotTimeout(); - final boolean isStateful = true; - final Class stateType = actor.stateType(); - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); - - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - stateType, - actorBeanName, - isStateful, - deactivateTimeout, - snapshotTimeout, - actions, - timerActions, - 0, - 0, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering UnNamedActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); - return entityType; - } - - public static Entity fromAnnotationToEntity(Class entity, StatefulPooledActor actor, Object arg, ActorFactory factory) { - - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.POOLED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final long snapshotTimeout = actor.snapshotTimeout(); - final boolean isStateful = true; - final Class stateType = actor.stateType(); - final int minPoolSize = actor.minPoolSize(); - final int maxPoolSize = actor.maxPoolSize(); - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); - - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - stateType, - actorBeanName, - isStateful, - deactivateTimeout, - snapshotTimeout, - actions, - timerActions, - minPoolSize, - maxPoolSize, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering PooledActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); - return entityType; - } - - public static Entity fromAnnotationToEntity(Class entity, StatelessNamedActor actor, Object arg, ActorFactory factory) { - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.NAMED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final boolean isStateful = false; - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); - - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - null, - actorBeanName, - isStateful, - deactivateTimeout, - 0, - actions, - timerActions, - 0, - 0, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering NamedActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); - return entityType; - } - - public static Entity fromAnnotationToEntity(Class entity, StatelessUnNamedActor actor, Object arg, ActorFactory factory) { - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.UNNAMED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final boolean isStateful = false; - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); - - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - null, - actorBeanName, - isStateful, - deactivateTimeout, - 0, - actions, - timerActions, - 0, - 0, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering UnNamedActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); - return entityType; - } - - public static Entity fromAnnotationToEntity(Class entity, StatelessPooledActor actor, Object arg, ActorFactory factory) { - - String actorBeanName = entity.getSimpleName(); - String actorName; - if ((Objects.isNull(actor.name()) || actor.name().isEmpty())) { - actorName = actorBeanName; - } else { - actorName = actor.name(); - } - - final ActorKind kind = ActorKind.POOLED; - final long deactivateTimeout = actor.deactivatedTimeout(); - final boolean isStateful = false; - final int minPoolSize = actor.minPoolSize(); - final int maxPoolSize = actor.maxPoolSize(); - final String channel = actor.channel(); - - final Map actions = buildActions(entity, Action.class); - final Map timerActions = buildActions(entity, TimerAction.class); + channel); - Entity entityType = new Entity( - actorName, - entity, - getKind(kind), - null, - actorBeanName, - isStateful, - deactivateTimeout, - 0, - actions, - timerActions, - minPoolSize, - maxPoolSize, - channel, - Optional.ofNullable(arg), - Optional.ofNullable(factory)); - - log.info("Registering PooledActor: {}", actorName); - log.debug("Registering Entity -> {}", entityType); return entityType; } - private static Map buildActions(Class entity, Class annotationType) { - final Map actions = new HashMap<>(); - - List methods = Arrays.stream(entity.getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(annotationType)) - .collect(Collectors.toList()); - - for (Method method : methods) { - try { - method.setAccessible(true); - String commandName = getActionName(method, annotationType); - Class inputType = getInputType(method, annotationType); - Class outputType = getOutputType(method, annotationType); - - Entity.EntityMethod action = new Entity.EntityMethod( - commandName, - getEntityMethodType(method, annotationType), - getPeriod(method, annotationType), - method, - inputType, - outputType); - - actions.put(commandName, action); - } catch (SecurityException e) { - log.error("Failure on load Actor Action", e); - } - } - return actions; - } - - private static int getPeriod(Method method, Class type) { - int period = 0; - - if (type.isAssignableFrom(TimerAction.class)) { - TimerAction act = method.getAnnotation(TimerAction.class); - period = act.period(); - } - - return period; - } - - private static Entity.EntityMethodType getEntityMethodType(Method method, Class type) { - Entity.EntityMethodType entityMethodType = null; - - if (type.isAssignableFrom(Action.class)) { - entityMethodType = Entity.EntityMethodType.DIRECT; - } - - if (type.isAssignableFrom(TimerAction.class)) { - entityMethodType = Entity.EntityMethodType.TIMER; - } - - return entityMethodType; - } - - private static String getActionName(Method method, Class type) { - String commandName = ""; - - if (type.isAssignableFrom(Action.class)) { - Action act = method.getAnnotation(Action.class); - commandName = ((!act.name().equalsIgnoreCase("")) ? act.name() : method.getName()); - } - - if (type.isAssignableFrom(TimerAction.class)) { - TimerAction act = method.getAnnotation(TimerAction.class); - commandName = ((!act.name().equalsIgnoreCase("")) ? act.name() : method.getName()); - } - - return commandName; - } - - private static Class getInputType(Method method, Class type) { + /*private static Class getInputType(Method method, Class type) { Class inputType = null; if (type.isAssignableFrom(Action.class)) { @@ -638,23 +290,7 @@ private static Class getInputType(Method method, Class } return inputType; - } - - private static Class getOutputType(Method method, Class type) { - Class outputType = null; - - if (type.isAssignableFrom(Action.class)) { - Action act = method.getAnnotation(Action.class); - outputType = (!act.outputType().isAssignableFrom(Action.Default.class) ? act.outputType() : method.getReturnType()); - } - - if (type.isAssignableFrom(TimerAction.class)) { - TimerAction act = method.getAnnotation(TimerAction.class); - outputType = (!act.outputType().isAssignableFrom(TimerAction.Default.class) ? act.outputType() : method.getReturnType()); - } - - return outputType; - } + }*/ private static ActorOuterClass.Kind getKind(ActorKind kind) { switch (kind) { @@ -669,6 +305,18 @@ private static ActorOuterClass.Kind getKind(ActorKind kind) { } } + public A getActor() { + return (A) this.actor; + } + + public B getBehavior() { + return (B) this.behavior; + } + + public BehaviorCtx getCtx() { + return this.ctx; + } + public String getActorName() { return actorName; } @@ -725,14 +373,6 @@ public String getChannel() { return channel; } - public Optional getActorArg() { - return actorArg; - } - - public Optional getActorFactory() { - return actorFactory; - } - @Override public String toString() { final StringBuilder sb = new StringBuilder("Entity{"); @@ -762,18 +402,19 @@ public static final class EntityMethod { private EntityMethodType type; + private int arity; + private int fixedPeriod; - private Method method; private Class inputType; private Class outputType; public EntityMethod( - String name, EntityMethodType type, int fixedPeriod, Method method, Class inputType, Class outputType) { + String name, EntityMethodType type, int arity, int fixedPeriod, Class inputType, Class outputType) { this.name = name; this.type = type; + this.arity = arity; this.fixedPeriod = fixedPeriod; - this.method = method; this.inputType = inputType; this.outputType = outputType; } @@ -786,24 +427,15 @@ public EntityMethodType getType() { return type; } - public int getFixedPeriod() { - return fixedPeriod; + public int getArity() { + return arity; } - public Method getMethod() { - return method; + public int getFixedPeriod() { + return fixedPeriod; } - public Class getInputType() { - int arity = method.getParameterTypes().length; - - if (arity == 2 && Objects.isNull(inputType)) { - for (Class parameterType : method.getParameterTypes()) { - if (!inputType.isAssignableFrom(ActorContext.class)) { - return parameterType; - } - } - } + public Class getInputType() { return inputType; } @@ -816,8 +448,8 @@ public String toString() { final StringBuilder sb = new StringBuilder("EntityMethod{"); sb.append("name='").append(name).append('\''); sb.append(", type=").append(type); + sb.append(", arity=").append(arity); sb.append(", fixedPeriod=").append(fixedPeriod); - sb.append(", method=").append(method); sb.append(", inputType=").append(inputType); sb.append(", outputType=").append(outputType); sb.append('}'); diff --git a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java index a58ab67..97a1f29 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java +++ b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java @@ -10,8 +10,9 @@ import io.eigr.functions.protocol.actors.ActorOuterClass.ActorId; import io.eigr.spawn.api.Spawn; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.ActorFactory; +import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.workflows.SideEffect; import io.eigr.spawn.api.exceptions.ActorInvocationException; import io.eigr.spawn.internal.Entity; @@ -23,14 +24,13 @@ import java.io.OutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -public final class ActorServiceHandler implements HttpHandler { +public final class ActorServiceHandler implements HttpHandler { private static final Logger log = LoggerFactory.getLogger(ActorServiceHandler.class); private static final int CACHE_MAXIMUM_SIZE = 10_000; private static final int CACHE_EXPIRE_AFTER_WRITE_SECONDS = 60; @@ -41,7 +41,7 @@ public final class ActorServiceHandler implements HttpHandler { private final List entities; - private final Cache cache; + private final Cache cache; public ActorServiceHandler(final Spawn spawn, final List actors) { @@ -113,6 +113,7 @@ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) th } } catch (Exception e) { + e.printStackTrace(); log.error("Error during handle request.", e); } @@ -126,24 +127,23 @@ private Optional callAction(String system, String actor, String parent, S try { String actorKey = String.format("%s:%s", system, actor); - Object actorRef = this.cache.getIfPresent(actorKey); + ActorBehavior actorRef = this.cache.getIfPresent(actorKey); if (Objects.isNull(actorRef)) { actorRef = buildInstance(entity); - this.cache.put(actorKey, actorRef); + this.cache.put(actorKey, (B) actorRef); } Entity.EntityMethod entityMethod; if (entity.getActions().containsKey(commandName)) { - entityMethod = entity.getActions().get(commandName); + entityMethod = (Entity.EntityMethod) entity.getActions().get(commandName); } else if (entity.getTimerActions().containsKey(commandName)) { - entityMethod = entity.getTimerActions().get(commandName); + entityMethod = (Entity.EntityMethod) entity.getTimerActions().get(commandName); } else { throw new ActorInvocationException( String.format("The Actor does not have the desired action: %s", commandName)); } - final Method actorMethod = entityMethod.getMethod(); Class inputType = entityMethod.getInputType(); log.debug("Action input type is: {}. Deserialize with value {}", inputType, value.getTypeUrl()); @@ -159,11 +159,11 @@ private Optional callAction(String system, String actor, String parent, S actorContext = new ActorContext(this.spawn); } - if (inputType.isAssignableFrom(ActorContext.class) && actorMethod.getParameterTypes().length == 1) { - return Optional.of((Value) actorMethod.invoke(actorRef, actorContext)); + if (entityMethod.getArity() == 0) { + return Optional.of((Value) actorRef.call(commandName, actorContext)); } else { - final Object unpack = value.unpack(inputType); - return Optional.of((Value) actorMethod.invoke(actorRef, unpack, actorContext)); + final var unpack = value.unpack(inputType); + return Optional.of((Value) actorRef.call(commandName, actorContext, unpack)); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ActorInvocationException(e); @@ -177,20 +177,21 @@ private Optional callAction(String system, String actor, String parent, S return Optional.empty(); } - private Object buildInstance(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + private B buildInstance(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { return buildInstanceByArg(entity); } - private Object buildInstanceByArg(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - if (entity.getActorFactory().isPresent() && entity.getActorArg().isPresent()) { - ActorFactory factory = entity.getActorFactory().get(); - Object arg = entity.getActorArg().get(); - return factory.newInstance(arg); + private B buildInstanceByArg(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + + ActorBehavior behavior = entity.getBehavior(); + if (Objects.nonNull(behavior)) { + return (B) behavior; } - Class klass = entity.getActorType(); - Constructor constructor = klass.getConstructor(); - return constructor.newInstance(); + Constructor constructor = entity.getActor().getClass().getConstructor(); + StatefulActor stActor = (StatefulActor) constructor.newInstance(); + stActor.configure(entity.getCtx()); + return (B) stActor.configure(entity.getCtx()); } private Optional getEntityByActor(String actor, String parent) { diff --git a/src/test/java/io/eigr/spawn/EntityTest.java b/src/test/java/io/eigr/spawn/EntityTest.java deleted file mode 100644 index d54490f..0000000 --- a/src/test/java/io/eigr/spawn/EntityTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.eigr.spawn; - -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.internal.Entity; -import io.eigr.spawn.test.actors.JoeActor; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class EntityTest { - - @Test - public void testEntityBuilder() { - StatefulNamedActor annotation = JoeActor.class.getAnnotation(StatefulNamedActor.class); - final Entity entity = Entity.fromAnnotationToEntity(JoeActor.class, annotation, null, null); - assertEquals(1, entity.getActions().values().size()); - assertEquals(0, entity.getTimerActions().values().size()); - assertEquals("test.channel", entity.getChannel()); - } -} diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 46c7f1f..24eff33 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -31,7 +31,7 @@ public void before() throws Exception { spawnSystem = new Spawn.SpawnSystem() .create("spawn-system", injector) .withActor(JoeActor.class) - //.withActor(ActorWithConstructor.class) + .withActor(ActorWithConstructor.class) .withTransportOptions( TransportOpts.builder() .port(8091) @@ -47,12 +47,12 @@ public void before() throws Exception { @Test public void testApp() throws ActorCreationException, ActorInvocationException { ActorRef joeActor = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "test_joe")); + ActorIdentity.of("spawn-system", "JoeActor")); assertNotNull(joeActor); Actor.Request msg = Actor.Request.newBuilder() - .setLanguage("erlang") + .setLanguage("Erlang") .build(); Optional maybeReply = @@ -61,7 +61,7 @@ public void testApp() throws ActorCreationException, ActorInvocationException { if (maybeReply.isPresent()) { Actor.Reply reply = maybeReply.get(); assertNotNull(reply); - assertEquals("Hello From Java", reply.getResponse()); + assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); } } } diff --git a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java index 27b7a09..4941b14 100644 --- a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java +++ b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java @@ -6,9 +6,7 @@ import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; - -import io.eigr.spawn.api.extensions.DependencyInjector; - +import io.eigr.spawn.internal.ActionBindings; import io.eigr.spawn.java.test.domain.Actor.Reply; import io.eigr.spawn.java.test.domain.Actor.Request; import io.eigr.spawn.java.test.domain.Actor.State; @@ -23,12 +21,12 @@ public final class ActorWithConstructor extends StatefulActor { public ActorBehavior configure(BehaviorCtx context) { defaultMessage = context.getInjector().getInstance(String.class); return new NamedActorBehavior( - name("test_actor_constructor"), - action("SetLanguage", this::setLanguage) + name("TestActorConstructor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) ); } - public Value setLanguage(ActorContext context, Request msg) { + private Value setLanguage(ActorContext context, Request msg) { return Value.at() .response(Reply.newBuilder() .setResponse(defaultMessage) diff --git a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java index c04ee15..3929cd7 100644 --- a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java +++ b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java @@ -1,11 +1,15 @@ package io.eigr.spawn.test.actors; -import io.eigr.spawn.api.actors.*; - +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; -import io.eigr.spawn.java.test.domain.Actor.*; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.test.domain.Actor.Reply; +import io.eigr.spawn.java.test.domain.Actor.Request; +import io.eigr.spawn.java.test.domain.Actor.State; import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; @@ -14,19 +18,19 @@ public final class JoeActor extends StatefulActor { @Override public ActorBehavior configure(BehaviorCtx context) { return new NamedActorBehavior( - name("test_joe"), + name("JoeActor"), channel("test.channel"), - action("SetLanguage", this::setLanguage) + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) ); } - public Value setLanguage(ActorContext context, Request msg) { + private Value setLanguage(ActorContext context, Request msg) { if (context.getState().isPresent()) { } return Value.at() .response(Reply.newBuilder() - .setResponse("Hello From Java") + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) .build()) .state(updateState("java")) .reply(); diff --git a/src/test/proto/eigr/functions/java/sdk/test/actor.proto b/src/test/proto/eigr/functions/java/sdk/test/actor.proto index 2ffea96..4e19e28 100644 --- a/src/test/proto/eigr/functions/java/sdk/test/actor.proto +++ b/src/test/proto/eigr/functions/java/sdk/test/actor.proto @@ -12,4 +12,12 @@ message Request { message Reply { string response = 1; +} + +service JoeActor { + rpc SetLanguage(Request) returns (Reply); +} + +service TestActorConstructor { + rpc SetLanguage(Request) returns (Reply); } \ No newline at end of file From bd561d68ef24bfab5cd241fb82ff7ecd3066da42 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Wed, 24 Jul 2024 19:22:29 -0300 Subject: [PATCH 08/19] Refactor. Extract methods --- .../spawn/api/actors/workflows/Broadcast.java | 1 - .../java/io/eigr/spawn/internal/Entity.java | 3 +- .../transport/server/ActorServiceHandler.java | 169 ++++++++---------- 3 files changed, 75 insertions(+), 98 deletions(-) diff --git a/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java b/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java index 0e94d78..580b320 100644 --- a/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java +++ b/src/main/java/io/eigr/spawn/api/actors/workflows/Broadcast.java @@ -3,7 +3,6 @@ import com.google.protobuf.Any; import com.google.protobuf.GeneratedMessage; import io.eigr.functions.protocol.Protocol; -import io.eigr.functions.protocol.actors.ActorOuterClass; import org.jetbrains.annotations.NotNull; import java.util.Objects; diff --git a/src/main/java/io/eigr/spawn/internal/Entity.java b/src/main/java/io/eigr/spawn/internal/Entity.java index 609e0a4..026d644 100644 --- a/src/main/java/io/eigr/spawn/internal/Entity.java +++ b/src/main/java/io/eigr/spawn/internal/Entity.java @@ -14,7 +14,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.AbstractMap; import java.util.Map; import java.util.stream.Collectors; @@ -148,7 +147,7 @@ private static Map getTimerActions(Map implements HttpH private final Spawn spawn; private final String system; - private final List entities; - private final Cache cache; - - public ActorServiceHandler(final Spawn spawn, final List actors) { + public ActorServiceHandler(final Spawn spawn, final List entities) { this.spawn = spawn; this.system = spawn.getSystem(); - this.entities = actors; + this.entities = entities; this.cache = Caffeine.newBuilder() .maximumSize(CACHE_MAXIMUM_SIZE) .expireAfterWrite(Duration.ofSeconds(CACHE_EXPIRE_AFTER_WRITE_SECONDS)) @@ -57,7 +53,6 @@ public ActorServiceHandler(final Spawn spawn, final List actors) { @Override public void handle(HttpExchange exchange) throws IOException { log.debug("Received Actor Action Request."); - if ("POST".equals(exchange.getRequestMethod())) { try (OutputStream os = exchange.getResponseBody()) { Protocol.ActorInvocationResponse response = handleRequest(exchange); @@ -87,34 +82,11 @@ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) th actor, commandName, maybeValueResponse); if (maybeValueResponse.isPresent()) { - Value valueResponse = maybeValueResponse.get(); - - Protocol.Context.Builder updatedContextBuilder = Protocol.Context.newBuilder(); - if (Objects.nonNull(valueResponse.getState())) { - Any encodedState = Any.pack(valueResponse.getState()); - updatedContextBuilder.setState(encodedState); - } - - Any encodedValue; - if (Objects.isNull(valueResponse.getResponse())) { - encodedValue = Any.pack(Protocol.Noop.getDefaultInstance()); - } else { - encodedValue = Any.pack(valueResponse.getResponse()); - } - - return Protocol.ActorInvocationResponse.newBuilder() - .setActorName(actor) - .setActorSystem(system) - .setValue(encodedValue) - .setWorkflow(buildWorkflow(valueResponse)) - .setUpdatedContext(updatedContextBuilder.build()) - .setCheckpoint(valueResponse.getCheckpoint()) - .build(); + return buildResponse(maybeValueResponse.get(), actor, system); } - } catch (Exception e) { - e.printStackTrace(); log.error("Error during handle request.", e); + throw new IOException("Action result is null", e); } throw new IOException("Action result is null"); @@ -124,88 +96,96 @@ private Optional callAction(String system, String actor, String parent, S Optional optionalEntity = getEntityByActor(actor, parent); if (optionalEntity.isPresent()) { Entity entity = optionalEntity.get(); - try { - String actorKey = String.format("%s:%s", system, actor); - ActorBehavior actorRef = this.cache.getIfPresent(actorKey); - if (Objects.isNull(actorRef)) { - actorRef = buildInstance(entity); - this.cache.put(actorKey, (B) actorRef); - } - - Entity.EntityMethod entityMethod; - - if (entity.getActions().containsKey(commandName)) { - entityMethod = (Entity.EntityMethod) entity.getActions().get(commandName); - } else if (entity.getTimerActions().containsKey(commandName)) { - entityMethod = (Entity.EntityMethod) entity.getTimerActions().get(commandName); - } else { - throw new ActorInvocationException( - String.format("The Actor does not have the desired action: %s", commandName)); - } - - Class inputType = entityMethod.getInputType(); - log.debug("Action input type is: {}. Deserialize with value {}", inputType, value.getTypeUrl()); - - ActorContext actorContext; - if (context.hasState()) { - Any anyCtxState = context.getState(); - log.debug("[{}] trying to get the state of the Actor {}. Parse Any type {} from State type {}", - system, actor, anyCtxState, entity.getStateType().getSimpleName()); - - Object state = anyCtxState.unpack(entity.getStateType()); - actorContext = new ActorContext(this.spawn, state); - } else { - actorContext = new ActorContext(this.spawn); - } - - if (entityMethod.getArity() == 0) { - return Optional.of((Value) actorRef.call(commandName, actorContext)); - } else { - final var unpack = value.unpack(inputType); - return Optional.of((Value) actorRef.call(commandName, actorContext, unpack)); - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new ActorInvocationException(e); - } catch (InvalidProtocolBufferException e) { - throw new ActorInvocationException(e); - } catch (NoSuchMethodException | InstantiationException e) { + ActorBehavior actorRef = getOrCreateActor(system, actor, entity); + Entity.EntityMethod entityMethod = getEntityMethod(commandName, entity); + ActorContext actorContext = createActorContext(context, entity); + + return invokeAction(actorRef, commandName, value, entityMethod, actorContext); + } catch (ReflectiveOperationException | InvalidProtocolBufferException e) { throw new ActorInvocationException(e); } } - return Optional.empty(); } - private B buildInstance(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - return buildInstanceByArg(entity); + private Optional invokeAction(ActorBehavior actorRef, String commandName, Any value, Entity.EntityMethod entityMethod, ActorContext actorContext) throws ReflectiveOperationException, InvalidProtocolBufferException, ActorInvocationException { + if (entityMethod.getArity() == 0) { + return Optional.of((Value) actorRef.call(commandName, actorContext)); + } else { + Class inputType = entityMethod.getInputType(); + final var unpackedValue = value.unpack(inputType); + return Optional.of((Value) actorRef.call(commandName, actorContext, unpackedValue)); + } + } + + private ActorBehavior getOrCreateActor(String system, String actor, Entity entity) throws ReflectiveOperationException { + String actorKey = String.format("%s:%s", system, actor); + ActorBehavior actorRef = cache.getIfPresent(actorKey); + if (actorRef == null) { + actorRef = buildInstance(entity); + cache.put(actorKey, (B) actorRef); + } + return actorRef; + } + + private Entity.EntityMethod getEntityMethod(String commandName, Entity entity) throws ActorInvocationException { + if (entity.getActions().containsKey(commandName)) { + return (Entity.EntityMethod) entity.getActions().get(commandName); + } else if (entity.getTimerActions().containsKey(commandName)) { + return (Entity.EntityMethod) entity.getTimerActions().get(commandName); + } else { + throw new ActorInvocationException( + String.format("The Actor does not have the desired action: %s", commandName)); + } } - private B buildInstanceByArg(Entity entity) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + private ActorContext createActorContext(Protocol.Context context, Entity entity) throws InvalidProtocolBufferException { + if (context.hasState()) { + Any anyCtxState = context.getState(); + log.debug("[{}] trying to get the state of the Actor {}. Parse Any type {} from State type {}", + system, entity.getActorName(), anyCtxState, entity.getStateType().getSimpleName()); - ActorBehavior behavior = entity.getBehavior(); - if (Objects.nonNull(behavior)) { - return (B) behavior; + Object state = anyCtxState.unpack(entity.getStateType()); + return new ActorContext(spawn, state); + } else { + return new ActorContext(spawn); } + } + private B buildInstance(Entity entity) throws ReflectiveOperationException { Constructor constructor = entity.getActor().getClass().getConstructor(); StatefulActor stActor = (StatefulActor) constructor.newInstance(); - stActor.configure(entity.getCtx()); return (B) stActor.configure(entity.getCtx()); } private Optional getEntityByActor(String actor, String parent) { - Optional entity = this.entities.stream() + return entities.stream() .filter(e -> e.getActorName().equalsIgnoreCase(actor)) - .findFirst(); + .findFirst() + .or(() -> entities.stream() + .filter(e -> e.getActorName().equalsIgnoreCase(parent)) + .findFirst()); + } - if (entity.isPresent()) { - return entity; - } + private Protocol.ActorInvocationResponse buildResponse(Value valueResponse, String actor, String system) { + Protocol.Context.Builder updatedContextBuilder = Protocol.Context.newBuilder(); + + Optional.of(valueResponse.getState()) + .ifPresent(state -> updatedContextBuilder.setState(Any.pack(state))); - return this.entities.stream() - .filter(e -> e.getActorName().equalsIgnoreCase(parent)) - .findFirst(); + Any encodedValue = Optional.of(valueResponse.getResponse()) + .map(value -> Any.pack(value)) + .orElse(Any.pack(Protocol.Noop.getDefaultInstance())); + + return Protocol.ActorInvocationResponse.newBuilder() + .setActorName(actor) + .setActorSystem(system) + .setValue(encodedValue) + .setWorkflow(buildWorkflow(valueResponse)) + .setUpdatedContext(updatedContextBuilder.build()) + .setCheckpoint(valueResponse.getCheckpoint()) + .build(); } private Protocol.Workflow buildWorkflow(Value valueResponse) { @@ -224,5 +204,4 @@ private List getProtocolEffects(List> effects .map(SideEffect::build) .collect(Collectors.toList()); } - } From e30a8af318ed6fb106b8e5411b8eba78f1bb527d Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Wed, 24 Jul 2024 19:32:49 -0300 Subject: [PATCH 09/19] Better documentation --- .../transport/server/ActorServiceHandler.java | 99 ++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java index 8c82cea..7dcb5e2 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java +++ b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java @@ -26,9 +26,16 @@ import java.lang.reflect.Constructor; import java.time.Duration; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +/** + * Handles HTTP requests for actor actions in an actor-based system. + * Implements {@link HttpHandler} to process incoming HTTP POST requests and invoke actions on actors. + * + * @param the type of {@link ActorBehavior} managed by this handler + */ public final class ActorServiceHandler implements HttpHandler { private static final Logger log = LoggerFactory.getLogger(ActorServiceHandler.class); private static final int CACHE_MAXIMUM_SIZE = 10_000; @@ -40,6 +47,12 @@ public final class ActorServiceHandler implements HttpH private final List entities; private final Cache cache; + /** + * Constructs an {@link ActorServiceHandler} instance. + * + * @param spawn the {@link Spawn} instance representing the actor system + * @param entities the list of {@link Entity} objects representing the actors + */ public ActorServiceHandler(final Spawn spawn, final List entities) { this.spawn = spawn; this.system = spawn.getSystem(); @@ -50,6 +63,12 @@ public ActorServiceHandler(final Spawn spawn, final List entities) { .build(); } + /** + * Handles incoming HTTP requests. + * + * @param exchange the {@link HttpExchange} representing the HTTP request-response exchange + * @throws IOException if an I/O error occurs + */ @Override public void handle(HttpExchange exchange) throws IOException { log.debug("Received Actor Action Request."); @@ -64,6 +83,13 @@ public void handle(HttpExchange exchange) throws IOException { } } + /** + * Processes the actor invocation request and returns the response. + * + * @param exchange the {@link HttpExchange} representing the HTTP request-response exchange + * @return the {@link Protocol.ActorInvocationResponse} representing the response to the actor invocation + * @throws IOException if an I/O error occurs + */ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) throws IOException { try (InputStream in = exchange.getRequestBody()) { Protocol.ActorInvocation actorInvocationRequest = Protocol.ActorInvocation.parseFrom(in); @@ -92,7 +118,21 @@ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) th throw new IOException("Action result is null"); } - private Optional callAction(String system, String actor, String parent, String commandName, Any value, Protocol.Context context) throws ActorInvocationException { + /** + * Invokes the specified action on the actor and returns the result. + * + * @param system the actor system name + * @param actor the actor name + * @param parent the parent actor name + * @param commandName the action name + * @param value the action input value + * @param context the actor context + * @return an {@link Optional} containing the result of the action invocation + * @throws ActorInvocationException if an error occurs during action invocation + */ + private Optional callAction( + String system, String actor, String parent, String commandName, Any value, Protocol.Context context) + throws ActorInvocationException { Optional optionalEntity = getEntityByActor(actor, parent); if (optionalEntity.isPresent()) { Entity entity = optionalEntity.get(); @@ -109,7 +149,9 @@ private Optional callAction(String system, String actor, String parent, S return Optional.empty(); } - private Optional invokeAction(ActorBehavior actorRef, String commandName, Any value, Entity.EntityMethod entityMethod, ActorContext actorContext) throws ReflectiveOperationException, InvalidProtocolBufferException, ActorInvocationException { + private Optional invokeAction( + ActorBehavior actorRef, String commandName, Any value, Entity.EntityMethod entityMethod, ActorContext actorContext) + throws ReflectiveOperationException, InvalidProtocolBufferException, ActorInvocationException { if (entityMethod.getArity() == 0) { return Optional.of((Value) actorRef.call(commandName, actorContext)); } else { @@ -119,16 +161,33 @@ private Optional invokeAction(ActorBehavior actorRef, String commandName, } } + /** + * Builds or retrieves an actor behavior instance from the cache. + * + * @param system the actor system name + * @param actor the actor name + * @param entity the {@link Entity} object representing the actor + * @return the {@link ActorBehavior} instance + * @throws ReflectiveOperationException if a reflection operation fails + */ private ActorBehavior getOrCreateActor(String system, String actor, Entity entity) throws ReflectiveOperationException { String actorKey = String.format("%s:%s", system, actor); ActorBehavior actorRef = cache.getIfPresent(actorKey); - if (actorRef == null) { + if (Objects.isNull(actorRef)) { actorRef = buildInstance(entity); cache.put(actorKey, (B) actorRef); } return actorRef; } + /** + * Retrieves the method corresponding to the specified command name from the entity. + * + * @param commandName the action name + * @param entity the {@link Entity} object representing the actor + * @return the {@link Entity.EntityMethod} representing the method for the command + * @throws ActorInvocationException if the entity does not contain the desired action + */ private Entity.EntityMethod getEntityMethod(String commandName, Entity entity) throws ActorInvocationException { if (entity.getActions().containsKey(commandName)) { return (Entity.EntityMethod) entity.getActions().get(commandName); @@ -140,6 +199,14 @@ private Entity.EntityMethod getEntityMethod(String commandName, Entity entity) t } } + /** + * Creates an {@link ActorContext} instance using the provided context and entity state. + * + * @param context the {@link Protocol.Context} representing the actor context + * @param entity the {@link Entity} object representing the actor + * @return the {@link ActorContext} instance + * @throws InvalidProtocolBufferException if the state cannot be unpacked + */ private ActorContext createActorContext(Protocol.Context context, Entity entity) throws InvalidProtocolBufferException { if (context.hasState()) { Any anyCtxState = context.getState(); @@ -153,12 +220,26 @@ private ActorContext createActorContext(Protocol.Context context, Entity entity) } } + /** + * Builds an {@link ActorBehavior} instance from the provided entity. + * + * @param entity the {@link Entity} object representing the actor + * @return the {@link ActorBehavior} instance + * @throws ReflectiveOperationException if a reflection operation fails + */ private B buildInstance(Entity entity) throws ReflectiveOperationException { Constructor constructor = entity.getActor().getClass().getConstructor(); StatefulActor stActor = (StatefulActor) constructor.newInstance(); return (B) stActor.configure(entity.getCtx()); } + /** + * Retrieves an {@link Entity} based on the actor or parent actor name. + * + * @param actor the actor name + * @param parent the parent actor name + * @return an {@link Optional} containing the {@link Entity} if found + */ private Optional getEntityByActor(String actor, String parent) { return entities.stream() .filter(e -> e.getActorName().equalsIgnoreCase(actor)) @@ -188,6 +269,12 @@ private Protocol.ActorInvocationResponse buildResponse(Value valueResponse, Stri .build(); } + /** + * Builds a {@link Protocol.Workflow} from the provided value response. + * + * @param valueResponse the {@link Value} representing the response value + * @return the {@link Protocol.Workflow} instance + */ private Protocol.Workflow buildWorkflow(Value valueResponse) { Protocol.Workflow.Builder workflowBuilder = Protocol.Workflow.newBuilder(); @@ -199,6 +286,12 @@ private Protocol.Workflow buildWorkflow(Value valueResponse) { return workflowBuilder.build(); } + /** + * Converts a list of {@link SideEffect} to a list of {@link Protocol.SideEffect}. + * + * @param effects the list of {@link SideEffect} objects + * @return the list of {@link Protocol.SideEffect} objects + */ private List getProtocolEffects(List> effects) { return effects.stream() .map(SideEffect::build) From d077ece57f0f5c8bcd3d4ade2916ee878b0c467b Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 08:00:50 -0300 Subject: [PATCH 10/19] Fix. NoBindings and TimerActions --- .../spawn/api/actors/behaviors/ActorBehavior.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index 8d30c21..6e63931 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -55,8 +55,7 @@ public static ActorOption init(ActionNoBindings action) { } public static ActorOption action(String name, ActionNoBindings action) { - final Method method = action.getClass().getDeclaredMethods()[0]; - final Class outputType = method.getReturnType(); + final Class outputType = Value.class; return instance -> instance.actions.put( name, new ActionEnvelope( @@ -65,8 +64,6 @@ public static ActorOption action(String name, ActionNoBindings action) { } public static ActorOption action(String name, ActionBindings action) { - final Method method = action.getClass().getDeclaredMethods()[0]; - final Class inputType = action.getArgumentType(); final Class outputType = Value.class; @@ -78,8 +75,7 @@ public static ActorOption action(String name, ActionBindings } public static ActorOption timerAction(String name, int timer, ActionNoBindings action) { - final Method method = action.getClass().getDeclaredMethods()[0]; - final Class outputType = method.getReturnType(); + final Class outputType = Value.class; return instance -> instance.actions.put( name, new ActionEnvelope( @@ -88,9 +84,8 @@ name, new ActionEnvelope( } public static ActorOption timerAction(String name, int timer, ActionBindings action) { - final Method method = action.getClass().getDeclaredMethods()[0]; - final Class inputType = method.getParameterTypes()[1]; - final Class outputType = method.getReturnType(); + final Class inputType = action.getArgumentType(); + final Class outputType = Value.class; return instance -> instance.actions.put( name, From 057d47962e8f2a8dc1f5a351024eadcee52a20d4 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 08:04:56 -0300 Subject: [PATCH 11/19] Added Stateless Actor support --- .../api/actors/behaviors/ActorBehavior.java | 1 - .../java/io/eigr/spawn/internal/Entity.java | 43 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java index 6e63931..7d995bd 100644 --- a/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java +++ b/src/main/java/io/eigr/spawn/api/actors/behaviors/ActorBehavior.java @@ -7,7 +7,6 @@ import io.eigr.spawn.api.exceptions.ActorInvocationException; import io.eigr.spawn.internal.*; -import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/io/eigr/spawn/internal/Entity.java b/src/main/java/io/eigr/spawn/internal/Entity.java index 026d644..dfba127 100644 --- a/src/main/java/io/eigr/spawn/internal/Entity.java +++ b/src/main/java/io/eigr/spawn/internal/Entity.java @@ -82,8 +82,27 @@ public Entity( this.channel = channel; } - public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class actor) { - return null; + public static Entity fromStatelessActorToEntity(BehaviorCtx ctx, Class actor) throws ActorCreationException { + try { + Constructor constructor = actor.getConstructor(); + StatelessActor stActor = (StatelessActor) constructor.newInstance(); + ActorBehavior behavior = stActor.configure(ctx); + + if (behavior.getClass().isAssignableFrom(NamedActorBehavior.class)) { + Entity entity = buildNamedActor(null, stActor, (NamedActorBehavior) behavior, ctx); + return entity; + } + + if (behavior.getClass().isAssignableFrom(UnNamedActorBehavior.class)) { + return buildUnNamedActor(null, stActor, (UnNamedActorBehavior) behavior, ctx); + } + + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | + IllegalAccessException e) { + throw new ActorCreationException(); + } + + throw new ActorCreationException(); } public static Entity fromStatefulActorToEntity(BehaviorCtx ctx, Class actor) throws ActorCreationException { @@ -199,7 +218,7 @@ private static Entity buildNamedActor(Class stateType, StatelessActor actor, ctx, actor, behavior, actorName, actor.getClass(), getKind(kind), - null, + stateType, actorName, actor.isStateful(), deactivateTimeout, @@ -261,7 +280,7 @@ private static Entity buildUnNamedActor(Class stateType, StatelessActor actor actorName, actor.getClass(), getKind(kind), - null, + stateType, actorName, actor.isStateful(), deactivateTimeout, @@ -275,22 +294,6 @@ private static Entity buildUnNamedActor(Class stateType, StatelessActor actor return entityType; } - /*private static Class getInputType(Method method, Class type) { - Class inputType = null; - - if (type.isAssignableFrom(Action.class)) { - Action act = method.getAnnotation(Action.class); - inputType = (!act.inputType().isAssignableFrom(Action.Default.class) ? act.inputType() : method.getParameterTypes()[0]); - } - - if (type.isAssignableFrom(TimerAction.class)) { - TimerAction act = method.getAnnotation(TimerAction.class); - inputType = (!act.inputType().isAssignableFrom(TimerAction.Default.class) ? act.inputType() : method.getParameterTypes()[0]); - } - - return inputType; - }*/ - private static ActorOuterClass.Kind getKind(ActorKind kind) { switch (kind) { case UNNAMED: From 94fbb62f2c9a6c0558cdfe64e71b5748ce898b95 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 11:28:13 -0300 Subject: [PATCH 12/19] Improvement on injector --- .../api/extensions/DependencyInjector.java | 4 ++++ .../extensions/SimpleDependencyInjector.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java b/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java index 667fcf1..3333c46 100644 --- a/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java +++ b/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java @@ -4,5 +4,9 @@ public interface DependencyInjector { void bind(Class type, Object instance); + void bind(Class type, String alias, Object instance); + T getInstance(Class type); + + T getInstanceByAlias(String alias); } diff --git a/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java b/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java index 4b08754..a8ca0be 100644 --- a/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java +++ b/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java @@ -6,9 +6,11 @@ public final class SimpleDependencyInjector implements DependencyInjector { private final Map, Object> bucket; + private final Map aliasBucket; private SimpleDependencyInjector() { this.bucket = new HashMap<>(); + this.aliasBucket = new HashMap<>(); } private static class SimpleDependencyInjectorHelper{ @@ -28,8 +30,23 @@ public void bind(Class type, Object instance) { this.bucket.put(type, instance); } + @Override + public void bind(Class type, String alias, Object instance) { + if (this.aliasBucket.containsKey(type)) { + throw new IllegalArgumentException("There is already an instance associated with this type in the bucket"); + } + + this.aliasBucket.put(alias, instance); + } + @Override public T getInstance(Class type) { return type.cast(this.bucket.get(type)); } + + @Override + public Object getInstanceByAlias(String alias) { + var typeClass = this.aliasBucket.get(alias).getClass(); + return typeClass.cast(this.aliasBucket.get(alias)); + } } From 2628e9624d78a7f476f20621b9c0421a1b556a47 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 11:28:24 -0300 Subject: [PATCH 13/19] Minor adjust --- .../eigr/spawn/AbstractContainerBaseTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java index e6d33b6..d1d5c27 100644 --- a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java +++ b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java @@ -31,7 +31,6 @@ abstract class AbstractContainerBaseTest { SPAWN_CONTAINER = new GenericContainer<>(DockerImageName.parse(spawnProxyImage)) .withCreateContainerCmdModifier(e -> e.withHostConfig(HostConfig.newHostConfig() .withPortBindings(PortBinding.parse("9004:9004")))) - // .withEnv("TZ", "America/Fortaleza") .withEnv("SPAWN_PROXY_LOGGER_LEVEL", "DEBUG") .withEnv("SPAWN_STATESTORE_KEY", "3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE=") .withEnv("PROXY_ACTOR_SYSTEM_NAME", spawnSystemName) @@ -52,23 +51,23 @@ abstract class AbstractContainerBaseTest { DependencyInjector injector = SimpleDependencyInjector.createInjector(); injector.bind(String.class, "Hello with Constructor"); - spawnSystem = new Spawn.SpawnSystem() - .create(spawnSystemName) - .withActor(JoeActor.class) - .withActor(ActorWithConstructor.class, injector, arg -> new ActorWithConstructor((DependencyInjector) arg)) - .withTerminationGracePeriodSeconds(5) - .withTransportOptions(TransportOpts.builder() - .port(8091) - .proxyPort(9004) - .build()) - .build(); - try { + spawnSystem = new Spawn.SpawnSystem() + .create(spawnSystemName) + .withActor(JoeActor.class) + .withActor(ActorWithConstructor.class) + .withTerminationGracePeriodSeconds(5) + .withTransportOptions(TransportOpts.builder() + .port(8091) + .proxyPort(9004) + .build()) + .build(); + spawnSystem.start(); + log.info(String.format("%s started", spawnSystemName)); } catch (SpawnException e) { throw new RuntimeException(e); } - log.info(String.format("%s started", spawnSystemName)); } } From ac5e78aabefe4fa5140c6d60bd49b0c5b67516ee Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 11:34:37 -0300 Subject: [PATCH 14/19] Fix. Correct system creation --- src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java index d1d5c27..eb5d72d 100644 --- a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java +++ b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java @@ -53,7 +53,7 @@ abstract class AbstractContainerBaseTest { try { spawnSystem = new Spawn.SpawnSystem() - .create(spawnSystemName) + .create(spawnSystemName, injector) .withActor(JoeActor.class) .withActor(ActorWithConstructor.class) .withTerminationGracePeriodSeconds(5) From 41a67f6f7bfcdd0003390168ed729047d9ef6db4 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 15:16:07 -0300 Subject: [PATCH 15/19] Added new method to ActorRef::getType to get class type of Actors --- src/main/java/io/eigr/spawn/api/ActorRef.java | 13 ++++-- src/main/java/io/eigr/spawn/api/Spawn.java | 36 ++++++++++++++-- .../io/eigr/spawn/api/actors/BaseActor.java | 2 + src/test/java/io/eigr/spawn/SpawnTest.java | 39 ++++++++++++++--- .../eigr/spawn/test/actors/UnNamedActor.java | 43 +++++++++++++++++++ .../eigr/functions/java/sdk/test/actor.proto | 4 ++ 6 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java diff --git a/src/main/java/io/eigr/spawn/api/ActorRef.java b/src/main/java/io/eigr/spawn/api/ActorRef.java index 4a3a703..4561847 100644 --- a/src/main/java/io/eigr/spawn/api/ActorRef.java +++ b/src/main/java/io/eigr/spawn/api/ActorRef.java @@ -24,9 +24,12 @@ public final class ActorRef { private final SpawnClient client; - private ActorRef(ActorOuterClass.ActorId actorId, SpawnClient client) { + private final Class type; + + private ActorRef(ActorOuterClass.ActorId actorId, SpawnClient client, Class type) { this.client = client; this.actorId = actorId; + this.type = type; } /** @@ -39,7 +42,7 @@ private ActorRef(ActorOuterClass.ActorId actorId, SpawnClient client) { * @return the ActorRef instance * @since 0.0.1 */ - protected static ActorRef of(SpawnClient client, Cache cache, ActorIdentity identity) throws ActorCreationException { + protected static ActorRef of(SpawnClient client, Cache cache, ActorIdentity identity, Class actorType) throws ActorCreationException { ActorOuterClass.ActorId actorId; if (identity.isParent()) { @@ -59,7 +62,7 @@ protected static ActorRef of(SpawnClient client, Cache actorIds, Spa client.spawn(req); } + public Class getType() { + return this.type; + } + /** *

This method synchronously invokes an action on the actor that this ActorRef instance represents through the Spawn Proxy. * Used when it is not necessary to send parameters to the Action. diff --git a/src/main/java/io/eigr/spawn/api/Spawn.java b/src/main/java/io/eigr/spawn/api/Spawn.java index e641fad..6d414ee 100644 --- a/src/main/java/io/eigr/spawn/api/Spawn.java +++ b/src/main/java/io/eigr/spawn/api/Spawn.java @@ -101,7 +101,18 @@ public int getTerminationGracePeriodSeconds() { * @since 0.0.1 */ public ActorRef createActorRef(ActorIdentity identity) throws ActorCreationException { - return ActorRef.of(this.client, this.actorIdCache, identity); + Class actorType; + if (identity.isParent()) { + actorType = this.entities.stream().filter(e -> e.getActorName().equalsIgnoreCase(identity.getParent())) + .map(e -> e.getActorType()) + .findFirst().get(); + } else { + actorType = this.entities.stream().filter(e -> e.getActorName().equalsIgnoreCase(identity.getName())) + .map(e -> e.getActorType()) + .findFirst().get(); + } + + return ActorRef.of(this.client, this.actorIdCache, identity, actorType); } /** @@ -127,14 +138,25 @@ public Stream createMultiActorRefs(List identities) thr return identities.stream().map(identity -> { try { + Class actorType; + if (identity.isParent()) { + actorType = this.entities.stream().filter(e -> e.getActorName().equalsIgnoreCase(identity.getParent())) + .map(e -> e.getActorType()) + .findFirst().get(); + } else { + actorType = this.entities.stream().filter(e -> e.getActorName().equalsIgnoreCase(identity.getName())) + .map(e -> e.getActorType()) + .findFirst().get(); + } + if (identity.isParent()) { return ActorRef.of( this.client, this.actorIdCache, - ActorIdentity.of(identity.getSystem(), identity.getName(), identity.getParent(), false)); + ActorIdentity.of(identity.getSystem(), identity.getName(), identity.getParent(), false), actorType); } - return ActorRef.of(this.client, this.actorIdCache, identity); + return ActorRef.of(this.client, this.actorIdCache, identity, actorType); } catch (ActorCreationException e) { throw new SpawnFailureException(e); } @@ -277,7 +299,13 @@ private List getTimerActions(Entity actorEntit @Override public String toString() { - return new StringJoiner(", ", Spawn.class.getSimpleName() + "[", "]").add("system='" + system + "'").add("port=" + port).add("host='" + host + "'").add("proxyHost='" + proxyHost + "'").add("proxyPort=" + proxyPort).toString(); + return new StringJoiner(", ", Spawn.class.getSimpleName() + "[", "]") + .add("system='" + system + "'") + .add("port=" + port) + .add("host='" + host + "'") + .add("proxyHost='" + proxyHost + "'") + .add("proxyPort=" + proxyPort) + .toString(); } public static final class SpawnSystem { diff --git a/src/main/java/io/eigr/spawn/api/actors/BaseActor.java b/src/main/java/io/eigr/spawn/api/actors/BaseActor.java index aac3318..477c8f8 100644 --- a/src/main/java/io/eigr/spawn/api/actors/BaseActor.java +++ b/src/main/java/io/eigr/spawn/api/actors/BaseActor.java @@ -8,4 +8,6 @@ public abstract class BaseActor { public abstract ActorBehavior configure(BehaviorCtx ctx); public abstract Boolean isStateful(); + + } diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 24eff33..ae53e73 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -11,7 +11,8 @@ import io.eigr.spawn.java.test.domain.Actor; import io.eigr.spawn.test.actors.ActorWithConstructor; import io.eigr.spawn.test.actors.JoeActor; -import org.junit.Before; +import io.eigr.spawn.test.actors.UnNamedActor; +import org.junit.BeforeClass; import org.junit.Test; import java.util.Optional; @@ -21,16 +22,17 @@ public class SpawnTest { - private Spawn spawnSystem; + private static Spawn spawnSystem; - @Before - public void before() throws Exception { + @BeforeClass + public static void setup() throws Exception { DependencyInjector injector = SimpleDependencyInjector.createInjector(); injector.bind(String.class, "Hello with Constructor"); spawnSystem = new Spawn.SpawnSystem() .create("spawn-system", injector) .withActor(JoeActor.class) + .withActor(UnNamedActor.class) .withActor(ActorWithConstructor.class) .withTransportOptions( TransportOpts.builder() @@ -45,10 +47,13 @@ public void before() throws Exception { } @Test - public void testApp() throws ActorCreationException, ActorInvocationException { + public void testNamedInvocation() throws ActorCreationException, ActorInvocationException { ActorRef joeActor = spawnSystem.createActorRef( ActorIdentity.of("spawn-system", "JoeActor")); + Class type = joeActor.getType(); + + assertEquals(type, JoeActor.class); assertNotNull(joeActor); Actor.Request msg = Actor.Request.newBuilder() @@ -64,4 +69,28 @@ public void testApp() throws ActorCreationException, ActorInvocationException { assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); } } + + @Test + public void testUnNamedInvocation() throws ActorCreationException, ActorInvocationException { + ActorRef unNamedJoeActor = spawnSystem.createActorRef( + ActorIdentity.of("spawn-system", "UnNamedJoeActor", "UnNamedActor")); + + Class type = unNamedJoeActor.getType(); + + assertEquals(type, UnNamedActor.class); + assertNotNull(unNamedJoeActor); + + Actor.Request msg = Actor.Request.newBuilder() + .setLanguage("Erlang") + .build(); + + Optional maybeReply = + unNamedJoeActor.invoke("SetLanguage", msg, Actor.Reply.class); + + if (maybeReply.isPresent()) { + Actor.Reply reply = maybeReply.get(); + assertNotNull(reply); + assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); + } + } } diff --git a/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java b/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java new file mode 100644 index 0000000..334c676 --- /dev/null +++ b/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java @@ -0,0 +1,43 @@ +package io.eigr.spawn.test.actors; + +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.test.domain.Actor.Reply; +import io.eigr.spawn.java.test.domain.Actor.Request; +import io.eigr.spawn.java.test.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class UnNamedActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("UnNamedActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { + if (context.getState().isPresent()) { + } + + return Value.at() + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) + .build()) + .state(updateState("java")) + .reply(); + } + + private State updateState(String language) { + return State.newBuilder() + .addLanguages(language) + .build(); + } +} diff --git a/src/test/proto/eigr/functions/java/sdk/test/actor.proto b/src/test/proto/eigr/functions/java/sdk/test/actor.proto index 4e19e28..d1f7b8c 100644 --- a/src/test/proto/eigr/functions/java/sdk/test/actor.proto +++ b/src/test/proto/eigr/functions/java/sdk/test/actor.proto @@ -20,4 +20,8 @@ service JoeActor { service TestActorConstructor { rpc SetLanguage(Request) returns (Reply); +} + +service UnNamedActor { + rpc SetLanguage(Request) returns (Reply); } \ No newline at end of file From 15ca12351ad11832b1a955821e07d67ac7466ab2 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 18:34:17 -0300 Subject: [PATCH 16/19] Support stateless actors and fix some NPEs --- pom.xml | 10 ++++- src/main/java/io/eigr/spawn/api/ActorRef.java | 1 + .../transport/client/OkHttpSpawnClient.java | 2 + .../transport/server/ActorServiceHandler.java | 16 ++++++-- src/main/resources/logback.xml | 11 ++++++ src/test/java/io/eigr/spawn/SpawnTest.java | 26 +++++++++++++ .../test/actors/StatelessNamedActor.java | 37 +++++++++++++++++++ .../eigr/spawn/test/actors/UnNamedActor.java | 3 +- .../eigr/functions/java/sdk/test/actor.proto | 4 ++ src/test/resources/logback.xml | 11 ++++++ 10 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/logback.xml create mode 100644 src/test/java/io/eigr/spawn/test/actors/StatelessNamedActor.java create mode 100644 src/test/resources/logback.xml diff --git a/pom.xml b/pom.xml index c665d14..38e2e44 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,13 @@ test + + ch.qos.logback + logback-classic + 1.4.7 + test + + org.testcontainers mysql @@ -110,7 +117,8 @@ protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java io.grpc:protoc-gen-grpc-java:1.65.0:exe:${os.detected.classifier} diff --git a/src/main/java/io/eigr/spawn/api/ActorRef.java b/src/main/java/io/eigr/spawn/api/ActorRef.java index 4561847..69e80c9 100644 --- a/src/main/java/io/eigr/spawn/api/ActorRef.java +++ b/src/main/java/io/eigr/spawn/api/ActorRef.java @@ -301,6 +301,7 @@ private Optional inv .build(); Protocol.InvocationResponse resp = this.client.invoke(invocationRequestBuilder.build()); + System.out.println(String.format("Res %s", resp)); final Protocol.RequestStatus status = resp.getStatus(); switch (status.getStatus()) { case UNKNOWN: diff --git a/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java b/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java index b987faa..29c5252 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java +++ b/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java @@ -91,10 +91,12 @@ public Protocol.InvocationResponse invoke(Protocol.InvocationRequest request) th Call invocationCall = client.newCall(invocationRequest); try (Response callInvocationResponse = invocationCall.execute()){ + System.out.println(String.format("---------------- %s", callInvocationResponse)); assert callInvocationResponse.body() != null; return Protocol.InvocationResponse .parseFrom(Objects.requireNonNull(callInvocationResponse.body()).bytes()); } catch (Exception e) { + e.printStackTrace(); throw new ActorInvocationException(e); } } diff --git a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java index 7dcb5e2..bf26a7e 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java +++ b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java @@ -12,6 +12,7 @@ import io.eigr.spawn.api.Spawn; import io.eigr.spawn.api.actors.ActorContext; import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.StatelessActor; import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.workflows.SideEffect; @@ -108,9 +109,11 @@ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) th actor, commandName, maybeValueResponse); if (maybeValueResponse.isPresent()) { + System.out.println(String.format("R ---------------- %s", maybeValueResponse)); return buildResponse(maybeValueResponse.get(), actor, system); } } catch (Exception e) { + e.printStackTrace(); log.error("Error during handle request.", e); throw new IOException("Action result is null", e); } @@ -229,8 +232,13 @@ private ActorContext createActorContext(Protocol.Context context, Entity entity) */ private B buildInstance(Entity entity) throws ReflectiveOperationException { Constructor constructor = entity.getActor().getClass().getConstructor(); - StatefulActor stActor = (StatefulActor) constructor.newInstance(); - return (B) stActor.configure(entity.getCtx()); + if (entity.isStateful()) { + StatefulActor stActor = (StatefulActor) constructor.newInstance(); + return (B) stActor.configure(entity.getCtx()); + } + + StatelessActor stsActor = (StatelessActor) constructor.newInstance(); + return (B) stsActor.configure(entity.getCtx()); } /** @@ -252,10 +260,10 @@ private Optional getEntityByActor(String actor, String parent) { private Protocol.ActorInvocationResponse buildResponse(Value valueResponse, String actor, String system) { Protocol.Context.Builder updatedContextBuilder = Protocol.Context.newBuilder(); - Optional.of(valueResponse.getState()) + Optional.ofNullable(valueResponse.getState()) .ifPresent(state -> updatedContextBuilder.setState(Any.pack(state))); - Any encodedValue = Optional.of(valueResponse.getResponse()) + Any encodedValue = Optional.ofNullable(valueResponse.getResponse()) .map(value -> Any.pack(value)) .orElse(Any.pack(Protocol.Noop.getDefaultInstance())); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..b1f9bfe --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index ae53e73..0514579 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -11,6 +11,7 @@ import io.eigr.spawn.java.test.domain.Actor; import io.eigr.spawn.test.actors.ActorWithConstructor; import io.eigr.spawn.test.actors.JoeActor; +import io.eigr.spawn.test.actors.StatelessNamedActor; import io.eigr.spawn.test.actors.UnNamedActor; import org.junit.BeforeClass; import org.junit.Test; @@ -34,6 +35,7 @@ public static void setup() throws Exception { .withActor(JoeActor.class) .withActor(UnNamedActor.class) .withActor(ActorWithConstructor.class) + .withActor(StatelessNamedActor.class) .withTransportOptions( TransportOpts.builder() .port(8091) @@ -93,4 +95,28 @@ public void testUnNamedInvocation() throws ActorCreationException, ActorInvocati assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); } } + + @Test + public void testStatelessInvocation() throws ActorCreationException, ActorInvocationException { + ActorRef statelessNamedActor = spawnSystem.createActorRef( + ActorIdentity.of("spawn-system", "StatelessNamedActor")); + + Class type = statelessNamedActor.getType(); + + assertEquals(type, StatelessNamedActor.class); + assertNotNull(statelessNamedActor); + + Actor.Request msg = Actor.Request.newBuilder() + .setLanguage("Erlang") + .build(); + + Optional maybeReply = + statelessNamedActor.invoke("SetLanguage", msg, Actor.Reply.class); + + if (maybeReply.isPresent()) { + Actor.Reply reply = maybeReply.get(); + assertNotNull(reply); + assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); + } + } } diff --git a/src/test/java/io/eigr/spawn/test/actors/StatelessNamedActor.java b/src/test/java/io/eigr/spawn/test/actors/StatelessNamedActor.java new file mode 100644 index 0000000..4bfbf67 --- /dev/null +++ b/src/test/java/io/eigr/spawn/test/actors/StatelessNamedActor.java @@ -0,0 +1,37 @@ +package io.eigr.spawn.test.actors; + +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatelessActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.test.domain.Actor.Reply; +import io.eigr.spawn.java.test.domain.Actor.Request; +import io.eigr.spawn.java.test.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.action; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.name; + +public final class StatelessNamedActor extends StatelessActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("StatelessNamedActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { + if (context.getState().isPresent()) { + } + + return Value.at() + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) + .build()) + .reply(); + } +} diff --git a/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java b/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java index 334c676..6b0a75b 100644 --- a/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java +++ b/src/test/java/io/eigr/spawn/test/actors/UnNamedActor.java @@ -6,6 +6,7 @@ import io.eigr.spawn.api.actors.behaviors.ActorBehavior; import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.api.actors.behaviors.UnNamedActorBehavior; import io.eigr.spawn.internal.ActionBindings; import io.eigr.spawn.java.test.domain.Actor.Reply; import io.eigr.spawn.java.test.domain.Actor.Request; @@ -17,7 +18,7 @@ public final class UnNamedActor extends StatefulActor { @Override public ActorBehavior configure(BehaviorCtx context) { - return new NamedActorBehavior( + return new UnNamedActorBehavior( name("UnNamedActor"), action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) ); diff --git a/src/test/proto/eigr/functions/java/sdk/test/actor.proto b/src/test/proto/eigr/functions/java/sdk/test/actor.proto index d1f7b8c..81b1951 100644 --- a/src/test/proto/eigr/functions/java/sdk/test/actor.proto +++ b/src/test/proto/eigr/functions/java/sdk/test/actor.proto @@ -24,4 +24,8 @@ service TestActorConstructor { service UnNamedActor { rpc SetLanguage(Request) returns (Reply); +} + +service StatelessNamedActor { + rpc SetLanguage(Request) returns (Reply); } \ No newline at end of file diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000..b1f9bfe --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + From 4a59940a41a0c1fe31db37719e3309740e70f73d Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 19:01:19 -0300 Subject: [PATCH 17/19] Remove unascessary code --- src/main/java/io/eigr/spawn/api/ActorRef.java | 1 - .../spawn/internal/transport/client/OkHttpSpawnClient.java | 1 - .../spawn/internal/transport/server/ActorServiceHandler.java | 1 - src/test/java/io/eigr/spawn/SpawnTest.java | 4 ++-- src/test/java/io/eigr/spawn/test/actors/JoeActor.java | 3 ++- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/eigr/spawn/api/ActorRef.java b/src/main/java/io/eigr/spawn/api/ActorRef.java index 69e80c9..4561847 100644 --- a/src/main/java/io/eigr/spawn/api/ActorRef.java +++ b/src/main/java/io/eigr/spawn/api/ActorRef.java @@ -301,7 +301,6 @@ private Optional inv .build(); Protocol.InvocationResponse resp = this.client.invoke(invocationRequestBuilder.build()); - System.out.println(String.format("Res %s", resp)); final Protocol.RequestStatus status = resp.getStatus(); switch (status.getStatus()) { case UNKNOWN: diff --git a/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java b/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java index 29c5252..0ad1779 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java +++ b/src/main/java/io/eigr/spawn/internal/transport/client/OkHttpSpawnClient.java @@ -91,7 +91,6 @@ public Protocol.InvocationResponse invoke(Protocol.InvocationRequest request) th Call invocationCall = client.newCall(invocationRequest); try (Response callInvocationResponse = invocationCall.execute()){ - System.out.println(String.format("---------------- %s", callInvocationResponse)); assert callInvocationResponse.body() != null; return Protocol.InvocationResponse .parseFrom(Objects.requireNonNull(callInvocationResponse.body()).bytes()); diff --git a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java index bf26a7e..1907a60 100644 --- a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java +++ b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java @@ -109,7 +109,6 @@ private Protocol.ActorInvocationResponse handleRequest(HttpExchange exchange) th actor, commandName, maybeValueResponse); if (maybeValueResponse.isPresent()) { - System.out.println(String.format("R ---------------- %s", maybeValueResponse)); return buildResponse(maybeValueResponse.get(), actor, system); } } catch (Exception e) { diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 0514579..9d99994 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -107,7 +107,7 @@ public void testStatelessInvocation() throws ActorCreationException, ActorInvoca assertNotNull(statelessNamedActor); Actor.Request msg = Actor.Request.newBuilder() - .setLanguage("Erlang") + .setLanguage("Elixir") .build(); Optional maybeReply = @@ -116,7 +116,7 @@ public void testStatelessInvocation() throws ActorCreationException, ActorInvoca if (maybeReply.isPresent()) { Actor.Reply reply = maybeReply.get(); assertNotNull(reply); - assertEquals("Hi Erlang. Hello From Java", reply.getResponse()); + assertEquals("Hi Elixir. Hello From Java", reply.getResponse()); } } } diff --git a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java index 3929cd7..8549007 100644 --- a/src/test/java/io/eigr/spawn/test/actors/JoeActor.java +++ b/src/test/java/io/eigr/spawn/test/actors/JoeActor.java @@ -26,13 +26,14 @@ public ActorBehavior configure(BehaviorCtx context) { private Value setLanguage(ActorContext context, Request msg) { if (context.getState().isPresent()) { + //Do something with previous state } return Value.at() .response(Reply.newBuilder() .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) .build()) - .state(updateState("java")) + .state(updateState(msg.getLanguage())) .reply(); } From 8717eccdb26e138c524b86157d6027df22fd40c3 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Thu, 25 Jul 2024 20:31:26 -0300 Subject: [PATCH 18/19] Update readme with new Java API --- Makefile | 2 +- README.md | 580 ++++++++++++++++++++++++++++++++---------------------- pom.xml | 2 +- 3 files changed, 342 insertions(+), 242 deletions(-) diff --git a/Makefile b/Makefile index 6e2d3c0..c0ec348 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # Variables -VERSION := 1.0-SNAPSHOT +VERSION := 1.3.0 PACKAGE := target/spawn-java-demo-${VERSION}-shaded.jar clean: diff --git a/README.md b/README.md index 42be8ec..d703f62 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The second thing we have to do is add the spawn dependency to the project. com.github.eigr spawn-java-std-sdk - v1.2.7 + v1.3.0 ``` We're also going to configure a few things for our application build to work, including compiling the protobuf files. @@ -128,7 +128,7 @@ See below a full example of the pom.xml file: com.github.eigr spawn-java-std-sdk - v1.2.7 + v1.3.0 ch.qos.logback @@ -254,19 +254,22 @@ And let's populate this file with the following content: syntax = "proto3"; package domain; - option java_package = "io.eigr.spawn.java.demo.domain"; -message JoeState { - repeated string languages = 1; +message State { + repeated string languages = 1; } message Request { - string language = 1; + string language = 1; } message Reply { - string response = 1; + string response = 1; +} + +service JoeActor { + rpc SetLanguage(Request) returns (Reply); } ``` @@ -287,35 +290,45 @@ Populate this file with the following content: ```Java package io.eigr.spawn.java.demo; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.java.demo.domain.Domain; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; -@StatefulNamedActor(name = "joe", stateType = Domain.JoeState.class) -public class Joe { - private static final Logger log = LoggerFactory.getLogger(Joe.class); +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; - @Action(name = "hi", inputType = Domain.Request.class) - public Value hi(Domain.Request msg, ActorContext context) { - log.info("Received invocation. Message: {}. Context: {}", msg, context); +public final class JoeActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("JoeActor"), + channel("test.channel"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { if (context.getState().isPresent()) { - log.info("State is present and value is {}", context.getState().get()); + //Do something with previous state } return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) .build()) - .state(updateState("erlang")) + .state(updateState(msg.getLanguage())) .reply(); } - private Domain.JoeState updateState(String language) { - return Domain.JoeState.newBuilder() + private State updateState(String language) { + return State.newBuilder() .addLanguages(language) .build(); } @@ -434,37 +447,37 @@ In this case, it is enough to declare a constructor that receives a single argum ```java package io.eigr.spawn.java.demo; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.java.demo.domain.Domain; - -import java.util.Map; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; -@StatefulNamedActor(name = "joe", stateful = true, stateType = Domain.JoeState.class, channel = "test") -public final class Joe { - private final String someValue; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.action; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.name; +public final class JoeActor extends StatefulActor { - public Joe(Map args) { - this.someValue = args.get("someKey"); - } + private String defaultMessage; - @Action(inputType = Domain.Request.class) - public Value setLanguage(Domain.Request msg, ActorContext context) { - return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") - .build()) - .state(updateState("java")) - .reply(); + @Override + public ActorBehavior configure(BehaviorCtx context) { + defaultMessage = context.getInjector().getInstance(String.class); + return new NamedActorBehavior( + name("JoeActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); } // ... } ``` -Then you also need to register your Actor passing arguments like as follows: +Then you also need to register your Actor using injector : ```java package io.eigr.spawn.java.demo; @@ -476,12 +489,12 @@ import java.util.Map; public class App { public static void main(String[] args) { - Map actorConstructorArgs = new HashMap<>(); - actorConstructorArgs.put("someKey", "someValue"); + DependencyInjector injector = SimpleDependencyInjector.createInjector(); + injector.bind(String.class, "Hello with Constructor"); Spawn spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system") - .withActor(Joe.class, actorConstructorArgs, arg -> new Joe((Map) arg)) + .create("spawn-system", injector) + .withActor(Joe.class) .build(); spawnSystem.start(); @@ -543,45 +556,52 @@ public class MessageServiceImpl implements MessageService { } ``` -2. Second, let's define an actor so that it receives an instance of the DependencyInjector class through the class constructor: +2. Second, let's define an actor so that it receives an instance of the DependencyInjector class through the context of configure method: ```java -package io.eigr.spawn.test.actors; +package io.eigr.spawn.java.demo; import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; import io.eigr.spawn.api.actors.Value; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.extensions.DependencyInjector; -import io.eigr.spawn.java.test.domain.Actor; - -@StatefulNamedActor(name = "test_actor_constructor", stateType = Actor.State.class) -public final class Joe { - - private final MessageService messageService; - - public Joe(DependencyInjector injector) { - // Note how to use dependency injection here to get a concrete class of MessageService. - this.messageService = injector.getInstance(MessageService.class); - } - - @Action(inputType = Actor.Request.class) - public Value setLanguage(Actor.Request msg, ActorContext context) { - return Value.at() - .response(Actor.Reply.newBuilder() - .setResponse(messageService.getDefaultMessage()) - .build()) - .state(updateState("java")) - .reply(); - } - - private Actor.State updateState(String language) { - return Actor.State.newBuilder() - .addLanguages(language) - .build(); - } -} +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.action; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.name; +public final class JoeActor extends StatefulActor { + + private String defaultMessage; + + @Override + public ActorBehavior configure(BehaviorCtx context) { + defaultMessage = context.getInjector().getInstance(String.class); + return new NamedActorBehavior( + name("JoeActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { + return Value.at() + .response(Reply.newBuilder() + .setResponse(defaultMessage) + .build()) + .state(updateState("java")) + .reply(); + } + private State updateState(String language) { + return State.newBuilder() + .addLanguages(language) + .build(); + } +} ``` 3. Then you can pass your dependent classes this way to your Actor: @@ -600,10 +620,13 @@ public class App { If you try to add different instances of the same type you will receive an error. */ injector.bind(MessageService.class, new MessageServiceImpl()); + + // or using alias for put different values of same key types + injector.bind(MessageService.class, "myMessageService", new MessageServiceImpl()); Spawn spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system") - .withActor(Joe.class, injector -> new Joe((DependencyInjector) injector)) + .create("spawn-system", injector) + .withActor(Joe.class) .build(); spawnSystem.start(); @@ -614,7 +637,7 @@ public class App { It is important to note that this helper mechanism does not currently implement any type of complex dependency graph. Therefore, it will not build objects based on complex dependencies nor take care of the object lifecycle for you. In other words, all instances added through the bind method of the SimpleDependencyInjector class will be singletons. -This mechanism works much more like a bucket of objects that will be forwarded via your actor's constructor. +This mechanism works much more like a bucket of objects that will be forwarded via your actor's context. > **_NOTE:_** **Why not use the java cdi 2.0 spec?** Our goals are to keep the SDK for standalone Java applications very simple. We consider that implementing the entire specification would not be viable for us at the moment. It would be a lot of effort and energy expenditure that we consider spending on other parts of the ecosystem that we think will guarantee us more benefits. @@ -644,28 +667,47 @@ or Stateless, those who do not need to maintain the state. For this the developer just needs to make use of the correct annotation. For example, I could declare a Serverless Actor using the following code: ```java -package io.eigr.spawn.test.actors; +package io.eigr.spawn.java.demo.actors; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateless.StatelessNamedActor; -import io.eigr.spawn.java.test.domain.Actor; - -@StatelessNamedActor(name = "test_joe") -public class JoeActor { - @Action - public Value hi(Actor.Request msg, ActorContext context) { +import io.eigr.spawn.api.actors.StatelessActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.action; +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.name; + +public final class StatelessNamedActor extends StatelessActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("StatelessNamedActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { + if (context.getState().isPresent()) { + } + return Value.at() - .response(Actor.Reply.newBuilder() - .setResponse("Hello From Java") + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) .build()) .reply(); } } + ``` -Other than that the same Named, UnNamed types are supported. Just use the StatelessNamed or StatelessUnNamed annotations. +Other than that the same Named, UnNamed types are supported. Just use the StatelessNamedActor or StatelessUnNamedActor super class. ### Considerations about Spawn actors @@ -683,33 +725,51 @@ Whereas unnamed and pooled actors are instantiated the first time only when they Actors in Spawn can subscribe to a thread and receive, as well as broadcast, events for a given thread. -To consume from a topic, you just need to configure the Actor annotation using the channel option as follows: +To consume from a topic, you just need to configure the Actor using the channel option as follows: -```Java -@StatefulNamedActor(name = "joe", stateful = true, stateType = Domain.JoeState.class, channel = "test") ``` -In the case above, the Actor `joe` was configured to receive events that are forwarded to the topic called `test`. +return new NamedActorBehavior( + name("JoeActor"), + channel("test.channel"), +); +``` +In the case above, the Actor `JoeActor` was configured to receive events that are forwarded to the topic called `test.channel`. To produce events in a topic, just use the Broadcast Workflow. The example below demonstrates a complete example of producing and consuming events. In this case, the same actor is the event consumer and producer, but in a more realistic scenario, different actors would be involved in these processes. ```Java -package io.eigr.spawn.java.demo; +package io.eigr.spawn.java.demo.actors; +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; import io.eigr.spawn.api.actors.workflows.Broadcast; -// some imports omitted for brevity +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; -@StatefulNamedActor(name = "joe", stateType = Domain.JoeState.class, channel = "test") -public class Joe { - @TimerAction(name = "hi", period = 60000) - public Value hi(ActorContext context) { - Domain.Request msg = Domain.Request.newBuilder() - .setLanguage("erlang") - .build(); +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class LoopActor extends StatefulActor { + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("LoopActor"), + channel("test.channel"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { return Value.at() - .flow(Broadcast.to("test", "setLanguage", msg)) + .flow(Broadcast.to("test.channel", "setLanguage", msg)) .response(Domain.Reply.newBuilder() .setResponse("Hello From Erlang") .build()) @@ -717,16 +777,7 @@ public class Joe { .reply(); } - @Action(inputType = Domain.Request.class) - public Value setLanguage(Domain.Request msg, ActorContext context) { - return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") - .build()) - .state(updateState("java")) - .reply(); - } - // .... + // ... } ``` @@ -736,36 +787,45 @@ Actors can also emit side effects to other Actors as part of their response. See an example: ```Java -package io.eigr.spawn.java.demo; +package io.eigr.spawn.java.demo.actors; -import io.eigr.spawn.api.ActorIdentity; -import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.workflows.SideEffect; -import io.eigr.spawn.java.demo.domain.Domain; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class JoeActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("JoeActor"), + channel("test.channel"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } -@StatefulNamedActor(name = "side_effect_actor", stateType = Domain.State.class) -public class SideEffectActorExample { - @Action - public Value setLanguage(Domain.Request msg, ActorContext ctx) throws Exception { - // Create a ActorReference to send side effect message + private Value setLanguage(ActorContext context, Request msg) { ActorRef sideEffectReceiverActor = ctx.getSpawnSystem() - .createActorRef(ActorIdentity.of("spawn-system", "mike", "abs_actor")); + .createActorRef(ActorIdentity.of("spawn-system", "MikeFriendActor", "MikeParentActor")); return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") - .build()) - .state(updateState("java")) .flow(SideEffect.to(sideEffectReceiverActor, "setLanguage", msg)) - //.flow(SideEffect.to(emailSenderReceiverActor, "sendEmail", emailMessage)) - //.flow(SideEffect.to(otherReceiverActor, "otherAction", otherMessage)) + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) + .build()) + .state(updateState(msg.getLanguage())) .noReply(); } - + // .... } ``` @@ -783,31 +843,34 @@ an action in another Actor. See an example: ```Java -package io.eigr.spawn.java.demo; +package io.eigr.spawn.java.demo.actors; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.ActorIdentity; -import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.workflows.Forward; -import io.eigr.spawn.java.demo.domain.Domain; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@StatefulNamedActor(name = "routing_actor", stateType = Domain.State.class) -public class ForwardExample { - private static final Logger log = LoggerFactory.getLogger(ForwardExample.class); - - @Action - public Value setLanguage(Domain.Request msg, ActorContext ctx) throws Exception { - log.info("Received invocation. Message: {}. Context: {}", msg, ctx); - if (ctx.getState().isPresent()) { - log.info("State is present and value is {}", ctx.getState().get()); - } +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class RoutingActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("RoutingActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { ActorRef forwardedActor = ctx.getSpawnSystem() - .createActorRef(ActorIdentity.of("spawn-system", "mike", "abs_actor")); + .createActorRef(ActorIdentity.of("spawn-system", "MikeFriendActor", "MikeActor")); return Value.at() .flow(Forward.to(forwardedActor, "setLanguage")) @@ -826,39 +889,45 @@ In the end, just like in a Forward, it is the response of the last Actor in the Example: ```Java -package io.eigr.spawn.java.demo; +package io.eigr.spawn.java.demo.actors; -import io.eigr.spawn.api.actors.Value; import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.ActorIdentity; -import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.api.actors.workflows.Pipe; -import io.eigr.spawn.java.demo.domain.Domain; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class PipeActor extends StatefulActor { -@StatefulNamedActor(name = "pipe_actor", stateType = Domain.State.class) -public class PipeActorExample { + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("PipeActor"), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } - @Action - public Value setLanguage(Domain.Request msg, ActorContext ctx) throws Exception { + private Value setLanguage(ActorContext context, Request msg) { ActorRef pipeReceiverActor = ctx.getSpawnSystem() - .createActorRef(ActorIdentity.of("spawn-system", "joe")); + .createActorRef(ActorIdentity.of("spawn-system", "JoeActor")); return Value.at() - .response(Domain.Reply.newBuilder() + .response(Reply.newBuilder() .setResponse("Hello From Java") .build()) .flow(Pipe.to(pipeReceiverActor, "someAction")) .state(updateState("java")) .noReply(); } - - private Domain.State updateState(String language) { - return Domain.State.newBuilder() - .addLanguages(language) - .build(); - } + + // ... } ``` @@ -875,32 +944,51 @@ during the moment of the Actor's deactivation. That is, data is saved at regular intervals asynchronously while the Actor is active and once synchronously when the Actor suffers a deactivation, when it is turned off. -These snapshots happen from time to time. And this time is configurable through the ***snapshotTimeout*** property of -the ***StatefulNamedActor*** or ***UnStatefulNamedActor*** annotation. +These snapshots happen from time to time. And this time is configurable through the ***snapshotTimeout*** method of +the ***NamedActorBehavior*** or ***UnNamedActorBehavior*** class. However, you can tell the Spawn runtime that you want it to persist the data immediately synchronously after executing an Action. And this can be done in the following way: Example: ```Java -import io.eigr.spawn.api.actors.Value; +package io.eigr.spawn.test.actors; + import io.eigr.spawn.api.actors.ActorContext; -import io.eigr.spawn.api.actors.annotations.Action; -import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor; -import io.eigr.spawn.java.demo.domain.Domain; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.NamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; -@StatefulNamedActor(name = "joe", stateType = Domain.JoeState.class) -public final class Joe { - @Action(inputType = Domain.Request.class) - public Value setLanguage(Domain.Request msg, ActorContext context) { +public final class JoeActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new NamedActorBehavior( + name("JoeActor"), + snapshot(1000), + deactivated(60000), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) .build()) - .state(updateState("java"), true) + .state(updateState(msg.getLanguage()), true) .reply(); } - // ... + + // ... } ``` @@ -938,13 +1026,13 @@ In the sections below we will give some examples of how to invoke different type To invoke an actor named like the one we defined in section [Getting Started](#getting-started) we could do as follows: ```Java -ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "joe")); +ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "JoeActor")); -Domain.Request msg = Domain.Request.newBuilder() +Request msg = Request.newBuilder() .setLanguage("erlang") .build(); -Optional maybeResponse = joeActor.invoke("setLanguage", msg, Domain.Reply.class); +Optional maybeResponse = joeActor.invoke("setLanguage", msg, Reply.class); Domain.Reply reply = maybeResponse.get(); ``` @@ -976,7 +1064,7 @@ public class App { spawnSystem.start(); - ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "joe")); + ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "JoeActor")); Domain.Request msg = Domain.Request.newBuilder() .setLanguage("erlang") @@ -997,50 +1085,67 @@ To better exemplify, let's first show the Actor's definition code and later how name at runtime: ```java -package io.eigr.spawn.java.demo; -// omitted imports for brevity... - -@UnStatefulNamedActor(name = "abs_actor", stateful = true, stateType = Domain.State.class) -public class AbstractActor { - @Action(inputType = Domain.Request.class) - public Value setLanguage(Domain.Request msg, ActorContext context) { - return Value.at() - .response(Domain.Reply.newBuilder() - .setResponse("Hello From Java") - .build()) - .state(updateState("java")) - .reply(); - } - - private Domain.State updateState(String language) { - return Domain.State.newBuilder() - .addLanguages(language) - .build(); - } +package io.eigr.spawn.test.actors; + +import io.eigr.spawn.api.actors.ActorContext; +import io.eigr.spawn.api.actors.StatefulActor; +import io.eigr.spawn.api.actors.Value; +import io.eigr.spawn.api.actors.behaviors.ActorBehavior; +import io.eigr.spawn.api.actors.behaviors.BehaviorCtx; +import io.eigr.spawn.api.actors.behaviors.UnNamedActorBehavior; +import io.eigr.spawn.internal.ActionBindings; +import io.eigr.spawn.java.demo.domain.Actor.Reply; +import io.eigr.spawn.java.demo.domain.Actor.Request; +import io.eigr.spawn.java.demo.domain.Actor.State; + +import static io.eigr.spawn.api.actors.behaviors.ActorBehavior.*; + +public final class MikeActor extends StatefulActor { + + @Override + public ActorBehavior configure(BehaviorCtx context) { + return new UnNamedActorBehavior( + name("MikeActor"), + snapshot(1000), + deactivated(60000), + action("SetLanguage", ActionBindings.of(Request.class, this::setLanguage)) + ); + } + + private Value setLanguage(ActorContext context, Request msg) { + return Value.at() + .response(Reply.newBuilder() + .setResponse(String.format("Hi %s. Hello From Java", msg.getLanguage())) + .build()) + .state(updateState(msg.getLanguage()), true) + .reply(); + } + + // ... } ``` So you could define and call this actor at runtime like this: ```Java -ActorRef mike = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "mike", "abs_actor")); +ActorRef mike = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "MikeInstanceActor", "MikeActor")); -Domain.Request msg = Domain.Request.newBuilder() +Request msg = Request.newBuilder() .setLanguage("erlang") .build(); -Optional maybeResponse = mike.invoke("setLanguage", msg, Domain.Reply.class); +Optional maybeResponse = mike.invoke("setLanguage", msg, Reply.class); Domain.Reply reply = maybeResponse.get(); ``` The important part of the code above is the following snippet: ```Java -ActorRef mike = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "mike", "abs_actor")); +ActorRef mike = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "MikeInstanceActor", "MikeActor")); ``` -These tells Spawn that this actor will actually be named at runtime. The name parameter with value "mike" -in this case is just a reference to "abs_actor" Actor that will be used later +These tells Spawn that this actor will actually be named at runtime. The name parameter with value "MikeInstanceActor" +in this case is just a reference to "MikeActor" Actor that will be used later so that we can actually create an instance of the real Actor. ### Async @@ -1063,17 +1168,12 @@ It is possible to change the request waiting timeout using the invocation option ```Java package io.eigr.spawn.java.demo; -import io.eigr.spawn.api.ActorIdentity; -import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.InvocationOpts; -import io.eigr.spawn.api.Spawn; -import io.eigr.spawn.api.TransportOpts; -import io.eigr.spawn.java.demo.domain.Domain; +// omitted for brevity import java.util.Optional; public class App { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { Spawn spawnSystem = new Spawn.SpawnSystem() .create("spawn-system") .withActor(Joe.class) @@ -1081,9 +1181,9 @@ public class App { spawnSystem.start(); - ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "joe")); + ActorRef joeActor = spawnSystem.createActorRef(ActorIdentity.of("spawn-system", "JoeActor")); - Domain.Request msg = Domain.Request.newBuilder() + Request msg = Request.newBuilder() .setLanguage("erlang") .build(); @@ -1091,7 +1191,7 @@ public class App { .timeoutSeconds(Duration.ofSeconds(30)) .build(); - Optional maybeResponse = joeActor.invoke("setLanguage", msg, Domain.Reply.class, opts); + Optional maybeResponse = joeActor.invoke("setLanguage", msg, Reply.class, opts); } } ``` diff --git a/pom.xml b/pom.xml index 38e2e44..4e43e8b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.eigr.spawn spawn-java-std-sdk jar - 1.2.7 + 1.3.0 spawn-java-std-sdk http://maven.apache.org From b7fcdc17a5d0eba843a205198ce83809329cedc9 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Alves Date: Thu, 25 Jul 2024 23:14:59 -0300 Subject: [PATCH 19/19] Upgrade to run test with testcontainers. --- .../eigr/spawn/AbstractContainerBaseTest.java | 4 ++ .../java/io/eigr/spawn/ContainerTest.java | 37 ------------------ src/test/java/io/eigr/spawn/SpawnTest.java | 39 ++----------------- src/test/java/io/eigr/spawn/WorkflowTest.java | 20 ++-------- 4 files changed, 11 insertions(+), 89 deletions(-) delete mode 100644 src/test/java/io/eigr/spawn/ContainerTest.java diff --git a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java index eb5d72d..6835f6e 100644 --- a/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java +++ b/src/test/java/io/eigr/spawn/AbstractContainerBaseTest.java @@ -9,6 +9,8 @@ import io.eigr.spawn.api.extensions.SimpleDependencyInjector; import io.eigr.spawn.test.actors.ActorWithConstructor; import io.eigr.spawn.test.actors.JoeActor; +import io.eigr.spawn.test.actors.StatelessNamedActor; +import io.eigr.spawn.test.actors.UnNamedActor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.Testcontainers; @@ -55,7 +57,9 @@ abstract class AbstractContainerBaseTest { spawnSystem = new Spawn.SpawnSystem() .create(spawnSystemName, injector) .withActor(JoeActor.class) + .withActor(UnNamedActor.class) .withActor(ActorWithConstructor.class) + .withActor(StatelessNamedActor.class) .withTerminationGracePeriodSeconds(5) .withTransportOptions(TransportOpts.builder() .port(8091) diff --git a/src/test/java/io/eigr/spawn/ContainerTest.java b/src/test/java/io/eigr/spawn/ContainerTest.java deleted file mode 100644 index 6440e8a..0000000 --- a/src/test/java/io/eigr/spawn/ContainerTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.eigr.spawn; - -import io.eigr.spawn.api.ActorIdentity; -import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.exceptions.SpawnException; -import io.eigr.spawn.java.test.domain.Actor; -import org.junit.Test; - -import java.util.Optional; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public final class ContainerTest extends AbstractContainerBaseTest { - - @Test - public void testApp() throws SpawnException { - ActorRef joeActor = spawnSystem.createActorRef( - ActorIdentity.of(spawnSystemName, "test_joe")); - - Actor.Request msg = Actor.Request.newBuilder() - .setLanguage("erlang") - .build(); - - Optional maybeReply = - joeActor.invoke("setLanguage", msg, Actor.Reply.class); - - if (maybeReply.isPresent()) { - Actor.Reply reply = maybeReply.get(); - assertNotNull(reply); - assertEquals("Hello From Java", reply.getResponse()); - } else { - throw new RuntimeException("Error"); - } - - } -} diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java index 9d99994..a7f50b1 100644 --- a/src/test/java/io/eigr/spawn/SpawnTest.java +++ b/src/test/java/io/eigr/spawn/SpawnTest.java @@ -2,18 +2,12 @@ import io.eigr.spawn.api.ActorIdentity; import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.Spawn; -import io.eigr.spawn.api.TransportOpts; import io.eigr.spawn.api.exceptions.ActorCreationException; import io.eigr.spawn.api.exceptions.ActorInvocationException; -import io.eigr.spawn.api.extensions.DependencyInjector; -import io.eigr.spawn.api.extensions.SimpleDependencyInjector; import io.eigr.spawn.java.test.domain.Actor; -import io.eigr.spawn.test.actors.ActorWithConstructor; import io.eigr.spawn.test.actors.JoeActor; import io.eigr.spawn.test.actors.StatelessNamedActor; import io.eigr.spawn.test.actors.UnNamedActor; -import org.junit.BeforeClass; import org.junit.Test; import java.util.Optional; @@ -21,37 +15,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -public class SpawnTest { - - private static Spawn spawnSystem; - - @BeforeClass - public static void setup() throws Exception { - DependencyInjector injector = SimpleDependencyInjector.createInjector(); - injector.bind(String.class, "Hello with Constructor"); - - spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system", injector) - .withActor(JoeActor.class) - .withActor(UnNamedActor.class) - .withActor(ActorWithConstructor.class) - .withActor(StatelessNamedActor.class) - .withTransportOptions( - TransportOpts.builder() - .port(8091) - .proxyPort(9003) - .build() - ) - .build(); - - System.out.println("Starting ActorSystem..."); - spawnSystem.start(); - } +public class SpawnTest extends AbstractContainerBaseTest { @Test public void testNamedInvocation() throws ActorCreationException, ActorInvocationException { ActorRef joeActor = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "JoeActor")); + ActorIdentity.of(spawnSystemName, "JoeActor")); Class type = joeActor.getType(); @@ -75,7 +44,7 @@ public void testNamedInvocation() throws ActorCreationException, ActorInvocation @Test public void testUnNamedInvocation() throws ActorCreationException, ActorInvocationException { ActorRef unNamedJoeActor = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "UnNamedJoeActor", "UnNamedActor")); + ActorIdentity.of(spawnSystemName, "UnNamedJoeActor", "UnNamedActor")); Class type = unNamedJoeActor.getType(); @@ -99,7 +68,7 @@ public void testUnNamedInvocation() throws ActorCreationException, ActorInvocati @Test public void testStatelessInvocation() throws ActorCreationException, ActorInvocationException { ActorRef statelessNamedActor = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "StatelessNamedActor")); + ActorIdentity.of(spawnSystemName, "StatelessNamedActor")); Class type = statelessNamedActor.getType(); diff --git a/src/test/java/io/eigr/spawn/WorkflowTest.java b/src/test/java/io/eigr/spawn/WorkflowTest.java index bb1d3eb..8e66d56 100644 --- a/src/test/java/io/eigr/spawn/WorkflowTest.java +++ b/src/test/java/io/eigr/spawn/WorkflowTest.java @@ -3,39 +3,25 @@ import io.eigr.functions.protocol.Protocol; import io.eigr.spawn.api.ActorIdentity; import io.eigr.spawn.api.ActorRef; -import io.eigr.spawn.api.Spawn; -import io.eigr.spawn.api.TransportOpts; import io.eigr.spawn.api.actors.workflows.Broadcast; import io.eigr.spawn.api.actors.workflows.Forward; import io.eigr.spawn.api.actors.workflows.Pipe; import io.eigr.spawn.api.actors.workflows.SideEffect; import io.eigr.spawn.api.exceptions.SpawnException; import io.eigr.spawn.java.test.domain.Actor; -import io.eigr.spawn.test.actors.JoeActor; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; -public class WorkflowTest { +public class WorkflowTest extends AbstractContainerBaseTest { private ActorRef joeActorRef; - private Spawn spawnSystem; + @Before public void before() throws SpawnException { - spawnSystem = new Spawn.SpawnSystem() - .create("spawn-system") - .withActor(JoeActor.class) - .withTransportOptions( - TransportOpts.builder() - .port(8091) - .proxyPort(9003) - .build() - ) - .build(); - joeActorRef = spawnSystem.createActorRef( - ActorIdentity.of("spawn-system", "joe")); + ActorIdentity.of(spawnSystemName, "joe")); } @Test