Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface DemoActor {

void registerReminder();

@ActorMethod(name = "echo_message")
String say(String something);

void clock(String message);
Expand Down
8 changes: 7 additions & 1 deletion examples/src/main/java/io/dapr/examples/actors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ 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()`. The `DemoActor` interface is used by the Actor runtime and also client. See how `DemoActor` interface can be annotated as Dapr Actor.

```java
import io.dapr.actors.ActorMethod;

/**
* Example of implementation of an Actor.
*/
Expand All @@ -113,6 +115,7 @@ public interface DemoActor {

void registerReminder();

@ActorMethod(name = "echo_message")
String say(String something);

void clock(String message);
Expand All @@ -123,7 +126,10 @@ public interface DemoActor {

```

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).
The `@ActorType` annotation indicates the Dapr Java SDK that this interface is an Actor Type, allowing a name for the type to be defined.

The `@ActorMethod` annotation can be applied to an interface method to specify configuration for that method. In this example, the `say` method, is renamed to `echo_message` - this can be used when invoking an actor method implemented in a different programming language (like C# or Python) and the method name does not match Java's naming conventions.
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:
Expand Down
10 changes: 9 additions & 1 deletion sdk-actors/src/main/java/io/dapr/actors/ActorMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,13 @@
*
* @return Actor's method return type.
*/
Class returns();
Class returns() default Undefined.class;

/**
* Actor's method name. This is optional and will override the method's default name for actor invocation.
*
* @return Actor's method name.
*/
String name() default "";

}
16 changes: 16 additions & 0 deletions sdk-actors/src/main/java/io/dapr/actors/Undefined.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/

package io.dapr.actors;

/**
* Internal class to represent the undefined value for an optional Class attribute.
*/
final class Undefined {

private Undefined() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
*/
class ActorProxyImpl implements ActorProxy, InvocationHandler {

private static final String UNDEFINED_CLASS_NAME = "io.dapr.actors.Undefined";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot refer to the class since it is internal to the package in which it was declared.


/**
* Actor's identifier for this Actor instance.
*/
Expand Down Expand Up @@ -136,29 +138,33 @@ public Object invoke(Object proxy, Method method, Object[] args) {
throw new UnsupportedOperationException("Actor methods can only have zero or one arguments.");
}

ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
String methodName = method.getName();
if ((actorMethodAnnotation != null) && !actorMethodAnnotation.name().isEmpty()) {
methodName = actorMethodAnnotation.name();
}

if (method.getParameterCount() == 0) {
if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeMethod(method.getName());
if ((actorMethodAnnotation == null) || UNDEFINED_CLASS_NAME.equals(actorMethodAnnotation.returns().getName())) {
return invokeMethod(methodName);
}

return invokeMethod(method.getName(), actorMethodAnnotation.returns());
return invokeMethod(methodName, actorMethodAnnotation.returns());
}

return invokeMethod(method.getName(), method.getReturnType()).block();
return invokeMethod(methodName, method.getReturnType()).block();
}

if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeMethod(method.getName(), args[0]);
if ((actorMethodAnnotation == null) || UNDEFINED_CLASS_NAME.equals(actorMethodAnnotation.returns().getName())) {
return invokeMethod(methodName, args[0]);
}

return invokeMethod(method.getName(), args[0], actorMethodAnnotation.returns());
return invokeMethod(methodName, args[0], actorMethodAnnotation.returns());
Comment thread
artursouza marked this conversation as resolved.
}

return invokeMethod(method.getName(), args[0], method.getReturnType()).block();
return invokeMethod(methodName, args[0], method.getReturnType()).block();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package io.dapr.actors.runtime;

import io.dapr.actors.ActorMethod;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -35,7 +37,12 @@ class ActorMethodInfoMap {
if (methodInfo.getParameterCount() <= 1) {
// If Actor class uses overloading, then one will win.
// Document this behavior, so users know how to write their code.
methods.put(methodInfo.getName(), methodInfo);
String methodName = methodInfo.getName();
ActorMethod actorMethodAnnotation = methodInfo.getAnnotation(ActorMethod.class);
if ((actorMethodAnnotation != null) && !actorMethodAnnotation.name().isEmpty()) {
methodName = actorMethodAnnotation.name();
}
methods.put(methodName, methodInfo);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.dapr.actors.runtime;

import io.dapr.actors.ActorId;
import io.dapr.actors.ActorMethod;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
Expand Down Expand Up @@ -43,6 +44,8 @@ public interface MyActor {
Mono<MyData> classInClassOut(MyData input);
Mono<String> registerBadCallbackName();
String registerTimerAutoName();
@ActorMethod(name = "DotNetMethodASync")
Mono<Void> dotNetMethod();
}

@ActorType(name = "MyActor")
Expand Down Expand Up @@ -108,6 +111,11 @@ public Mono<String> registerBadCallbackName() {
public String registerTimerAutoName() {
return super.registerActorTimer("", "anything", "state", Duration.ofSeconds(1), Duration.ofSeconds(1)).block();
}

@Override
public Mono<Void> dotNetMethod() {
return Mono.empty();
}
}

static class MyData {
Expand Down Expand Up @@ -174,6 +182,12 @@ public void stringInVoidOutIntentionallyThrows() {
actorProxy.invokeMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
}

@Test
public void testMethodNameChange() {
MyActor actor = createActorProxy(MyActor.class);
actor.dotNetMethod();
}

@Test
public void classInClassOut() {
ActorProxy actorProxy = createActorProxy();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/

package io.dapr.it.actors;

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.app.MyActor;
import io.dapr.it.actors.app.MyActorService;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.dapr.it.Retry.callWithRetry;
import static org.junit.Assert.assertTrue;

public class ActorMethodNameIT extends BaseIT {

private static Logger logger = LoggerFactory.getLogger(ActorMethodNameIT.class);

@Test
public void actorMethodNameChange() throws Exception {
// The call below will fail if service cannot start successfully.
startDaprApp(
ActorMethodNameIT.class.getSimpleName(),
MyActorService.SUCCESS_MESSAGE,
MyActorService.class,
true,
60000);

logger.debug("Creating proxy builder");
ActorProxyBuilder<MyActor> proxyBuilder = deferClose(new ActorProxyBuilder("MyActorTest", MyActor.class));
logger.debug("Creating actorId");
ActorId actorId1 = new ActorId("1");
logger.debug("Building proxy");
MyActor proxy = proxyBuilder.build(actorId1);

callWithRetry(() -> {
logger.debug("Invoking dotNetMethod from Proxy");
boolean response = proxy.dotNetMethod();
logger.debug("asserting true response: [" + response + "]");
assertTrue(response);
}, 60000);

logger.debug("Creating proxy builder 2");
ActorProxyBuilder<ActorProxy> proxyBuilder2 = deferClose(new ActorProxyBuilder("MyActorTest", ActorProxy.class));
logger.debug("Building proxy 2");
ActorProxy proxy2 = proxyBuilder2.build(actorId1);

callWithRetry(() -> {
logger.debug("Invoking DotNetMethodAsync from Proxy 2");
boolean response = proxy2.invokeMethod("DotNetMethodAsync", boolean.class).block();
logger.debug("asserting true response 2: [" + response + "]");
assertTrue(response);
}, 60000);

}
}
5 changes: 5 additions & 0 deletions sdk-tests/src/test/java/io/dapr/it/actors/app/MyActor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package io.dapr.it.actors.app;

import io.dapr.actors.ActorMethod;

import java.util.ArrayList;
import java.util.List;

Expand All @@ -26,4 +28,7 @@ public interface MyActor {
ArrayList<String> getCallLog();

String getIdentifier();

@ActorMethod(name = "DotNetMethodAsync")
boolean dotNetMethod();
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ public String getIdentifier() {
return System.getenv("DAPR_HTTP_PORT");
}

@Override
public boolean dotNetMethod() {
return true;
}

private void formatAndLog(boolean isEnter, String methodName) {
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

Expand Down