diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java index 6cf19f784..ad8d5d2f8 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java @@ -5,11 +5,14 @@ package io.dapr.examples.actors.http; +import io.dapr.actors.ActorMethod; +import io.dapr.actors.ActorType; import reactor.core.publisher.Mono; /** * Example of implementation of an Actor. */ +@ActorType(name = "DemoActor") public interface DemoActor { void registerReminder(); @@ -18,5 +21,6 @@ public interface DemoActor { void clock(String message); + @ActorMethod(returns = Integer.class) Mono incrementAndGet(int delta); } diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java index 2dc0b7c58..b5e9efbc5 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java @@ -6,7 +6,6 @@ package io.dapr.examples.actors.http; import io.dapr.actors.ActorId; -import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyBuilder; import java.util.ArrayList; @@ -30,16 +29,17 @@ public class DemoActorClient { * @throws InterruptedException If program has been interrupted. */ public static void main(String[] args) throws InterruptedException { - ActorProxyBuilder builder = new ActorProxyBuilder("DemoActor"); + ActorProxyBuilder builder = new ActorProxyBuilder(DemoActor.class); List threads = new ArrayList<>(NUM_ACTORS); // Creates multiple actors. for (int i = 0; i < NUM_ACTORS; i++) { - ActorProxy actor = builder.build(ActorId.createRandom()); + ActorId actorId = ActorId.createRandom(); + DemoActor actor = builder.build(actorId); // Start a thread per actor. - Thread thread = new Thread(() -> callActorForever(actor)); + Thread thread = new Thread(() -> callActorForever(actorId.toString(), actor)); thread.start(); threads.add(thread); } @@ -54,21 +54,22 @@ public class DemoActorClient { /** * Makes multiple method calls into actor until interrupted. + * @param actorId Actor's identifier. * @param actor Actor to be invoked. */ - private static final void callActorForever(ActorProxy actor) { + private static final void callActorForever(String actorId, DemoActor actor) { // First, register reminder. - actor.invokeActorMethod("registerReminder").block(); + actor.registerReminder(); // Now, we run until thread is interrupted. while (!Thread.currentThread().isInterrupted()) { // Invoke actor method to increment counter by 1, then build message. - int messageNumber = actor.invokeActorMethod("incrementAndGet", 1, int.class).block(); - String message = String.format("Actor %s said message #%d", actor.getActorId().toString(), messageNumber); + int messageNumber = actor.incrementAndGet(1).block(); + String message = String.format("Actor %s said message #%d", actorId, messageNumber); // Invoke the 'say' method in actor. - String result = actor.invokeActorMethod("say", message, String.class).block(); - System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result)); + String result = actor.say(message); + System.out.println(String.format("Actor %s got a reply: %s", actorId, result)); try { // Waits for up to 1 second. diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java index b10d938a7..50cea4574 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java @@ -8,7 +8,6 @@ package io.dapr.examples.actors.http; import io.dapr.actors.ActorId; import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.ActorRuntimeContext; -import io.dapr.actors.runtime.ActorType; import io.dapr.actors.runtime.Remindable; import reactor.core.publisher.Mono; @@ -21,7 +20,6 @@ import java.util.TimeZone; /** * Implementation of the DemoActor for the server side. */ -@ActorType(name = "DemoActor") public class DemoActorImpl extends AbstractActor implements DemoActor, Remindable { /** diff --git a/examples/src/main/java/io/dapr/examples/actors/http/README.md b/examples/src/main/java/io/dapr/examples/actors/http/README.md index 97650f9db..f48706ebe 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/README.md +++ b/examples/src/main/java/io/dapr/examples/actors/http/README.md @@ -35,7 +35,7 @@ mvn install ### Running the Demo actor service -The first element is to run is `DemoActorService`. Its job is registering the `DemoActor` implementation in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below: +The first Java class is `DemoActorService`. Its job is to register an implementation of `DemoActor` in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below: ```java @SpringBootApplication @@ -59,7 +59,6 @@ This application uses `ActorRuntime.getInstance().registerActor()` in order to r See [DemoActorImpl](DemoActorImpl.java) for details on the implementation of an actor: ```java -@ActorType(name = "DemoActor") public class DemoActorImpl extends AbstractActor implements DemoActor, Remindable { //... @@ -99,7 +98,28 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl } } ``` -An actor inherits from `AbstractActor` and implements the constructor to pass through `ActorRuntimeContext` and `ActorId`. By default, the actor's name will be the same as the class' name. Optionally, it can be annotated with `ActorType` and override the actor's name. The actor's methods can be synchronously or use [Project Reactor's Mono](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html) return type. Finally, state management is done via methods in `super.getActorStateManager()`. +An actor inherits from `AbstractActor` and implements the constructor to pass through `ActorRuntimeContext` and `ActorId`. By default, the actor's name will be the same as the class' name. Optionally, it can be annotated with `ActorType` and override the actor's name. The actor's methods can be synchronously or use [Project Reactor's Mono](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html) return type. Finally, state management is done via methods in `super.getActorStateManager()`. The `DemoActor` interface is used by the Actor runtime and also client. See how `DemoActor` interface can be annotated as Dapr Actor. + +```java +/** + * Example of implementation of an Actor. + */ +@ActorType(name = "DemoActor") +public interface DemoActor { + + void registerReminder(); + + String say(String something); + + void clock(String message); + + @ActorMethod(returns = Integer.class) + Mono incrementAndGet(int delta); +} + +``` + +The `@ActorType` annotation indicates the Dapr Java SDK that this interface is an Actor Type, allowing a name for the type to be defined. Some methods can return a `Mono` object. In these cases, the `@ActorMethod` annotation is used to hint the Dapr Java SDK of the type encapsulated in the `Mono` object. You can read more about Java generic type erasure [here](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html). Now, execute the following script in order to run DemoActorService: @@ -120,31 +140,32 @@ public class DemoActorClient { private static final int NUM_ACTORS = 3; public static void main(String[] args) throws InterruptedException { + ActorProxyBuilder builder = new ActorProxyBuilder(DemoActor.class); ///... for (int i = 0; i < NUM_ACTORS; i++) { - ActorProxy actor = builder.build(ActorId.createRandom()); + DemoActor actor = builder.build(ActorId.createRandom()); // Start a thread per actor. - Thread thread = new Thread(() -> callActorForever(actor)); + Thread thread = new Thread(() -> callActorForever(actorId.toString(), actor)); thread.start(); threads.add(thread); } ///... } - private static final void callActorForever(ActorProxy actor) { + private static final void callActorForever(String actorId, DemoActor actor) { // First, register reminder. - actor.invokeActorMethod("registerReminder").block(); + actor.registerReminder(); // Now, we run until thread is interrupted. while (!Thread.currentThread().isInterrupted()) { // Invoke actor method to increment counter by 1, then build message. - int messageNumber = actor.invokeActorMethod("incrementAndGet", 1, int.class).block(); - String message = String.format("Actor %s said message #%d", actor.getActorId().toString(), messageNumber); + int messageNumber = actor.incrementAndGet(1).block(); + String message = String.format("Actor %s said message #%d", actorId, messageNumber); // Invoke the 'say' method in actor. - String result = actor.invokeActorMethod("say", message, String.class).block(); - System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result)); + String result = actor.say(message); + System.out.println(String.format("Actor %s got a reply: %s", actorId, result)); try { // Waits for up to 1 second. @@ -158,7 +179,7 @@ public class DemoActorClient { } ``` -First, the client defines how many actors it is going to create. Then the main method declares a `ActorProxyBuilder` for the `DemoActor` class to create `ActorProxy` instances, which are the actor representation provided by the SDK. The code executes the `callActorForever` private method once per actor. This method triggers the DemoActor's implementation by using `actor.invokeActorMethod()`. Initially, it will invoke `registerReminder()`, which sets the due time and period for the reminder. Then, `incrementAndGet()` increments a counter, persists it and sends it back as response. Finally `say` method which will print a message containing the received string along with the formatted server time. +First, the client defines how many actors it is going to create. Then the main method declares a `ActorProxyBuilder` to create instances of the `DemoActor` interface, which are implemented automatically by the SDK and make remote calls to the equivalent methods in Actor runtime. The code executes the `callActorForever` private method once per actor. Initially, it will invoke `registerReminder()`, which sets the due time and period for the reminder. Then, `incrementAndGet()` increments a counter, persists it and sends it back as response. Finally `say` method which will print a message containing the received string along with the formatted server time. Use the follow command to execute the DemoActorClient: @@ -180,5 +201,10 @@ After invoking `incrementAndGet`, the code invokes `say` method (you'll see thes On the other hand, the console for `DemoActorService` is also responding to the remote invocations: ![actordemo2](../../../../../../resources/img/demo-actor-service.png) +For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation. -For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation. +### Limitations + +Currently, these are the limitations in the Java SDK for Dapr: +* Actor interface cannot have overloaded methods (methods with same name but different signature). +* Actor methods can only have zero or one parameter. \ No newline at end of file diff --git a/sdk-actors/src/main/java/io/dapr/actors/ActorMethod.java b/sdk-actors/src/main/java/io/dapr/actors/ActorMethod.java new file mode 100644 index 000000000..424a41ccc --- /dev/null +++ b/sdk-actors/src/main/java/io/dapr/actors/ActorMethod.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ActorMethod { + + /** + * Actor's method return type. This is required when result object is within a Mono response. + * + * @return Actor's method return type. + */ + Class returns(); +} diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorType.java b/sdk-actors/src/main/java/io/dapr/actors/ActorType.java similarity index 94% rename from sdk-actors/src/main/java/io/dapr/actors/runtime/ActorType.java rename to sdk-actors/src/main/java/io/dapr/actors/ActorType.java index 9cc8f4a40..6a0ed3510 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorType.java +++ b/sdk-actors/src/main/java/io/dapr/actors/ActorType.java @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -package io.dapr.actors.runtime; +package io.dapr.actors; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/sdk-actors/src/main/java/io/dapr/actors/ActorUtils.java b/sdk-actors/src/main/java/io/dapr/actors/ActorUtils.java new file mode 100644 index 000000000..c5921a0ef --- /dev/null +++ b/sdk-actors/src/main/java/io/dapr/actors/ActorUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors; + +public final class ActorUtils { + + /** + * Finds the actor type name for the given class or interface. + * + * @param actorClass Class or interface for Actor Type. + * @return Name for Actor Type. + */ + public static String findActorTypeName(Class actorClass) { + if (actorClass == null) { + throw new IllegalArgumentException("ActorClass is required."); + } + + Class node = actorClass; + while ((node != null) && (node.getAnnotation(ActorType.class) == null)) { + node = node.getSuperclass(); + } + + if (node == null) { + // No annotation found in parent classes, so we scan interfaces. + for (Class interfaze : actorClass.getInterfaces()) { + if (interfaze.getAnnotation(ActorType.class) != null) { + node = interfaze; + break; + } + } + } + + if (node == null) { + // No ActorType annotation found, so we use the class name. + return actorClass.getSimpleName(); + } + + ActorType actorTypeAnnotation = node.getAnnotation(ActorType.class); + return actorTypeAnnotation != null ? actorTypeAnnotation.name() : actorClass.getSimpleName(); + } +} diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java index ad034443b..ee3319952 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java @@ -6,14 +6,17 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorUtils; import io.dapr.client.DaprHttpBuilder; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; +import java.lang.reflect.Proxy; + /** * Builder to generate an ActorProxy instance. Builder can be reused for multiple instances. */ -public class ActorProxyBuilder { +public class ActorProxyBuilder { /** * Builder for Dapr's raw http client. @@ -25,6 +28,11 @@ public class ActorProxyBuilder { */ private final String actorType; + /** + * Actor's type class. + */ + private final Class clazz; + /** * Dapr's object serializer. */ @@ -35,15 +43,31 @@ public class ActorProxyBuilder { * * {@link DefaultObjectSerializer} is not recommended for production scenarios. * - * @param actorType Actor's type. + * @param actorTypeClass Actor's type class. */ - public ActorProxyBuilder(String actorType) { + public ActorProxyBuilder(Class actorTypeClass) { + this(ActorUtils.findActorTypeName(actorTypeClass), actorTypeClass); + } + + /** + * Instantiates a new builder for a given Actor type, using {@link DefaultObjectSerializer} by default. + * + * {@link DefaultObjectSerializer} is not recommended for production scenarios. + * + * @param actorType Actor's type. + * @param actorTypeClass Actor's type class. + */ + public ActorProxyBuilder(String actorType, Class actorTypeClass) { if ((actorType == null) || actorType.isEmpty()) { throw new IllegalArgumentException("ActorType is required."); } + if (actorTypeClass == null) { + throw new IllegalArgumentException("ActorTypeClass is required."); + } this.actorType = actorType; this.objectSerializer = new DefaultObjectSerializer(); + this.clazz = actorTypeClass; } /** @@ -67,16 +91,26 @@ public class ActorProxyBuilder { * @param actorId Actor's identifier. * @return New instance of ActorProxy. */ - public ActorProxy build(ActorId actorId) { + public T build(ActorId actorId) { if (actorId == null) { throw new IllegalArgumentException("Cannot instantiate an Actor without Id."); } - return new ActorProxyImpl( - this.actorType, - actorId, - this.objectSerializer, - new DaprHttpClient(this.daprHttpBuilder.build())); + ActorProxyImpl proxy = new ActorProxyImpl( + this.actorType, + actorId, + this.objectSerializer, + new DaprHttpClient(this.daprHttpBuilder.build())); + + if (this.clazz.equals(ActorProxy.class)) { + // If users want to use the not strongly typed API, we respect that here. + return (T) proxy; + } + + return (T) Proxy.newProxyInstance( + ActorProxyImpl.class.getClassLoader(), + new Class[]{this.clazz}, + proxy); } } diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java index da66a86b1..81d1b4071 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java @@ -6,21 +6,18 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; -import io.dapr.actors.runtime.ActorObjectSerializer; +import io.dapr.actors.ActorMethod; import io.dapr.serializer.DaprObjectSerializer; import reactor.core.publisher.Mono; import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; /** * Implements a proxy client for an Actor's instance. */ -class ActorProxyImpl implements ActorProxy { - - /** - * Serializer used for internal objects. - */ - private static final ActorObjectSerializer INTERNAL_SERIALIZER = new ActorObjectSerializer(); +class ActorProxyImpl implements ActorProxy, InvocationHandler { /** * Actor's identifier for this Actor instance. @@ -107,6 +104,45 @@ class ActorProxyImpl implements ActorProxy { return this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, this.serialize(data)).then(); } + /** + * Handles an invocation via reflection. + * + * @param proxy Interface or class being invoked. + * @param method Method being invoked. + * @param args Arguments to invoke method. + * @return Response object for the invocation. + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + if (method.getParameterCount() > 1) { + throw new UnsupportedOperationException("Actor methods can only have zero or one arguments."); + } + + if (method.getParameterCount() == 0) { + if (method.getReturnType().equals(Mono.class)) { + ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class); + if (actorMethodAnnotation == null) { + return invokeActorMethod(method.getName()); + } + + return invokeActorMethod(method.getName(), actorMethodAnnotation.returns()); + } + + return invokeActorMethod(method.getName(), method.getReturnType()).block(); + } + + if (method.getReturnType().equals(Mono.class)) { + ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class); + if (actorMethodAnnotation == null) { + return invokeActorMethod(method.getName(), args[0]); + } + + return invokeActorMethod(method.getName(), args[0], actorMethodAnnotation.returns()); + } + + return invokeActorMethod(method.getName(), args[0], method.getReturnType()).block(); + } + /** * Extracts the response object from the Actor's method result. * @@ -138,5 +174,4 @@ class ActorProxyImpl implements ActorProxy { throw new RuntimeException(e); } } - } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java index fac3916d8..6b218e925 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java @@ -5,6 +5,8 @@ package io.dapr.actors.runtime; +import io.dapr.actors.ActorUtils; + import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; @@ -143,9 +145,8 @@ final class ActorTypeInformation { boolean isAbstract = Modifier.isAbstract(actorClass.getModifiers()); boolean isRemindable = ActorTypeUtilities.isRemindableActor(actorClass); - ActorType actorTypeAnnotation = actorClass.getAnnotation(ActorType.class); - String typeName = actorTypeAnnotation != null ? actorTypeAnnotation.name() : actorClass.getSimpleName(); + String typeName = ActorUtils.findActorTypeName(actorClass); return new ActorTypeInformation(typeName, actorClass, Arrays.asList(actorInterfaces), isAbstract, isRemindable); } diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java index 52ca1a0c1..be8df0637 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java @@ -6,7 +6,7 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; -import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.actors.ActorType; import org.junit.Assert; import org.junit.Test; @@ -14,28 +14,28 @@ public class ActorProxyBuilderTest { @Test(expected = IllegalArgumentException.class) public void buildWithNullActorId() { - new ActorProxyBuilder("test") + new ActorProxyBuilder("test", Object.class) .build(null); } @Test(expected = IllegalArgumentException.class) public void buildWithEmptyActorType() { - new ActorProxyBuilder("") + new ActorProxyBuilder("", Object.class) .build(new ActorId("100")); } @Test(expected = IllegalArgumentException.class) public void buildWithNullActorType() { - new ActorProxyBuilder(null) + new ActorProxyBuilder(null, Object.class) .build(new ActorId("100")); } @Test(expected = IllegalArgumentException.class) public void buildWithNullSerializer() { - new ActorProxyBuilder("MyActor") + new ActorProxyBuilder("MyActor", Object.class) .withObjectSerializer(null) .build(new ActorId("100")); @@ -43,12 +43,34 @@ public class ActorProxyBuilderTest { @Test() public void build() { - ActorProxyBuilder builder = new ActorProxyBuilder("test"); + ActorProxyBuilder builder = new ActorProxyBuilder("test", ActorProxy.class); ActorProxy actorProxy = builder.build(new ActorId("100")); Assert.assertNotNull(actorProxy); Assert.assertEquals("test", actorProxy.getActorType()); Assert.assertEquals("100", actorProxy.getActorId().toString()); + } + @Test() + public void buildWithType() { + ActorProxyBuilder builder = new ActorProxyBuilder(MyActor.class); + MyActor actorProxy = builder.build(new ActorId("100")); + + Assert.assertNotNull(actorProxy); + } + + @Test() + public void buildWithTypeDefaultName() { + ActorProxyBuilder builder = new ActorProxyBuilder(MyActorWithDefaultName.class); + MyActorWithDefaultName actorProxy = builder.build(new ActorId("100")); + + Assert.assertNotNull(actorProxy); + } + + @ActorType(name = "MyActor") + public interface MyActor { + } + + public interface MyActorWithDefaultName { } } \ No newline at end of file diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java index fc3fab2b6..b6d15bef7 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java @@ -36,16 +36,16 @@ public class ActorProxyImplTest { public void invokeActorMethodWithoutDataWithReturnType() { final DaprClient daprClient = mock(DaprClient.class); Mono daprResponse = Mono.just( - "{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes()); + "{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes()); when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNull())) - .thenReturn(daprResponse); + .thenReturn(daprResponse); final ActorProxy actorProxy = new ActorProxyImpl( - "myActorType", - new ActorId("100"), - new DefaultObjectSerializer(), - daprClient); + "myActorType", + new ActorId("100"), + new DefaultObjectSerializer(), + daprClient); Mono result = actorProxy.invokeActorMethod("getData", MyData.class); MyData myData = result.block(); @@ -54,6 +54,27 @@ public class ActorProxyImplTest { Assert.assertEquals("valueB", myData.getPropertyB());// propertyB=null } + @Test() + public void invokeActorMethodWithoutDataWithReturnTypeViaReflection() throws NoSuchMethodException { + final DaprClient daprClient = mock(DaprClient.class); + Mono daprResponse = Mono.just( + "{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes()); + + when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNull())) + .thenReturn(daprResponse); + + final ActorProxyImpl actorProxy = new ActorProxyImpl( + "myActorType", + new ActorId("100"), + new DefaultObjectSerializer(), + daprClient); + + MyData myData = (MyData) actorProxy.invoke(actorProxy, Actor.class.getMethod("getData"), null); + Assert.assertNotNull(myData); + Assert.assertEquals("valueA", myData.getPropertyA()); + Assert.assertEquals("valueB", myData.getPropertyB());// propertyB=null + } + @Test() public void invokeActorMethodWithoutDataWithEmptyReturnType() { final DaprClient daprClient = mock(DaprClient.class); @@ -250,6 +271,10 @@ public class ActorProxyImplTest { Assert.assertNull(emptyResponse); } + interface Actor { + MyData getData(); + } + static class MyData { /// Gets or sets the value for PropertyA. diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorCustomSerializerTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorCustomSerializerTest.java index 775235faf..d85ab428f 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorCustomSerializerTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorCustomSerializerTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java index 17e7a101b..59673a83e 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.serializer.DefaultObjectSerializer; import org.junit.Assert; import org.junit.Test; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java index 93f0c1a53..c1a8c293d 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorRuntimeTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorRuntimeTest.java index 0ea8b454b..34b4605a8 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorRuntimeTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorRuntimeTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java index 62856d586..71b2acd84 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; @@ -604,23 +605,23 @@ public class ActorStatefulTest { DaprClientStub daprClient = mock(DaprClientStub.class); when(daprClient.invokeActorMethod( - eq(context.getActorTypeInformation().getName()), - eq(actorId.toString()), - any(), - any())) - .thenAnswer(invocationOnMock -> - this.manager.invokeMethod( - new ActorId(invocationOnMock.getArgument(1, String.class)), - invocationOnMock.getArgument(2, String.class), - invocationOnMock.getArgument(3, byte[].class))); + eq(context.getActorTypeInformation().getName()), + eq(actorId.toString()), + any(), + any())) + .thenAnswer(invocationOnMock -> + this.manager.invokeMethod( + new ActorId(invocationOnMock.getArgument(1, String.class)), + invocationOnMock.getArgument(2, String.class), + invocationOnMock.getArgument(3, byte[].class))); this.manager.activateActor(actorId).block(); return new ActorProxyForTestsImpl( - context.getActorTypeInformation().getName(), - actorId, - new DefaultObjectSerializer(), - daprClient); + context.getActorTypeInformation().getName(), + actorId, + new DefaultObjectSerializer(), + daprClient); } private byte[] createReminderParams(String data) throws IOException { diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java index e2f03328f..484bb8ef4 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java @@ -5,6 +5,7 @@ package io.dapr.actors.runtime; +import io.dapr.actors.ActorType; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.Mono; @@ -16,6 +17,13 @@ import java.time.Duration; */ public class ActorTypeInformationTest { + /** + * Actor interfaced used in this test only. + */ + @ActorType(name = "MyActorWithAnnotation") + private interface MyActorAnnotated { + } + /** * Actor interfaced used in this test only. */ @@ -99,6 +107,27 @@ public class ActorTypeInformationTest { Assert.assertTrue(info.getInterfaces().contains(MyActor.class)); } + /** + * Checks information for an actor renamed via annotation at interface. + */ + @Test + public void renamedWithAnnotationAtInterface() { + class A extends AbstractActor implements MyActorAnnotated { + A() { + super(null, null); + } + } + + ActorTypeInformation info = ActorTypeInformation.create(A.class); + Assert.assertNotNull(info); + Assert.assertEquals("MyActorWithAnnotation", info.getName()); + Assert.assertEquals(A.class, info.getImplementationClass()); + Assert.assertFalse(info.isAbstractClass()); + Assert.assertFalse(info.isRemindable()); + Assert.assertEquals(1, info.getInterfaces().size()); + Assert.assertTrue(info.getInterfaces().contains(MyActorAnnotated.class)); + } + /** * Checks information for an actor is invalid due to an non-actor parent. */ diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java index dad8bcae8..3d59ae8c5 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java index f07ed6df0..a0a418dec 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActivationDeactivationIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActivationDeactivationIT.java index be9d480a4..1b4e2435e 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActivationDeactivationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActivationDeactivationIT.java @@ -9,6 +9,7 @@ import io.dapr.actors.ActorId; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyBuilder; import io.dapr.it.BaseIT; +import io.dapr.it.actors.services.springboot.DemoActor; import io.dapr.it.actors.services.springboot.DemoActorService; import io.dapr.it.services.EmptyService; import io.dapr.serializer.DefaultObjectSerializer; @@ -39,30 +40,29 @@ public class ActivationDeactivationIT extends BaseIT { 60000); final AtomicInteger atomicInteger = new AtomicInteger(1); - String actorType = "DemoActorTest"; logger.debug("Creating proxy builder"); - ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType); + ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(DemoActor.class); logger.debug("Creating actorId"); ActorId actorId1 = new ActorId(Integer.toString(atomicInteger.getAndIncrement())); logger.debug("Building proxy"); - ActorProxy proxy = proxyBuilder.build(actorId1); + DemoActor proxy = proxyBuilder.build(actorId1); callWithRetry(() -> { logger.debug("Invoking Say from Proxy"); - String sayResponse = proxy.invokeActorMethod("say", "message", String.class).block(); + String sayResponse = proxy.say("message"); logger.debug("asserting not null response: [" + sayResponse + "]"); assertNotNull(sayResponse); }, 60000); logger.debug("Retrieving active Actors"); - List activeActors = proxy.invokeActorMethod("retrieveActiveActors", null, List.class).block(); + List activeActors = proxy.retrieveActiveActors(); logger.debug("Active actors: [" + activeActors.toString() + "]"); assertTrue("Expecting actorId:[" + actorId1.toString() + "]", activeActors.contains(actorId1.toString())); ActorId actorId2 = new ActorId(Integer.toString(atomicInteger.getAndIncrement())); - ActorProxy proxy2 = proxyBuilder.build(actorId2); + DemoActor proxy2 = proxyBuilder.build(actorId2); callWithRetry(() -> { - List activeActorsSecondTry = proxy2.invokeActorMethod("retrieveActiveActors", null, List.class).block(); + List activeActorsSecondTry = proxy2.retrieveActiveActors(); logger.debug("Active actors: [" + activeActorsSecondTry.toString() + "]"); assertFalse("NOT Expecting actorId:[" + actorId1.toString() + "]", activeActorsSecondTry.contains(actorId1.toString())); }, 15000); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java index 5477c960d..c2fb89f7e 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java @@ -41,7 +41,7 @@ public class ActorStateIT extends BaseIT { ActorId actorId = new ActorId(Long.toString(System.currentTimeMillis())); String actorType = "StatefulActorTest"; logger.debug("Building proxy ..."); - ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType); + ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class); ActorProxy proxy = proxyBuilder.build(actorId); callWithRetry(() -> { diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java index 9d2843add..45ea788d4 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java @@ -83,7 +83,7 @@ public class ActorTurnBasedConcurrencyIT extends BaseIT { String actorType="MyActorTest"; logger.debug("Creating proxy builder"); - ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType); + ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class); logger.debug("Creating actorId"); ActorId actorId1 = new ActorId(ACTOR_ID); logger.debug("Building proxy"); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java index 260a8d7c4..002ca0f45 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java @@ -6,9 +6,9 @@ package io.dapr.it.actors.app; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.ActorRuntimeContext; -import io.dapr.actors.runtime.ActorType; import io.dapr.actors.runtime.Remindable; import io.dapr.it.actors.MethodEntryTracker; import reactor.core.publisher.Mono; @@ -16,7 +16,11 @@ import reactor.core.publisher.Mono; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; @ActorType(name = "MyActorTest") public class MyActorImpl extends AbstractActor implements MyActor, Remindable { diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActor.java b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActor.java index db959e484..03c8dde0a 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActor.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActor.java @@ -5,8 +5,11 @@ package io.dapr.it.actors.services.springboot; +import io.dapr.actors.ActorType; + import java.util.List; +@ActorType(name = "DemoActorTest") public interface DemoActor { String say(String something); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorImpl.java index ae5c55d4c..e17d765a0 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorImpl.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorImpl.java @@ -8,15 +8,16 @@ package io.dapr.it.actors.services.springboot; import io.dapr.actors.ActorId; import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.ActorRuntimeContext; -import io.dapr.actors.runtime.ActorType; import reactor.core.publisher.Mono; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; -@ActorType(name = "DemoActorTest") public class DemoActorImpl extends AbstractActor implements DemoActor { public static final List ACTIVE_ACTOR = new ArrayList<>(); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorService.java b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorService.java index c57208bd0..7afe0019f 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorService.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/DemoActorService.java @@ -28,8 +28,7 @@ public class DemoActorService { ActorRuntime.getInstance().getConfig().setActorScanInterval(Duration.ofSeconds(2)); ActorRuntime.getInstance().getConfig().setDrainOngoingCallTimeout(Duration.ofSeconds(10)); ActorRuntime.getInstance().getConfig().setDrainBalancedActors(true); - ActorRuntime.getInstance().registerActor( - DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + ActorRuntime.getInstance().registerActor(DemoActorImpl.class); DaprApplication.start(port); } } diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/StatefulActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/StatefulActorImpl.java index 1b28f2b58..195438cfc 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/StatefulActorImpl.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/services/springboot/StatefulActorImpl.java @@ -6,9 +6,9 @@ package io.dapr.it.actors.services.springboot; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorType; import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.ActorRuntimeContext; -import io.dapr.actors.runtime.ActorType; @ActorType(name = "StatefulActorTest") public class StatefulActorImpl extends AbstractActor implements StatefulActor {