From 87a8588365c0286513d9f9ac61d1c4bb14723fef Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Thu, 19 Dec 2019 18:41:42 -0800 Subject: [PATCH 1/7] ActorManager --- .../io/dapr/actors/runtime/AbstractActor.java | 82 ++++++++- .../io/dapr/actors/runtime/ActorFactory.java | 4 +- .../io/dapr/actors/runtime/ActorManager.java | 158 +++++++++++++++++- .../actors/runtime/ActorMethodContext.java | 4 +- .../actors/runtime/ActorMethodInfoMap.java | 46 ++--- .../io/dapr/actors/runtime/ActorRuntime.java | 18 +- .../actors/runtime/ActorRuntimeContext.java | 43 +++++ .../io/dapr/actors/runtime/ActorService.java | 4 +- .../dapr/actors/runtime/ActorServiceImpl.java | 16 +- .../actors/runtime/ActorStateManager.java | 26 +++ .../actors/runtime/ActorStateSerializer.java | 45 ++++- .../io/dapr/actors/runtime/ActorTimer.java | 105 ++++++++++-- .../dapr/actors/runtime/ActorTimerImpl.java | 136 --------------- .../actors/runtime/ActorTypeInformation.java | 2 +- .../actors/runtime/DefaultActorFactory.java | 25 +-- .../io/dapr/actors/runtime/Remindable.java | 21 ++- .../io/dapr/actors/runtime/ReminderInfo.java | 110 ++++-------- .../runtime/ActorMethodInfoMapTest.java | 4 +- ...TimerImplTest.java => ActorTimerTest.java} | 10 +- .../runtime/ActorTypeInformationTest.java | 12 ++ .../runtime/DefaultActorFactoryTest.java | 4 + 21 files changed, 571 insertions(+), 304 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java delete mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java rename sdk/src/test/java/io/dapr/actors/runtime/{ActorTimerImplTest.java => ActorTimerTest.java} (86%) diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 5f58e6185a..e4422e1a50 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -4,8 +4,88 @@ */ package io.dapr.actors.runtime; +import io.dapr.actors.ActorId; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * TODO - this is the base class Actor implementations (user code) will extend. */ -public abstract class AbstractActor { +public abstract class AbstractActor { + + private final ActorId id; + + private final ActorRuntime actorRuntime; + + private final ActorStateSerializer actorSerializer; + + private final ActorService actorService; + + private final ActorStateManager actorStateManager; + + private final Map> timers; + + protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { + this.id = id; + this.actorRuntime = runtimeContext.getActorRuntime(); + this.actorSerializer = runtimeContext.getActorSerializer(); + this.actorService = runtimeContext.getActorService(); + this.actorStateManager = new ActorStateManager(runtimeContext.getActorTypeInformation().getName(), id); + this.timers = Collections.synchronizedMap(new HashMap<>()); + } + + /** + * Registers a Timer for the actor. A timer name is autogenerated by the runtime to keep track of it. + * + * @param timerName Name of the timer, unique per Actor (auto-generated if null). + * @param methodName Name of the method to be called. + * @param state State object to be passed it to the method when timer triggers. + * @param dueTime The amount of time to delay before the async callback is first invoked. + * Specify negative one (-1) milliseconds to prevent the timer from starting. + * Specify zero (0) to start the timer immediately. + * @param period The time interval between invocations of the async callback. + * Specify negative one (-1) milliseconds to disable periodic signaling. + * @param Type for the state object. + * @return Asynchronous result. + */ + protected Mono RegisterActorTimer(String timerName, String methodName, T state, Duration dueTime, Duration period) throws IOException { + String name = timerName; + if ((timerName == null) || (timerName.isEmpty())) { + name = String.format("%s_Timer_%d", this.id); + } + + ActorTimer actorTimer = new ActorTimer(this, name, methodName, state, dueTime, period); + String serializedState = this.actorSerializer.serialize(actorTimer); + this.timers.put(name, actorTimer); + } + + protected Mono onPreActorMethod(ActorMethodContext actorMethodContext) { + return Mono.empty(); + } + + protected Mono onPostActorMethod(ActorMethodContext actorMethodContext) { + return Mono.empty(); + } + + protected Mono saveState() { + return this.actorStateManager.SaveState(); + } + + ActorTimer getActorTimer(String timerName) + { + return timers.getOrDefault(timerName, null); + } + + Mono onPreActorMethodInternal(ActorMethodContext actorMethodContext) { + return this.onPreActorMethod(actorMethodContext); + } + + Mono onPostActorMethodInternal(ActorMethodContext actorMethodContext) { + return this.onPostActorMethod(actorMethodContext); + } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java index 8a34af0763..18c386d2ac 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java @@ -16,9 +16,9 @@ public interface ActorFactory { /** * Creates an Actor. - * @param actorService Actor Service. + * @param actorRuntimeContext Actor type's context in the runtime. * @param actorId Actor Id. * @return Actor or null it failed. */ - T createActor(ActorService actorService, ActorId actorId); + T createActor(ActorRuntimeContext actorRuntimeContext, ActorId actorId); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java index c42bcb2cc3..d286fd22a3 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java @@ -1,9 +1,161 @@ package io.dapr.actors.runtime; -// stub -public class ActorManager { +import io.dapr.actors.ActorId; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; - public ActorManager(ActorService actorService) { +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +/** + * Manages actors of a specific type. + */ +class ActorManager { + + private final ActorTypeInformation actorType; + + private final ActorService actorService; + + private final Map activeActors; + + private final ActorMethodInfoMap actorMethods; + + private final ActorStateSerializer actorStateSerializer; + + ActorManager(ActorTypeInformation actorType, ActorStateSerializer actorStateSerializer, ActorService actorService) { + this.actorType = actorType; + this.actorStateSerializer = actorStateSerializer; + this.actorService = actorService; + this.activeActors = Collections.synchronizedMap(new HashMap<>()); + this.actorMethods = new ActorMethodInfoMap(actorType.getInterfaces()); + } + + Mono invokeMethod(ActorId actorId, String methodName, String request) { + return invokeMethod(actorId, null, methodName, request); + } + + Mono invokeReminder(ActorId actorId, String reminderName, String request) { + if (!this.actorType.isRemindable()) { + return Mono.empty(); + } + + try { + ReminderInfo reminder = this.actorStateSerializer.deserialize(request, ReminderInfo.class); + + return invoke(actorId, ActorMethodContext.CreateForReminder(reminderName), actor -> + ((Remindable) actor).receiveReminder( + reminderName, + reminder.getData(), + reminder.getDueTime(), + reminder.getPeriod())).then(); + } catch (Exception e) { + return Mono.error(e); + } + } + + Mono invokeTimer(ActorId actorId, String timerName) { + try { + AbstractActor actor = this.activeActors.getOrDefault(actorId, null); + if (actor == null) { + throw new IllegalArgumentException( + String.format("Could not find actor %s of type %s.", actorId.getStringId(), this.actorType.getName())); + } + + ActorTimer actorTimer = actor.getActorTimer(timerName); + if (actorTimer == null) { + throw new IllegalStateException( + String.format("Could not find timer %s for actor %s.", timerName, this.actorType.getName())); + } + + return invokeMethod( + actorId, + ActorMethodContext.CreateForTimer(timerName), + actorTimer.getMethodName(), + actorTimer.getState()) + .then(); + } catch (Exception e) { + return Mono.error(e); + } + } + + Mono activateActor(ActorId actorId) { + AbstractActor actor = this.actorService.createActor(actorId); + + actor. + } + + private Mono invokeMethod(ActorId actorId, ActorMethodContext context, String methodName, Object request) { + ActorMethodContext actorMethodContext = context; + if (actorMethodContext == null) { + actorMethodContext = ActorMethodContext.CreateForActor(methodName); + } + + return this.invoke(actorId, actorMethodContext, actor -> { + try { + Class clazz = this.actorType.getImplementationClass(); + + // Finds the actor method with the given name and 1 or no parameter. + Method method = this.actorMethods.get(methodName); + + Object response = null; + + if (method.getParameterCount() == 0) { + response = method.invoke(actor); + } else { + // Actor methods must have a one or no parameter, which is guaranteed at this point. + Class inputClass = method.getParameterTypes()[0]; + + if ((request != null) && !inputClass.isInstance(request)) { + // If request object is String, we deserialize it. + response = method.invoke(actor, this.actorStateSerializer.deserialize((String) request, inputClass)); + } else { + // If input already of the right type, so we just cast it. + response = method.invoke(actor, inputClass.cast(request)); + } + } + + if (response == null) { + return Mono.empty(); + } + + if (response instanceof Mono) { + return ((Mono) response).map(r -> { + try { + return this.actorStateSerializer.serialize(r); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + // Method was not Mono, so we serialize response. + return Mono.just(this.actorStateSerializer.serialize(response)); + } catch (Exception e) { + return Mono.error(e); + } + }).map(r -> r.toString()); + } + + private Mono invoke(ActorId actorId, ActorMethodContext context, Function> func) { + try { + AbstractActor actor = this.activeActors.getOrDefault(actorId, null); + if (actor == null) { + throw new IllegalArgumentException( + String.format("Could not find actor %s of type %s.", actorId.getStringId(), this.actorType.getName())); + } + + Mono preMethodCall = actor.onPreActorMethodInternal(context); + Mono methodCall = func.apply(actor); + Mono postMethodCall = actor.onPostActorMethodInternal(context); + + // TODO: find a way to make this generic and return Mono instead of Mono. + return Flux.concat(preMethodCall, methodCall, postMethodCall).singleOrEmpty(); + } catch (Exception e) { + return Mono.error(e); + } } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodContext.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodContext.java index cd841d7fe0..6826395efc 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodContext.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodContext.java @@ -7,7 +7,7 @@ /** * Contains information about the method that is invoked by actor runtime. */ -class ActorMethodContext { +public class ActorMethodContext { /** * Method name to be invoked. @@ -20,7 +20,7 @@ class ActorMethodContext { private final ActorCallType callType; /** - * Constructs a new instance of {@link ActorMethodContext} + * Constructs a new instance of {@link ActorMethodContext}, representing a call for an Actor. * * @param methodName Method name to be invoked. * @param callType Call type to be used. diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java index fed5e30555..062d324d2b 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java @@ -1,35 +1,43 @@ package io.dapr.actors.runtime; import java.lang.reflect.Method; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** - * Actor method dispatcher map. Holds method_name -> Method for methods defined in Actor interfaces. + * Actor method dispatcher map. Holds method_name -> Method for methods defined in Actor interfaces. */ class ActorMethodInfoMap { - private final HashMap methods; + /** + * Map for methods based on name. + */ + private final Map methods; - public ActorMethodInfoMap(Iterable> interfaceTypes) - { - this.methods = new HashMap(); + ActorMethodInfoMap(Iterable> interfaceTypes) { + Map methods = new HashMap<>(); - // Find methods which are defined in Actor interface. - for (Class actorInterface : interfaceTypes) - { - for (Method methodInfo : actorInterface.getMethods()) - { - this.methods.put(methodInfo.getName(), methodInfo); - } + // Find methods which are defined in Actor interface. + for (Class actorInterface : interfaceTypes) { + for (Method methodInfo : actorInterface.getMethods()) { + // Only support methods with 1 or 0 argument. + 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); } + } } - public Method LookupActorMethodInfo(String methodName) throws NoSuchMethodException - { - Method methodInfo = this.methods.get(methodName); - if (methodInfo == null) { - throw new NoSuchMethodException("Actor type doesn't contain method " + methodName); - } + this.methods = Collections.unmodifiableMap(methods); + } - return methodInfo; + Method get(String methodName) throws NoSuchMethodException { + Method method = this.methods.get(methodName); + if (method == null) { + throw new NoSuchMethodException(String.format("Could not find method %s.", methodName)); } + + return method; + } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java index 180b8acd1e..c0f5d9babe 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -92,18 +92,16 @@ public void RegisterActor(Class clazz) { * @param Actor class type. * This can be used for dependency injection into actors. */ - public void RegisterActor(Class clazz, ActorFactory actorFactory) { - ActorTypeInformation actorTypeInfo = ActorTypeInformation.create(clazz); + public void RegisterActor(Class clazz, ActorFactory actorFactory) { + ActorTypeInformation actorTypeInfo = ActorTypeInformation.create(clazz); - ActorFactory actualActorFactory = actorFactory != null ? actorFactory : new DefaultActorFactory(actorTypeInfo); - // TODO: Refactor into a Builder class. - DaprStateAsyncProvider stateProvider = new DaprStateAsyncProvider(this.appToDaprAsyncClient, new ActorStateSerializer()); - ActorService actorService = new ActorServiceImpl(actorTypeInfo, stateProvider, actualActorFactory); + ActorFactory actualActorFactory = actorFactory != null ? actorFactory : new DefaultActorFactory(); + // TODO: Refactor into a Builder class. + DaprStateAsyncProvider stateProvider = new DaprStateAsyncProvider(this.appToDaprAsyncClient, new ActorStateSerializer()); + ActorService actorService = new ActorServiceImpl(actorTypeInfo, stateProvider, actualActorFactory); // Create ActorManagers, override existing entry if registered again. - synchronized (this.actorManagers) { - this.actorManagers.put(actorTypeInfo.getName(), new ActorManager(actorService)); - } + this.actorManagers.put(actorTypeInfo.getName(), new ActorManager(actorService)); } /** @@ -112,7 +110,7 @@ public void RegisterActor(Class clazz, ActorFactory * @param actorTypeName Actor type name to activate the actor for. * @param actorId Actor id for the actor to be activated. */ - static void Activate(String actorTypeName, String actorId) { + void Activate(String actorTypeName, String actorId) { // uncomment when ActorManager implemented // return instance.GetActorManager(actorTypeName).ActivateActor(new ActorId(actorId)); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java new file mode 100644 index 0000000000..41af1a76a7 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +public class ActorRuntimeContext { + + private final ActorRuntime actorRuntime; + + private final ActorStateSerializer actorSerializer; + + private final ActorService actorService; + + private final ActorTypeInformation actorTypeInformation; + + ActorRuntimeContext(ActorRuntime actorRuntime, + ActorStateSerializer actorSerializer, + ActorService actorService, + ActorTypeInformation actorTypeInformation) { + this.actorRuntime = actorRuntime; + this.actorSerializer = actorSerializer; + this.actorService = actorService; + this.actorTypeInformation = actorTypeInformation; + } + + ActorRuntime getActorRuntime() { + return actorRuntime; + } + + ActorStateSerializer getActorSerializer() { + return actorSerializer; + } + + ActorService getActorService() { + return actorService; + } + + ActorTypeInformation getActorTypeInformation() { + return actorTypeInformation; + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java index 124e51f04c..2e16ce9cdc 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java @@ -5,12 +5,12 @@ /** * Interface exposed to Actor's implementations (application layer). */ -public interface ActorService { +public interface ActorService { /** * Creates an actor. * @param actorId Identifier for the Actor to be created. * @return New Actor instance. */ - AbstractActor createActor(ActorId actorId); + T createActor(ActorId actorId); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java index 18f67dc10e..33be49cd64 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java @@ -10,12 +10,12 @@ /** * Implementation of the Actor Service that contains a state provider. */ -class ActorServiceImpl implements ActorService { +class ActorServiceImpl implements ActorService { /** * Customizable factory for Actors. */ - private final ActorFactory actorFactory; + private final ActorFactory actorFactory; /** * State provider for Actors. @@ -25,7 +25,7 @@ class ActorServiceImpl implements ActorService { /** * Information on the {@link Actor} type being serviced. */ - private final ActorTypeInformation actorTypeInformation; + private final ActorTypeInformation actorTypeInformation; /** * Instantiates a stateful service for a given {@link Actor} type. @@ -33,7 +33,7 @@ class ActorServiceImpl implements ActorService { * @param stateProvider State provider for Actors. * @param actorFactory Customizable factor for Actors. */ - public ActorServiceImpl(ActorTypeInformation actorTypeInformation, DaprStateAsyncProvider stateProvider, ActorFactory actorFactory) { + public ActorServiceImpl(ActorTypeInformation actorTypeInformation, DaprStateAsyncProvider stateProvider, ActorFactory actorFactory) { this.actorTypeInformation = actorTypeInformation; this.actorFactory = actorFactory; this.stateProvider = stateProvider; @@ -51,17 +51,15 @@ DaprStateAsyncProvider getStateProvider() { * Gets the information on the {@link Actor} Type. * @return Information on the {@link Actor} Type. */ - ActorTypeInformation getActorTypeInformation() { + ActorTypeInformation getActorTypeInformation() { return actorTypeInformation; } /** - * Creates an {@link Actor} for this service. - * @param actorId Identifier for the Actor to be created. - * @return New {@link Actor} instance. + * {@inheritDoc} */ @Override - public AbstractActor createActor(ActorId actorId) { + public T createActor(ActorId actorId) { return this.actorFactory.createActor(this, actorId); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java new file mode 100644 index 0000000000..8e5cc7f79f --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import io.dapr.actors.ActorId; +import reactor.core.publisher.Mono; + +// TODO +class ActorStateManager { + + private final String actorTypeName; + + private final ActorId actorId; + + ActorStateManager(String actorTypeName, ActorId actorId) { + this.actorTypeName = actorTypeName; + this.actorId = actorId; + } + + Mono SaveState() { + return Mono.empty(); + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java index 993d1e7e5b..349d7e682f 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java @@ -4,16 +4,25 @@ */ package io.dapr.actors.runtime; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; /** * Serializes and deserializes an object. */ class ActorStateSerializer { + /** + * Shared Json Factory as per Jackson's documentation, used only for this class. + */ + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + /** * Shared Json serializer/deserializer as per Jackson's documentation. */ @@ -35,7 +44,12 @@ String serialize(T state) throws IOException { return state.toString(); } - if (isPrimitive(state.getClass())) { + if (state.getClass() == ActorTimer.class) { + // Special serializer for this internal classes. + return serialize((ActorTimer) state); + } + + if (isPrimitiveOrEquivalent(state.getClass())) { return state.toString(); } @@ -57,15 +71,19 @@ T deserialize(String value, Class clazz) throws IOException { return (T) value; } - if (isPrimitive(clazz)) { + if (isPrimitiveOrEquivalent(clazz)) { return parse(value, clazz); } + if (value == null) { + return (T) null; + } + // Not string, not primitive, so it is a complex type: we use JSON for that. return OBJECT_MAPPER.readValue(value, clazz); } - private static boolean isPrimitive(Class clazz) { + private static boolean isPrimitiveOrEquivalent(Class clazz) { if (clazz == null) { return false; } @@ -84,6 +102,14 @@ private static boolean isPrimitive(Class clazz) { private static T parse(String value, Class clazz) { if (value == null) { + if (boolean.class == clazz) return (T) Boolean.FALSE; + if (byte.class == clazz) return (T) Byte.valueOf((byte) 0); + if (short.class == clazz) return (T) Short.valueOf((short) 0); + if (int.class == clazz) return (T) Integer.valueOf(0); + if (long.class == clazz) return (T) Long.valueOf(0L); + if (float.class == clazz) return (T) Float.valueOf(0); + if (double.class == clazz) return (T) Double.valueOf(0); + return null; } @@ -97,4 +123,17 @@ private static T parse(String value, Class clazz) { return null; } + + private static String serialize(ActorTimer timer) throws IOException { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(timer.getDueTime())); + generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(timer.getPeriod())); + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java index c1c5f36b17..4310a0dac1 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java @@ -1,41 +1,114 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + package io.dapr.actors.runtime; import java.time.Duration; -import java.util.function.Function; /** - * Represents the timer set on an Actor. + * Represents the timer set on an Actor, to be called once after due time and then every period. */ -public interface ActorTimer { +class ActorTimer { /** - * Gets the time when timer is first due. - * @return Time as Duration when timer is first due. + * Actor that owns this timer. */ - Duration getDueTime(); + private final AbstractActor owner; /** - * Gets the periodic time when timer will be invoked. - * @return Periodic time as Duration when timer will be invoked. + * Name of this timer. + */ + private String name; + + /** + * Name of the method to be called for this timer. + */ + private String methodName; + + /** + * State to be sent in the timer. + */ + private T state; + + /** + * Due time for the timer's first trigger. + */ + private Duration dueTime; + + /** + * Period at which the timer will be triggered. */ - Duration getPeriod(); + private Duration period; + + /** + * Instantiates a new Actor Timer. + * + * @param owner The Actor that owns this timer. The timer callback will be fired for this Actor. + * @param timerName The name of the timer. + * @param methodName The name of the method to be called for this timer. + * @param state information to be used by the callback method + * @param dueTime the time when timer is first due. + * @param period the periodic time when timer will be invoked. + */ + ActorTimer(AbstractActor owner, + String timerName, + String methodName, + T state, + Duration dueTime, + Duration period) { + this.owner = owner; + this.name = timerName; + this.methodName = methodName; + this.state = state; + this.dueTime = dueTime; + this.period = period; + } /** * Gets the name of the Timer. The name is unique per actor. + * * @return The name of the timer. */ - String getName(); + public String getName() { + return this.name; + } + + /** + * Gets the name of the method for this Timer. + * + * @return The name of the method for this timer. + */ + public String getMethodName() { + return this.methodName; + } /** + * Gets the time when timer is first due. * - * @return Gets a delegate that specifies a method to be called when the timer fires. - * It has one parameter: the state object passed to RegisterTimer. + * @return Time as Duration when timer is first due. */ - Function getAsyncCallback(); + public Duration getDueTime() { + return this.dueTime; + } /** + * Gets the periodic time when timer will be invoked. * - * @return Gets state containing information to be used by the callback method, or null. + * @return Periodic time as Duration when timer will be invoked. */ - Object getState(); -} + public Duration getPeriod() { + return this.period; + } + + /** + * Gets state containing information to be used by the callback method, or null. + * + * @return State containing information to be used by the callback method, or null. + */ + public T getState() { + return this.state; + } + +} \ No newline at end of file diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java deleted file mode 100644 index ee53c2951b..0000000000 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.dapr.actors.runtime; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.time.Duration; -import java.util.function.Function; - -/** - * Represents the timer set on an Actor. - */ -class ActorTimerImpl implements ActorTimer { - - /** - * Shared Json Factory as per Jackson's documentation, used only for this class. - */ - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - /** - * Actor that owns this timer. - */ - private final AbstractActor owner; - - /** - * Name of this timer. - */ - private String name; - - /** - * Async callbacks for the timer. - */ - private Function asyncCallback; - - /** - * State to be sent in the timer. - */ - private Object state; - - /** - * Due time for the timer's first trigger. - */ - private Duration dueTime; - - /** - * Period at which the timer will be triggered. - */ - private Duration period; - - /** - * @param owner The Actor that owns this timer. The timer callback will be fired for this Actor. - * @param timerName The name of the timer. - * @param asyncCallback The callback to invoke when the timer fires. - * @param state information to be used by the callback method - * @param dueTime the time when timer is first due. - * @param period the periodic time when timer will be invoked. - */ - public ActorTimerImpl(AbstractActor owner, - String timerName, - Function asyncCallback, - Object state, - Duration dueTime, - Duration period) { - this.owner = owner; - this.name = timerName; - this.asyncCallback = asyncCallback; - this.state = state; - this.dueTime = dueTime; - this.period = period; - } - - /** - * Gets the name of the Timer. The name is unique per actor. - * - * @return The name of the timer. - */ - public String getName() { - return this.name; - } - - /** - * Gets the time when timer is first due. - * - * @return Time as Duration when timer is first due. - */ - public Duration getDueTime() { - return this.dueTime; - } - - /** - * @return Gets a delegate that specifies a method to be called when the timer fires. - * It has one parameter: the state object passed to RegisterTimer. - */ - public Function getAsyncCallback() { - return this.asyncCallback; - } - - /** - * Gets the periodic time when timer will be invoked. - * - * @return Periodic time as Duration when timer will be invoked. - */ - public Duration getPeriod() { - return this.period; - } - - /** - * @return Gets state containing information to be used by the callback method, or null. - */ - public Object getState() { - return this.state; - } - - /** - * Generates JSON representation of this timer. - * - * @return JSON. - */ - String serialize() throws IOException { - try (Writer writer = new StringWriter()) { - JsonGenerator generator = JSON_FACTORY.createGenerator(writer); - generator.writeStartObject(); - generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(this.dueTime)); - generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(this.period)); - generator.writeEndObject(); - generator.close(); - writer.flush(); - return writer.toString(); - } - } -} \ No newline at end of file diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java index 55f4b7a6aa..e398082503 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java @@ -84,7 +84,7 @@ public Class getImplementationClass() { * * @return Collection of actor interfaces. */ - public Collection getInterfaces() { + public Collection> getInterfaces() { return Collections.unmodifiableCollection(this.interfaces); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java index 94ac51f206..0b9aac2e15 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java @@ -15,34 +15,21 @@ */ class DefaultActorFactory implements ActorFactory { - /** - * Information on the {@link Actor} type being serviced. - */ - private final ActorTypeInformation actorTypeInformation; - - /** - * Instantiates the default factory for Actors of a given type. - * @param actorTypeInformation Information of the actor type for this instance. - */ - DefaultActorFactory(ActorTypeInformation actorTypeInformation) { - this.actorTypeInformation = actorTypeInformation; - } - /** * {@inheritDoc} */ @Override - public T createActor(ActorService actorService, ActorId actorId) { + public T createActor(ActorRuntimeContext actorRuntimeContext, ActorId actorId) { try { - if (this.actorTypeInformation == null) { + if (actorRuntimeContext == null) { return null; } - Constructor constructor = this - .actorTypeInformation + Constructor constructor = actorRuntimeContext + .getActorTypeInformation() .getImplementationClass() - .getConstructor(ActorService.class, ActorId.class); - return constructor.newInstance(actorService, actorId); + .getConstructor(ActorRuntimeContext.class, ActorId.class); + return constructor.newInstance(actorRuntimeContext, actorId); } catch (ReflectiveOperationException e) { e.printStackTrace(); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java index bbdada8175..4d56077b8a 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java @@ -4,8 +4,27 @@ */ package io.dapr.actors.runtime; +import reactor.core.publisher.Mono; + +import java.time.Duration; + /** - * TODO + * Interface that actors must implement to consume reminders registered using RegisterReminderAsync. */ public interface Remindable { + + /** + * The reminder call back invoked when an actor reminder is triggered. + * + * The state of this actor is saved by the actor runtime upon completion of the task returned by this method. + * If an error occurs while saving the state, then all state cached by this actor's {@link ActorStateManager} will + * be discarded and reloaded from previously saved state when the next actor method or reminder invocation occurs. + * + * @param reminderName The name of reminder provided during registration. + * @param state The user state provided during registration. + * @param dueTime The invocation due time provided during registration. + * @param period The invocation period provided during registration. + * @return A task that represents the asynchronous operation performed by this callback. + */ + Mono receiveReminder(String reminderName, byte[] state, Duration dueTime, Duration period); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java b/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java index 4fd1cb685c..78790c43e2 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java @@ -5,97 +5,61 @@ package io.dapr.actors.runtime; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.type.MapType; -import java.io.IOException; -import java.time.*; -import java.util.Base64; -import java.util.Map; - -class ReminderInfo -{ - private final Duration minTimePeriod = Duration.ofMillis(-1); - - public Duration dueTime; - public Duration period; - public byte[] data; - - public ReminderInfo() { +import java.time.Duration; + +class ReminderInfo { + + private static final Duration MIN_TIME_PERIOD = Duration.ofMillis(-1); + + private Duration dueTime; + + private Duration period; + + private byte[] data; + + public Duration getDueTime() { + return dueTime; } - public ReminderInfo(byte[] state, Duration dueTime, Duration period) { - this.ValidateDueTime("DueTime", dueTime); - this.ValidatePeriod("Period", period); - this.data = state; + public void setDueTime(Duration dueTime) { this.dueTime = dueTime; - this.period = period; } - Duration getDueTime() { - return this.dueTime; + public Duration getPeriod() { + return period; } - Duration getPeriod() { - return this.period; + public void setPeriod(Duration period) { + this.period = period; } - byte[] getData() { - return this.data; + public byte[] getData() { + return data; } - String serialize() throws IOException { - try { - ObjectMapper om = new ObjectMapper(); - ObjectNode objectNode = om.createObjectNode(); - objectNode.put("dueTime", ConverterUtils.ConvertDurationToDaprFormat(this.dueTime)); - objectNode.put("period", ConverterUtils.ConvertDurationToDaprFormat(this.period)); - if (this.data != null) { - objectNode.put("data", Base64.getEncoder().encodeToString(this.data)); - } - - return om.writeValueAsString(objectNode); - } catch (IOException e) { - throw e; - } + public void setData(byte[] data) { + this.data = data; } - static ReminderInfo deserialize(byte[] stream) throws IOException { - try { - ObjectMapper om = new ObjectMapper(); - MapType type = om.getTypeFactory().constructMapType(Map.class, String.class, Object.class); - Map data = om.readValue(stream, type); - - String d = (String)data.getOrDefault("dueTime", ""); - Duration dueTime = ConverterUtils.ConvertDurationFromDaprFormat(d); - - String p = (String)data.getOrDefault("period", ""); - Duration period = ConverterUtils.ConvertDurationFromDaprFormat(p); - - String s = (String)data.getOrDefault("data", null); - byte[] state = (s == null) ? null : Base64.getDecoder().decode(s); - - return new ReminderInfo(state, dueTime, period); - } catch (IOException e) { - throw e; - } + public ReminderInfo(byte[] state, Duration dueTime, Duration period) { + ValidateDueTime("DueTime", dueTime); + ValidatePeriod("Period", period); + this.data = state; + this.dueTime = dueTime; + this.period = period; } - private void ValidateDueTime(String argName, Duration value) - { - if (value.compareTo(Duration.ZERO) < 0 ) - { - String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); - throw new IllegalArgumentException(message); + private static void ValidateDueTime(String argName, Duration value) { + if (value.compareTo(Duration.ZERO) < 0) { + String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + throw new IllegalArgumentException(message); } } - private void ValidatePeriod(String argName, Duration value) throws IllegalArgumentException - { - if (value.compareTo(this.minTimePeriod) < 0) - { - String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); - throw new IllegalArgumentException(message); + private static void ValidatePeriod(String argName, Duration value) throws IllegalArgumentException { + if (value.compareTo(MIN_TIME_PERIOD) < 0) { + String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + throw new IllegalArgumentException(message); } } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java index 2020e54552..03a33f71a3 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java @@ -24,7 +24,7 @@ public void normalUsage() { ActorMethodInfoMap m = new ActorMethodInfoMap(interfaceTypes); try { - Method m1 = m.LookupActorMethodInfo("getData"); + Method m1 = m.get("getData"); Assert.assertEquals("getData", m1.getName()); Class c = m1.getReturnType(); Assert.assertEquals(c.getClass(), String.class.getClass()); @@ -41,7 +41,7 @@ public void lookUpNonExistingMethod() throws NoSuchMethodException { interfaceTypes.add(TestActor.class); ActorMethodInfoMap m = new ActorMethodInfoMap(interfaceTypes); - m.LookupActorMethodInfo("thisMethodDoesNotExist"); + m.get("thisMethodDoesNotExist"); } /** diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java similarity index 86% rename from sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java rename to sdk/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java index 5bf6a48f5b..f804e4e2bd 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java @@ -7,7 +7,7 @@ import java.io.IOException; import java.time.Duration; -public class ActorTimerImplTest { +public class ActorTimerTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -21,14 +21,14 @@ public void serialize() throws IOException { .plusHours(1) .plusSeconds(3); - ActorTimerImpl timer = new ActorTimerImpl( + ActorTimer timer = new ActorTimer( null, "testTimer", null, null, dueTime, period); - String s = timer.serialize(); + String s = new ActorStateSerializer().serialize(timer); String expected = "{\"period\":\"1h0m3s0ms\",\"dueTime\":\"0h7m17s0ms\"}"; // Deep comparison via JsonNode.equals method. @@ -46,14 +46,14 @@ public void serializeWithOneTimePeriod() throws IOException { .minusHours(1) .minusMinutes(3); - ActorTimerImpl timer = new ActorTimerImpl( + ActorTimer timer = new ActorTimer( null, "testTimer", null, null, dueTime, period); - String s = timer.serialize(); + String s = new ActorStateSerializer().serialize(timer); // A negative period will be serialized to an empty string which is interpreted by Dapr to mean fire once only. String expected = "{\"period\":\"\",\"dueTime\":\"0h7m17s0ms\"}"; diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java index d0a825aadd..6ee2d46d8c 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java @@ -26,6 +26,9 @@ private interface MyActor extends Actor { public void notRemindable() { class A extends AbstractActor implements MyActor { + A() { + super(timers); + } } ActorTypeInformation info = ActorTypeInformation.create(A.class); @@ -45,6 +48,9 @@ class A extends AbstractActor implements MyActor { public void remindable() { class A extends AbstractActor implements MyActor, Remindable { + A() { + super(timers); + } } ActorTypeInformation info = ActorTypeInformation.create(A.class); @@ -65,6 +71,9 @@ class A extends AbstractActor implements MyActor, Remindable { public void renamedWithAnnotation() { @ActorType(Name = "B") class A extends AbstractActor implements MyActor { + A() { + super(timers); + } } ActorTypeInformation info = ActorTypeInformation.create(A.class); @@ -83,6 +92,9 @@ class A extends AbstractActor implements MyActor { @Test public void nonActorParentClass() { abstract class MyAbstractClass extends AbstractActor implements MyActor { + MyAbstractClass() { + super(timers); + } } class A extends MyAbstractClass { diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java index 642d076fdc..f760cd4c82 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java @@ -26,6 +26,7 @@ static class MyActor extends AbstractActor implements Actor { ActorId actorId; public MyActor(ActorService actorService, ActorId actorId) { + super(timers); this.actorService = actorService; this.actorId = actorId; } @@ -35,6 +36,9 @@ public MyActor(ActorService actorService, ActorId actorId) { * A non-compliant implementation of Actor to be used in the tests below. */ static class InvalidActor extends AbstractActor { + InvalidActor() { + super(timers); + } } /** From 694ce26653a53b6190689632c90cf0dba4b30bcd Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 23 Dec 2019 01:25:14 -0800 Subject: [PATCH 2/7] More work done. --- .../main/java/io/dapr/actors/ActorTrace.java | 42 ++- .../io/dapr/actors/runtime/AbstractActor.java | 102 +++++-- .../io/dapr/actors/runtime/ActorManager.java | 100 ++++--- .../actors/runtime/ActorMethodInfoMap.java | 3 +- .../actors/runtime/ActorReminderInfo.java | 55 ++++ .../io/dapr/actors/runtime/ActorRuntime.java | 118 +++++--- .../actors/runtime/ActorRuntimeContext.java | 31 ++- .../io/dapr/actors/runtime/ActorService.java | 16 -- .../dapr/actors/runtime/ActorServiceImpl.java | 65 ----- .../actors/runtime/ActorStateManager.java | 6 +- .../actors/runtime/ActorStateSerializer.java | 260 ++++++++++-------- .../io/dapr/actors/runtime/ActorTimer.java | 3 +- .../actors/runtime/ActorTypeInformation.java | 4 +- .../actors/runtime/AppToDaprAsyncClient.java | 2 +- .../runtime/AppToDaprClientBuilder.java | 2 +- .../runtime/AppToDaprHttpAsyncClient.java | 5 +- .../dapr/actors/runtime/ConverterUtils.java | 2 +- .../actors/runtime/DefaultActorFactory.java | 9 +- .../io/dapr/actors/runtime/Remindable.java | 10 +- .../io/dapr/actors/runtime/ReminderInfo.java | 65 ----- .../runtime/ActorMethodInfoMapTest.java | 4 +- .../actors/runtime/ActorReminderInfoTest.java | 62 +++++ .../runtime/ActorTypeInformationTest.java | 21 +- .../runtime/DefaultActorFactoryTest.java | 31 ++- .../dapr/actors/runtime/ReminderInfoTest.java | 60 ---- 25 files changed, 618 insertions(+), 460 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java delete mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorService.java delete mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java delete mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java create mode 100644 sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java delete mode 100644 sdk/src/test/java/io/dapr/actors/runtime/ReminderInfoTest.java diff --git a/sdk/src/main/java/io/dapr/actors/ActorTrace.java b/sdk/src/main/java/io/dapr/actors/ActorTrace.java index b3c61bbdeb..6ab8699932 100644 --- a/sdk/src/main/java/io/dapr/actors/ActorTrace.java +++ b/sdk/src/main/java/io/dapr/actors/ActorTrace.java @@ -2,22 +2,44 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ + package io.dapr.actors; -/** - * Stub - */ -public class ActorTrace { +import java.util.logging.Level; +import java.util.logging.Logger; + +// TODO: Implement distributed tracing. +// TODO: Make this generic to thw SDK and not only for Actors. +public final class ActorTrace { - public static void WriteInfo(String text) { - System.out.println(text); + private static final Logger LOGGER = Logger.getLogger(ActorTrace.class.getName()); + + public void writeInfo(String type, String id, String msgFormat, Object... params) { + this.write(Level.INFO, type, id, msgFormat, params); } - public static void WriteWarning(String text) { - System.out.println("Warning: " + text); + public void writeWarning(String type, String id, String msgFormat, Object... params) { + this.write(Level.WARNING, type, id, msgFormat, params); } - public static void WriteError(String text) { - System.err.println(text); + public void writeError(String type, String id, String msgFormat, Object... params) { + this.write(Level.SEVERE, type, id, msgFormat, params); + } + + private void write(Level level, String type, String id, String msgFormat, Object... params) { + String formatString = String.format("%s:%s %s", emptyIfNul(type), emptyIfNul(id), emptyIfNul(msgFormat)); + if ((params == null) || (params.length == 0)) { + LOGGER.log(level, formatString); + } else { + LOGGER.log(level, String.format(formatString, params)); + } + } + + private static String emptyIfNul(String s) { + if (s == null) { + return ""; + } + + return s; } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java index e4422e1a50..5767132042 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -5,6 +5,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.actors.ActorTrace; import reactor.core.publisher.Mono; import java.io.IOException; @@ -16,29 +17,41 @@ /** * TODO - this is the base class Actor implementations (user code) will extend. */ -public abstract class AbstractActor { +public abstract class AbstractActor { - private final ActorId id; + private static final String TRACE_TYPE = "Actor"; - private final ActorRuntime actorRuntime; + private final ActorRuntimeContext actorRuntimeContext; - private final ActorStateSerializer actorSerializer; + private final ActorId id; - private final ActorService actorService; + private final ActorStateManager actorStateManager; - private final ActorStateManager actorStateManager; + private final ActorTrace actorTrace; private final Map> timers; - protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { + protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { + this.actorRuntimeContext = runtimeContext; this.id = id; - this.actorRuntime = runtimeContext.getActorRuntime(); - this.actorSerializer = runtimeContext.getActorSerializer(); - this.actorService = runtimeContext.getActorService(); this.actorStateManager = new ActorStateManager(runtimeContext.getActorTypeInformation().getName(), id); + this.actorTrace = runtimeContext.getActorTrace(); this.timers = Collections.synchronizedMap(new HashMap<>()); } + protected Mono registerReminder( + String reminderName, + S data, + Duration dueTime, + Duration period) throws IOException { + String serialized = this.actorRuntimeContext.getActorSerializer().serialize(data); + return this.actorRuntimeContext.getDaprClient().registerReminder( + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.getStringId(), + reminderName, + serialized); + } + /** * Registers a Timer for the actor. A timer name is autogenerated by the runtime to keep track of it. * @@ -50,20 +63,43 @@ protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { * Specify zero (0) to start the timer immediately. * @param period The time interval between invocations of the async callback. * Specify negative one (-1) milliseconds to disable periodic signaling. - * @param Type for the state object. + * @param Type for the state object. * @return Asynchronous result. + * @throws IOException in case cannot parse or register with Dapr. */ - protected Mono RegisterActorTimer(String timerName, String methodName, T state, Duration dueTime, Duration period) throws IOException { + protected Mono registerActorTimer( + String timerName, + String methodName, + S state, + Duration dueTime, + Duration period) throws IOException { String name = timerName; if ((timerName == null) || (timerName.isEmpty())) { - name = String.format("%s_Timer_%d", this.id); + name = String.format("%s_Timer_%d", this.id.getStringId(), this.timers.size() + 1); } - ActorTimer actorTimer = new ActorTimer(this, name, methodName, state, dueTime, period); - String serializedState = this.actorSerializer.serialize(actorTimer); + ActorTimer actorTimer = new ActorTimer(this, name, methodName, state, dueTime, period); + String serializedTimer = this.actorRuntimeContext.getActorSerializer().serialize(actorTimer); this.timers.put(name, actorTimer); + return this.actorRuntimeContext.getDaprClient().registerTimer( + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.getStringId(), + name, + serializedTimer); + } + + protected Mono unregister(ActorTimer actorTimer) { + return this.actorRuntimeContext.getDaprClient().unregisterTimer( + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.getStringId(), + actorTimer.getName()) + .then(this.onUnregisteredTimer(actorTimer)); } + protected Mono onActivate() { return Mono.empty(); } + + protected Mono onDeactivate() { return Mono.empty(); } + protected Mono onPreActorMethod(ActorMethodContext actorMethodContext) { return Mono.empty(); } @@ -73,19 +109,51 @@ protected Mono onPostActorMethod(ActorMethodContext actorMethodContext) { } protected Mono saveState() { - return this.actorStateManager.SaveState(); + return this.actorStateManager.saveState(); } + Mono resetState() { return this.actorStateManager.clearCache(); } + ActorTimer getActorTimer(String timerName) { return timers.getOrDefault(timerName, null); } + Mono onActivateInternal() { + this.actorTrace.writeInfo(TRACE_TYPE, this.id.getStringId(), "Activating ..."); + + return this.resetState() + .then(this.onActivate()) + .then(this.doWriteInfo(TRACE_TYPE, this.id.getStringId(), "Activated")) + .then(this.saveState()); + } + + Mono onDeactivateInternal() { + this.actorTrace.writeInfo(TRACE_TYPE, this.id.getStringId(), "Deactivating ..."); + + return this.resetState() + .then(this.onDeactivate()) + .then(this.doWriteInfo(TRACE_TYPE, this.id.getStringId(), "Deactivated")) + .then(this.saveState()); + } + Mono onPreActorMethodInternal(ActorMethodContext actorMethodContext) { return this.onPreActorMethod(actorMethodContext); } Mono onPostActorMethodInternal(ActorMethodContext actorMethodContext) { - return this.onPostActorMethod(actorMethodContext); + return this.onPostActorMethod(actorMethodContext) + .then(this.saveState()); } + + Mono onUnregisteredTimer(ActorTimer timer) { + this.timers.remove(timer.getName()); + return Mono.empty(); + } + + private Mono doWriteInfo(String type, String id, String message) { + this.actorTrace.writeInfo(type, id, message); + return Mono.empty(); + } + } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java index d286fd22a3..9e1e1f68b2 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java @@ -13,25 +13,35 @@ /** * Manages actors of a specific type. + * */ class ActorManager { - private final ActorTypeInformation actorType; + private final ActorRuntimeContext runtimeContext; - private final ActorService actorService; + private final ActorMethodInfoMap actorMethods; private final Map activeActors; - private final ActorMethodInfoMap actorMethods; + ActorManager(ActorRuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + this.actorMethods = new ActorMethodInfoMap(runtimeContext.getActorTypeInformation().getInterfaces()); + this.activeActors = Collections.synchronizedMap(new HashMap<>()); + } - private final ActorStateSerializer actorStateSerializer; + Mono activateActor(ActorId actorId) { + T actor = this.runtimeContext.getActorFactory().createActor(runtimeContext, actorId); - ActorManager(ActorTypeInformation actorType, ActorStateSerializer actorStateSerializer, ActorService actorService) { - this.actorType = actorType; - this.actorStateSerializer = actorStateSerializer; - this.actorService = actorService; - this.activeActors = Collections.synchronizedMap(new HashMap<>()); - this.actorMethods = new ActorMethodInfoMap(actorType.getInterfaces()); + return actor.onActivateInternal().then(this.onActivatedActor(actorId, actor)); + } + + Mono deactivateActor(ActorId actorId) { + T actor = this.activeActors.remove(actorId); + if (actor != null) { + return actor.onDeactivateInternal(); + } + + return Mono.empty(); } Mono invokeMethod(ActorId actorId, String methodName, String request) { @@ -39,19 +49,18 @@ Mono invokeMethod(ActorId actorId, String methodName, String request) { } Mono invokeReminder(ActorId actorId, String reminderName, String request) { - if (!this.actorType.isRemindable()) { + if (!this.runtimeContext.getActorTypeInformation().isRemindable()) { return Mono.empty(); } try { - ReminderInfo reminder = this.actorStateSerializer.deserialize(request, ReminderInfo.class); + ActorReminderInfo reminder = this.runtimeContext.getActorSerializer().deserialize(request, ActorReminderInfo.class); - return invoke(actorId, ActorMethodContext.CreateForReminder(reminderName), actor -> - ((Remindable) actor).receiveReminder( - reminderName, - reminder.getData(), - reminder.getDueTime(), - reminder.getPeriod())).then(); + return invoke( + actorId, + ActorMethodContext.CreateForReminder(reminderName), + actor -> doReminderInvokation((Remindable)actor, reminderName, reminder)) + .then(); } catch (Exception e) { return Mono.error(e); } @@ -62,30 +71,51 @@ Mono invokeTimer(ActorId actorId, String timerName) { AbstractActor actor = this.activeActors.getOrDefault(actorId, null); if (actor == null) { throw new IllegalArgumentException( - String.format("Could not find actor %s of type %s.", actorId.getStringId(), this.actorType.getName())); + String.format("Could not find actor %s of type %s.", + actorId.getStringId(), + this.runtimeContext.getActorTypeInformation().getName())); } ActorTimer actorTimer = actor.getActorTimer(timerName); if (actorTimer == null) { throw new IllegalStateException( - String.format("Could not find timer %s for actor %s.", timerName, this.actorType.getName())); + String.format("Could not find timer %s for actor %s.", + timerName, + this.runtimeContext.getActorTypeInformation().getName())); } return invokeMethod( - actorId, - ActorMethodContext.CreateForTimer(timerName), - actorTimer.getMethodName(), - actorTimer.getState()) - .then(); + actorId, + ActorMethodContext.CreateForTimer(timerName), + actorTimer.getMethodName(), + actorTimer.getState()) + .then(); } catch (Exception e) { return Mono.error(e); } } - Mono activateActor(ActorId actorId) { - AbstractActor actor = this.actorService.createActor(actorId); + private Mono onActivatedActor(ActorId actorId, T actor) { + this.activeActors.put(actorId, actor); + return Mono.empty(); + } - actor. + private Mono doReminderInvokation( + Remindable actor, + String reminderName, + ActorReminderInfo reminderParams) { + try { + Object data = this.runtimeContext.getActorSerializer().deserialize( + reminderParams.getData(), + actor.getReminderStateType()); + return actor.receiveReminder( + reminderName, + data, + reminderParams.getDueTime(), + reminderParams.getPeriod()); + } catch (IOException e) { + return Mono.error(e); + } } private Mono invokeMethod(ActorId actorId, ActorMethodContext context, String methodName, Object request) { @@ -96,7 +126,7 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S return this.invoke(actorId, actorMethodContext, actor -> { try { - Class clazz = this.actorType.getImplementationClass(); + Class clazz = this.runtimeContext.getActorTypeInformation().getImplementationClass(); // Finds the actor method with the given name and 1 or no parameter. Method method = this.actorMethods.get(methodName); @@ -111,7 +141,9 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S if ((request != null) && !inputClass.isInstance(request)) { // If request object is String, we deserialize it. - response = method.invoke(actor, this.actorStateSerializer.deserialize((String) request, inputClass)); + response = method.invoke( + actor, + this.runtimeContext.getActorSerializer().deserialize((String) request, inputClass)); } else { // If input already of the right type, so we just cast it. response = method.invoke(actor, inputClass.cast(request)); @@ -125,7 +157,7 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S if (response instanceof Mono) { return ((Mono) response).map(r -> { try { - return this.actorStateSerializer.serialize(r); + return this.runtimeContext.getActorSerializer().serialize(r); } catch (IOException e) { throw new RuntimeException(e); } @@ -133,7 +165,7 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S } // Method was not Mono, so we serialize response. - return Mono.just(this.actorStateSerializer.serialize(response)); + return Mono.just(this.runtimeContext.getActorSerializer().serialize(response)); } catch (Exception e) { return Mono.error(e); } @@ -145,7 +177,9 @@ private Mono invoke(ActorId actorId, ActorMethodContext context, Fun AbstractActor actor = this.activeActors.getOrDefault(actorId, null); if (actor == null) { throw new IllegalArgumentException( - String.format("Could not find actor %s of type %s.", actorId.getStringId(), this.actorType.getName())); + String.format("Could not find actor %s of type %s.", + actorId.getStringId(), + this.runtimeContext.getActorTypeInformation().getName())); } Mono preMethodCall = actor.onPreActorMethodInternal(context); diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java index 062d324d2b..cc8b96404b 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java @@ -1,6 +1,7 @@ package io.dapr.actors.runtime; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -14,7 +15,7 @@ class ActorMethodInfoMap { */ private final Map methods; - ActorMethodInfoMap(Iterable> interfaceTypes) { + ActorMethodInfoMap(Collection> interfaceTypes) { Map methods = new HashMap<>(); // Find methods which are defined in Actor interface. diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java new file mode 100644 index 0000000000..72ff97265b --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java @@ -0,0 +1,55 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package io.dapr.actors.runtime; + +import java.time.Duration; + +final class ActorReminderInfo { + + private static final Duration MIN_TIME_PERIOD = Duration.ofMillis(-1); + + private final String data; + + private final Duration dueTime; + + private final Duration period; + + ActorReminderInfo(String data, Duration dueTime, Duration period) { + ValidateDueTime("DueTime", dueTime); + ValidatePeriod("Period", period); + this.data = data; + this.dueTime = dueTime; + this.period = period; + } + + Duration getDueTime() { + return dueTime; + } + + Duration getPeriod() { + return period; + } + + String getData() { + return data; + } + + private static void ValidateDueTime(String argName, Duration value) { + if (value.compareTo(Duration.ZERO) < 0) { + String message = String.format( + "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + throw new IllegalArgumentException(message); + } + } + + private static void ValidatePeriod(String argName, Duration value) throws IllegalArgumentException { + if (value.compareTo(MIN_TIME_PERIOD) < 0) { + String message = String.format( + "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + throw new IllegalArgumentException(message); + } + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java index c0f5d9babe..8a92459050 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -4,10 +4,14 @@ */ package io.dapr.actors.runtime; -import io.dapr.actors.*; +import io.dapr.actors.ActorId; +import io.dapr.actors.ActorTrace; +import reactor.core.publisher.Mono; + import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** * Contains methods to register actor types. Registering the types allows the @@ -15,6 +19,16 @@ */ public class ActorRuntime { + /** + * A trace type used when logging. + */ + private static final String TRACE_TYPE = "ActorRuntime"; + + /** + * Tracing errors, warnings and info logs. + */ + private static final ActorTrace ACTOR_TRACE = new ActorTrace(); + /** * Gets an instance to the ActorRuntime. There is only 1. */ @@ -23,17 +37,22 @@ public class ActorRuntime { /** * A client used to communicate from the actor to the Dapr runtime. */ - private static AppToDaprAsyncClient appToDaprAsyncClient; + private final AppToDaprAsyncClient appToDaprAsyncClient; /** - * A trace type used when logging. + * State provider for Dapr. */ - private static final String TraceType = "ActorRuntime"; + private final DaprStateAsyncProvider daprStateProvider; + + /** + * Serializes/deserializes objects for Actors. + */ + private final ActorStateSerializer actorSerializer; /** * Map of ActorType --> ActorManager. */ - private final HashMap actorManagers; + private final Map actorManagers; /** * The default constructor. This should not be called directly. @@ -45,8 +64,10 @@ private ActorRuntime() throws IllegalStateException { throw new IllegalStateException("ActorRuntime should only be constructed once"); } - this.actorManagers = new HashMap(); - appToDaprAsyncClient = new AppToDaprClientBuilder().buildAsyncClient(); + this.actorManagers = Collections.synchronizedMap(new HashMap<>()); + this.appToDaprAsyncClient = new AppToDaprClientBuilder().buildAsyncClient(); + this.actorSerializer = new ActorStateSerializer(); + this.daprStateProvider = new DaprStateAsyncProvider(this.appToDaprAsyncClient, this.actorSerializer); } /** @@ -67,8 +88,8 @@ public static ActorRuntime getInstance() { } /** - * - * @return Actor type names registered with the runtime. + * Gets the Actor type names registered with the runtime. + * @return Actor type names. */ public Collection getRegisteredActorTypes() { return Collections.unmodifiableCollection(this.actorManagers.keySet()); @@ -79,9 +100,10 @@ public Collection getRegisteredActorTypes() { * * @param clazz The type of actor. * @param Actor class type. + * @return Async void task. */ - public void RegisterActor(Class clazz) { - RegisterActor(clazz, null); + public Mono registerActor(Class clazz) { + return registerActor(clazz, null); } /** @@ -90,18 +112,24 @@ public void RegisterActor(Class clazz) { * @param clazz The type of actor. * @param actorFactory An optional factory to create actors. * @param Actor class type. + * @return Async void task. * This can be used for dependency injection into actors. */ - public void RegisterActor(Class clazz, ActorFactory actorFactory) { + public Mono registerActor(Class clazz, ActorFactory actorFactory) { ActorTypeInformation actorTypeInfo = ActorTypeInformation.create(clazz); ActorFactory actualActorFactory = actorFactory != null ? actorFactory : new DefaultActorFactory(); - // TODO: Refactor into a Builder class. - DaprStateAsyncProvider stateProvider = new DaprStateAsyncProvider(this.appToDaprAsyncClient, new ActorStateSerializer()); - ActorService actorService = new ActorServiceImpl(actorTypeInfo, stateProvider, actualActorFactory); + + ActorRuntimeContext context = new ActorRuntimeContext( + this, + this.actorSerializer, + actualActorFactory, + actorTypeInfo, + this.appToDaprAsyncClient); // Create ActorManagers, override existing entry if registered again. - this.actorManagers.put(actorTypeInfo.getName(), new ActorManager(actorService)); + this.actorManagers.put(actorTypeInfo.getName(), new ActorManager(context)); + return Mono.empty(); } /** @@ -109,10 +137,10 @@ public void RegisterActor(Class clazz, ActorFactory * * @param actorTypeName Actor type name to activate the actor for. * @param actorId Actor id for the actor to be activated. + * @return Async void task. */ - void Activate(String actorTypeName, String actorId) { - // uncomment when ActorManager implemented - // return instance.GetActorManager(actorTypeName).ActivateActor(new ActorId(actorId)); + Mono activate(String actorTypeName, String actorId) { + return this.getActorManager(actorTypeName).flatMap(m -> m.activateActor(new ActorId(actorId))); } /** @@ -120,10 +148,10 @@ void Activate(String actorTypeName, String actorId) { * * @param actorTypeName Actor type name to deactivate the actor for. * @param actorId Actor id for the actor to be deactivated. + * @return Async void task. */ - static void Deactivate(String actorTypeName, String actorId) { - // uncomment when ActorManager implemented - // return instance.GetActorManager(actorTypeName).DeactivateActor(new ActorId(actorId)); + Mono deactivate(String actorTypeName, String actorId) { + return this.getActorManager(actorTypeName).flatMap(m -> m.deactivateActor(new ActorId(actorId))); } /** @@ -133,13 +161,11 @@ static void Deactivate(String actorTypeName, String actorId) { * @param actorTypeName Actor type name to invoke the method for. * @param actorId Actor id for the actor for which method will be invoked. * @param actorMethodName Method name on actor type which will be invoked. - * @param requestBodyStream Payload for the actor method. - * @param responseBodyStream Response for the actor method. - * @return + * @param request Payload for the actor method. + * @return Response for the actor method. */ - static void Dispatch(String actorTypeName, String actorId, String actorMethodName, byte[] requestBodyStream, byte[] responseBodyStream) { - // uncomment when ActorManager implemented - // return instance.GetActorManager(actorTypeName).Dispatch(new ActorId(actorId), actorMethodName, requestBodyStream, responseBodyStream); + Mono invoke(String actorTypeName, String actorId, String actorMethodName, String request) { + return this.getActorManager(actorTypeName).flatMap(m -> m.invokeMethod(new ActorId(actorId), actorMethodName, request)); } /** @@ -148,11 +174,11 @@ static void Dispatch(String actorTypeName, String actorId, String actorMethodNam * @param actorTypeName Actor type name to invoke the method for. * @param actorId Actor id for the actor for which method will be invoked. * @param reminderName The name of reminder provided during registration. - * @param requestBodyStream Payload for the actor method + * @param request Payload for the actor method + * @return Async void task. */ - static void FireReminder(String actorTypeName, String actorId, String reminderName, byte[] requestBodyStream) { - // uncomment when ActorManager implemented - // return instance.GetActorManager(actorTypeName).FireReminder(new ActorId(actorId), reminderName, requestBodyStream); + Mono invokeReminder(String actorTypeName, String actorId, String reminderName, String request) { + return this.getActorManager(actorTypeName).flatMap(m -> m.invokeReminder(new ActorId(actorId), reminderName, request)); } /** @@ -161,22 +187,32 @@ static void FireReminder(String actorTypeName, String actorId, String reminderNa * @param actorTypeName Actor type name to invoke the method for. * @param actorId Actor id for the actor for which method will be invoked. * @param timerName The name of timer provided during registration. + * @return Async void task. */ - static void FireTimer(String actorTypeName, String actorId, String timerName) { - // uncomment when ActorManager implemented - // return instance.GetActorManager(actorTypeName).FireTimerAsync(new ActorId(actorId), timerName); + Mono invokeTimer(String actorTypeName, String actorId, String timerName) { + return this.getActorManager(actorTypeName).flatMap(m -> m.invokeTimer(new ActorId(actorId), timerName)); } - private ActorManager GetActorManager(String actorTypeName) throws IllegalStateException { + /** + * Finds the actor manager or errors out. + * @param actorTypeName Actor type for the actor manager to be found. + * @return Actor manager or error if not found. + */ + private Mono getActorManager(String actorTypeName) { ActorManager actorManager = this.actorManagers.get(actorTypeName); - if (actorManager == null) { - String errorMsg = String.format("Actor type %s is not registered with Actor runtime.", actorTypeName); + try { + if (actorManager == null) { + String errorMsg = String.format("Actor type %s is not registered with Actor runtime.", actorTypeName); - ActorTrace.WriteError(errorMsg); - throw new IllegalStateException(errorMsg); + ACTOR_TRACE.writeError(TRACE_TYPE, actorTypeName, "Actor type is not registered with runtime."); + + throw new IllegalStateException(errorMsg); + } + } catch (IllegalStateException e) { + return Mono.error(e); } - return actorManager; + return Mono.just(actorManager); } } \ No newline at end of file diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java index 41af1a76a7..02fdd04641 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java @@ -5,39 +5,52 @@ package io.dapr.actors.runtime; +import io.dapr.actors.ActorTrace; + public class ActorRuntimeContext { private final ActorRuntime actorRuntime; private final ActorStateSerializer actorSerializer; - private final ActorService actorService; + private final ActorFactory actorFactory; private final ActorTypeInformation actorTypeInformation; + private final ActorTrace actorTrace; + + private final AppToDaprAsyncClient daprClient; + ActorRuntimeContext(ActorRuntime actorRuntime, ActorStateSerializer actorSerializer, - ActorService actorService, - ActorTypeInformation actorTypeInformation) { + ActorFactory actorFactory, + ActorTypeInformation actorTypeInformation, + AppToDaprAsyncClient daprClient) { this.actorRuntime = actorRuntime; this.actorSerializer = actorSerializer; - this.actorService = actorService; + this.actorFactory = actorFactory; this.actorTypeInformation = actorTypeInformation; + this.actorTrace = new ActorTrace(); + this.daprClient = daprClient; } ActorRuntime getActorRuntime() { - return actorRuntime; + return this.actorRuntime; } ActorStateSerializer getActorSerializer() { - return actorSerializer; + return this.actorSerializer; } - ActorService getActorService() { - return actorService; + ActorFactory getActorFactory() { + return this.actorFactory; } ActorTypeInformation getActorTypeInformation() { - return actorTypeInformation; + return this.actorTypeInformation; } + + ActorTrace getActorTrace() { return this.actorTrace; } + + AppToDaprAsyncClient getDaprClient() { return this.daprClient; } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java deleted file mode 100644 index 2e16ce9cdc..0000000000 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.dapr.actors.runtime; - -import io.dapr.actors.ActorId; - -/** - * Interface exposed to Actor's implementations (application layer). - */ -public interface ActorService { - - /** - * Creates an actor. - * @param actorId Identifier for the Actor to be created. - * @return New Actor instance. - */ - T createActor(ActorId actorId); -} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java deleted file mode 100644 index 33be49cd64..0000000000 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -package io.dapr.actors.runtime; - -import io.dapr.actors.ActorId; - -/** - * Implementation of the Actor Service that contains a state provider. - */ -class ActorServiceImpl implements ActorService { - - /** - * Customizable factory for Actors. - */ - private final ActorFactory actorFactory; - - /** - * State provider for Actors. - */ - private final DaprStateAsyncProvider stateProvider; - - /** - * Information on the {@link Actor} type being serviced. - */ - private final ActorTypeInformation actorTypeInformation; - - /** - * Instantiates a stateful service for a given {@link Actor} type. - * @param actorTypeInformation Information on the {@link Actor} type being serviced. - * @param stateProvider State provider for Actors. - * @param actorFactory Customizable factor for Actors. - */ - public ActorServiceImpl(ActorTypeInformation actorTypeInformation, DaprStateAsyncProvider stateProvider, ActorFactory actorFactory) { - this.actorTypeInformation = actorTypeInformation; - this.actorFactory = actorFactory; - this.stateProvider = stateProvider; - } - - /** - * Gets the state provider for {@link Actor}. - * @return State provider. - */ - DaprStateAsyncProvider getStateProvider() { - return stateProvider; - } - - /** - * Gets the information on the {@link Actor} Type. - * @return Information on the {@link Actor} Type. - */ - ActorTypeInformation getActorTypeInformation() { - return actorTypeInformation; - } - - /** - * {@inheritDoc} - */ - @Override - public T createActor(ActorId actorId) { - return this.actorFactory.createActor(this, actorId); - } -} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java index 8e5cc7f79f..902aa70aa6 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java @@ -20,7 +20,11 @@ class ActorStateManager { this.actorId = actorId; } - Mono SaveState() { + Mono saveState() { + return Mono.empty(); + } + + public Mono clearCache() { return Mono.empty(); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java index 349d7e682f..c2d12cd510 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java @@ -6,134 +6,174 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.time.Duration; /** * Serializes and deserializes an object. */ class ActorStateSerializer { - /** - * Shared Json Factory as per Jackson's documentation, used only for this class. - */ - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - /** - * Shared Json serializer/deserializer as per Jackson's documentation. - */ - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - /** - * Serializes a given state object into byte array. - * - * @param state State object to be serialized. - * @return Array of bytes[] with the serialized content. - * @throws IOException - */ - String serialize(T state) throws IOException { - if (state == null) { - return null; - } - - if (state.getClass() == String.class) { - return state.toString(); - } - - if (state.getClass() == ActorTimer.class) { - // Special serializer for this internal classes. - return serialize((ActorTimer) state); - } - - if (isPrimitiveOrEquivalent(state.getClass())) { - return state.toString(); - } - - // Not string, not primitive, so it is a complex type: we use JSON for that. - return OBJECT_MAPPER.writeValueAsString(state); + /** + * Shared Json Factory as per Jackson's documentation, used only for this class. + */ + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + + /** + * Shared Json serializer/deserializer as per Jackson's documentation. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * Serializes a given state object into byte array. + * + * @param state State object to be serialized. + * @return Array of bytes[] with the serialized content. + * @throws IOException + */ + String serialize(T state) throws IOException { + if (state == null) { + return null; } - /** - * Deserializes the byte array into the original object. - * - * @param value String to be parsed. - * @param clazz Type of the object being deserialized. - * @param Generic type of the object being deserialized. - * @return Object of type T. - * @throws IOException - */ - T deserialize(String value, Class clazz) throws IOException { - if (clazz == String.class) { - return (T) value; - } - - if (isPrimitiveOrEquivalent(clazz)) { - return parse(value, clazz); - } - - if (value == null) { - return (T) null; - } - - // Not string, not primitive, so it is a complex type: we use JSON for that. - return OBJECT_MAPPER.readValue(value, clazz); + if (state.getClass() == String.class) { + return state.toString(); } - private static boolean isPrimitiveOrEquivalent(Class clazz) { - if (clazz == null) { - return false; - } - - return (clazz.isPrimitive() || - (clazz == Boolean.class) || - (clazz == Character.class) || - (clazz == Byte.class) || - (clazz == Short.class) || - (clazz == Integer.class) || - (clazz == Long.class) || - (clazz == Float.class) || - (clazz == Double.class) || - (clazz == Void.class)); + if (state.getClass() == ActorTimer.class) { + // Special serializer for this internal classes. + return serialize((ActorTimer) state); } - private static T parse(String value, Class clazz) { - if (value == null) { - if (boolean.class == clazz) return (T) Boolean.FALSE; - if (byte.class == clazz) return (T) Byte.valueOf((byte) 0); - if (short.class == clazz) return (T) Short.valueOf((short) 0); - if (int.class == clazz) return (T) Integer.valueOf(0); - if (long.class == clazz) return (T) Long.valueOf(0L); - if (float.class == clazz) return (T) Float.valueOf(0); - if (double.class == clazz) return (T) Double.valueOf(0); - - return null; - } - - if ((Boolean.class == clazz) || (boolean.class == clazz)) return (T) Boolean.valueOf(value); - if ((Byte.class == clazz) || (byte.class == clazz)) return (T) Byte.valueOf(value); - if ((Short.class == clazz) || (short.class == clazz)) return (T) Short.valueOf(value); - if ((Integer.class == clazz) || (int.class == clazz)) return (T) Integer.valueOf(value); - if ((Long.class == clazz) || (long.class == clazz)) return (T) Long.valueOf(value); - if ((Float.class == clazz) || (float.class == clazz)) return (T) Float.valueOf(value); - if ((Double.class == clazz) || (double.class == clazz)) return (T) Double.valueOf(value); - - return null; + if (state.getClass() == ActorReminderInfo.class) { + // Special serializer for this internal classes. + return serialize((ActorReminderInfo) state); } - private static String serialize(ActorTimer timer) throws IOException { - try (Writer writer = new StringWriter()) { - JsonGenerator generator = JSON_FACTORY.createGenerator(writer); - generator.writeStartObject(); - generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(timer.getDueTime())); - generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(timer.getPeriod())); - generator.writeEndObject(); - generator.close(); - writer.flush(); - return writer.toString(); - } + if (isPrimitiveOrEquivalent(state.getClass())) { + return state.toString(); } + + // Not string, not primitive, so it is a complex type: we use JSON for that. + return OBJECT_MAPPER.writeValueAsString(state); + } + + /** + * Deserializes the byte array into the original object. + * + * @param value String to be parsed. + * @param clazz Type of the object being deserialized. + * @param Generic type of the object being deserialized. + * @return Object of type T. + * @throws IOException + */ + T deserialize(String value, Class clazz) throws IOException { + if (clazz == String.class) { + return (T) value; + } + + if (clazz == ActorReminderInfo.class) { + // Special serializer for this internal classes. + return (T) deserialize(value); + } + + if (isPrimitiveOrEquivalent(clazz)) { + return parse(value, clazz); + } + + if (value == null) { + return (T) null; + } + + // Not string, not primitive, so it is a complex type: we use JSON for that. + return OBJECT_MAPPER.readValue(value, clazz); + } + + private static boolean isPrimitiveOrEquivalent(Class clazz) { + if (clazz == null) { + return false; + } + + return (clazz.isPrimitive() || + (clazz == Boolean.class) || + (clazz == Character.class) || + (clazz == Byte.class) || + (clazz == Short.class) || + (clazz == Integer.class) || + (clazz == Long.class) || + (clazz == Float.class) || + (clazz == Double.class) || + (clazz == Void.class)); + } + + private static T parse(String value, Class clazz) { + if (value == null) { + if (boolean.class == clazz) return (T) Boolean.FALSE; + if (byte.class == clazz) return (T) Byte.valueOf((byte) 0); + if (short.class == clazz) return (T) Short.valueOf((short) 0); + if (int.class == clazz) return (T) Integer.valueOf(0); + if (long.class == clazz) return (T) Long.valueOf(0L); + if (float.class == clazz) return (T) Float.valueOf(0); + if (double.class == clazz) return (T) Double.valueOf(0); + + return null; + } + + if ((Boolean.class == clazz) || (boolean.class == clazz)) return (T) Boolean.valueOf(value); + if ((Byte.class == clazz) || (byte.class == clazz)) return (T) Byte.valueOf(value); + if ((Short.class == clazz) || (short.class == clazz)) return (T) Short.valueOf(value); + if ((Integer.class == clazz) || (int.class == clazz)) return (T) Integer.valueOf(value); + if ((Long.class == clazz) || (long.class == clazz)) return (T) Long.valueOf(value); + if ((Float.class == clazz) || (float.class == clazz)) return (T) Float.valueOf(value); + if ((Double.class == clazz) || (double.class == clazz)) return (T) Double.valueOf(value); + + return null; + } + + private static String serialize(ActorTimer timer) throws IOException { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(timer.getDueTime())); + generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(timer.getPeriod())); + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } + + private static String serialize(ActorReminderInfo reminder) throws IOException { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(reminder.getDueTime())); + generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(reminder.getPeriod())); + if (reminder.getData() != null) { + generator.writeStringField("data", reminder.getData()); + } + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } + + private static ActorReminderInfo deserialize(String value) throws IOException { + if (value == null) { + return null; + } + + JsonNode node = OBJECT_MAPPER.readTree(value); + Duration dueTime = ConverterUtils.ConvertDurationFromDaprFormat(node.get("dueTime").asText()); + Duration period = ConverterUtils.ConvertDurationFromDaprFormat(node.get("period").asText()); + String data = node.get("data") != null ? node.get("data").asText() : null; + + return new ActorReminderInfo(data, dueTime, period); + } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java index 4310a0dac1..fad5214132 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimer.java @@ -9,8 +9,9 @@ /** * Represents the timer set on an Actor, to be called once after due time and then every period. + * @param State type. */ -class ActorTimer { +final class ActorTimer { /** * Actor that owns this timer. diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java index e398082503..2eeaf91c54 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java @@ -27,7 +27,7 @@ final class ActorTypeInformation { /** * Actor's immediate interfaces. */ - private final Collection interfaces; + private final Collection> interfaces; /** * Whether Actor type is abstract. @@ -50,7 +50,7 @@ final class ActorTypeInformation { */ private ActorTypeInformation(String name, Class implementationClass, - Collection interfaces, + Collection> interfaces, boolean abstractClass, boolean remindable) { this.name = name; diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprAsyncClient.java b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprAsyncClient.java index b31c7beca9..5b270dcb24 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprAsyncClient.java @@ -71,5 +71,5 @@ interface AppToDaprAsyncClient { * @param timerName Name of timer to be unregistered. * @return Asynchronous void result. */ - Mono unregisterTimerAsync(String actorType, String actorId, String timerName); + Mono unregisterTimer(String actorType, String actorId, String timerName); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java index 48bb382c0d..e4cef9f487 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java @@ -4,7 +4,7 @@ */ package io.dapr.actors.runtime; -import io.dapr.actors.*; +import io.dapr.actors.AbstractClientBuilder; import okhttp3.OkHttpClient; /** diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprHttpAsyncClient.java b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprHttpAsyncClient.java index 82259c79db..93f5db0945 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprHttpAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprHttpAsyncClient.java @@ -6,13 +6,12 @@ import io.dapr.actors.AbstractDaprClient; import io.dapr.actors.Constants; -import okhttp3.*; +import okhttp3.OkHttpClient; import reactor.core.publisher.Mono; /** * Http client to call Dapr's API for actors. */ -//public class DaprHttpAsyncClient implements DaprAsyncClient { class AppToDaprHttpAsyncClient extends AbstractDaprClient implements AppToDaprAsyncClient { /** @@ -74,7 +73,7 @@ public Mono registerTimer(String actorType, String actorId, String timerNa * {@inheritDoc} */ @Override - public Mono unregisterTimerAsync(String actorType, String actorId, String timerName) { + public Mono unregisterTimer(String actorType, String actorId, String timerName) { String url = String.format(Constants.ACTOR_TIMER_RELATIVE_URL_FORMAT, actorType, actorId, timerName); return super.invokeAPIVoid("DELETE", url, null); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java b/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java index 0a46a68548..4446c78ae0 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java @@ -5,7 +5,7 @@ package io.dapr.actors.runtime; -import java.time.*; +import java.time.Duration; public class ConverterUtils { diff --git a/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java index 0b9aac2e15..40e140d331 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java @@ -26,11 +26,12 @@ public T createActor(ActorRuntimeContext actorRuntimeContext, ActorId actorId } Constructor constructor = actorRuntimeContext - .getActorTypeInformation() - .getImplementationClass() - .getConstructor(ActorRuntimeContext.class, ActorId.class); + .getActorTypeInformation() + .getImplementationClass() + .getConstructor(ActorRuntimeContext.class, ActorId.class); return constructor.newInstance(actorRuntimeContext, actorId); - } catch (ReflectiveOperationException e) { + } catch (Exception e) { + //TODO: Use ActorTrace. e.printStackTrace(); } return null; diff --git a/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java index 4d56077b8a..84843affef 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java @@ -11,7 +11,13 @@ /** * Interface that actors must implement to consume reminders registered using RegisterReminderAsync. */ -public interface Remindable { +public interface Remindable { + + /** + * Gets the class for state object. + * @return Class for state object. + */ + Class getReminderStateType(); /** * The reminder call back invoked when an actor reminder is triggered. @@ -26,5 +32,5 @@ public interface Remindable { * @param period The invocation period provided during registration. * @return A task that represents the asynchronous operation performed by this callback. */ - Mono receiveReminder(String reminderName, byte[] state, Duration dueTime, Duration period); + Mono receiveReminder(String reminderName, T state, Duration dueTime, Duration period); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java b/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java deleted file mode 100644 index 78790c43e2..0000000000 --- a/sdk/src/main/java/io/dapr/actors/runtime/ReminderInfo.java +++ /dev/null @@ -1,65 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -// ------------------------------------------------------------ - -package io.dapr.actors.runtime; - -import java.time.Duration; - -class ReminderInfo { - - private static final Duration MIN_TIME_PERIOD = Duration.ofMillis(-1); - - private Duration dueTime; - - private Duration period; - - private byte[] data; - - public Duration getDueTime() { - return dueTime; - } - - public void setDueTime(Duration dueTime) { - this.dueTime = dueTime; - } - - public Duration getPeriod() { - return period; - } - - public void setPeriod(Duration period) { - this.period = period; - } - - public byte[] getData() { - return data; - } - - public void setData(byte[] data) { - this.data = data; - } - - public ReminderInfo(byte[] state, Duration dueTime, Duration period) { - ValidateDueTime("DueTime", dueTime); - ValidatePeriod("Period", period); - this.data = state; - this.dueTime = dueTime; - this.period = period; - } - - private static void ValidateDueTime(String argName, Duration value) { - if (value.compareTo(Duration.ZERO) < 0) { - String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); - throw new IllegalArgumentException(message); - } - } - - private static void ValidatePeriod(String argName, Duration value) throws IllegalArgumentException { - if (value.compareTo(MIN_TIME_PERIOD) < 0) { - String message = String.format("argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); - throw new IllegalArgumentException(message); - } - } -} diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java index 03a33f71a3..fbaba3df73 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java @@ -19,7 +19,7 @@ public class ActorMethodInfoMapTest { @Test public void normalUsage() { - ArrayList> interfaceTypes = new ArrayList>(); + ArrayList> interfaceTypes = new ArrayList<>(); interfaceTypes.add(TestActor.class); ActorMethodInfoMap m = new ActorMethodInfoMap(interfaceTypes); @@ -37,7 +37,7 @@ public void normalUsage() { @Test(expected = NoSuchMethodException.class) public void lookUpNonExistingMethod() throws NoSuchMethodException { - ArrayList> interfaceTypes = new ArrayList>(); + ArrayList> interfaceTypes = new ArrayList<>(); interfaceTypes.add(TestActor.class); ActorMethodInfoMap m = new ActorMethodInfoMap(interfaceTypes); diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java new file mode 100644 index 0000000000..88e22dcb48 --- /dev/null +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java @@ -0,0 +1,62 @@ +package io.dapr.actors.runtime; + +import org.junit.Assert; +import org.junit.Test; +import java.time.Duration; + +public class ActorReminderInfoTest { + + private static final ActorStateSerializer SERIALIZER = new ActorStateSerializer(); + + @Test(expected = IllegalArgumentException.class) + public void outOfRangeDueTime() { + ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusSeconds(-10), Duration.ZERO.plusMinutes(1)); + } + + @Test + public void negativePeriod() { + // this is ok + ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMillis(-1)); + } + + @Test(expected = IllegalArgumentException.class) + public void outOfRangePeriod() { + ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMinutes(-10)); + } + + @Test + public void noState() { + ActorReminderInfo original = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); + ActorReminderInfo recreated = null; + try { + String serialized = SERIALIZER.serialize(original); + recreated = SERIALIZER.deserialize(serialized, ActorReminderInfo.class); + } + catch(Exception e) { + System.out.println("The error is: " + e); + Assert.fail(); + } + + Assert.assertEquals(original.getData(), recreated.getData()); + Assert.assertEquals(original.getDueTime(), recreated.getDueTime()); + Assert.assertEquals(original.getPeriod(), recreated.getPeriod()); + } + + @Test + public void withState() { + ActorReminderInfo original = new ActorReminderInfo("maru", Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); + ActorReminderInfo recreated = null; + try { + String serialized = SERIALIZER.serialize(original); + recreated = SERIALIZER.deserialize(serialized, ActorReminderInfo.class); + } + catch(Exception e) { + System.out.println("The error is: " + e); + Assert.fail(); + } + + Assert.assertEquals(original.getData(), recreated.getData()); + Assert.assertEquals(original.getDueTime(), recreated.getDueTime()); + Assert.assertEquals(original.getPeriod(), recreated.getPeriod()); + } +} diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java index 6ee2d46d8c..8307067661 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java @@ -7,6 +7,9 @@ import org.junit.Assert; import org.junit.Test; +import reactor.core.publisher.Mono; + +import java.time.Duration; /** * Unit tests for ActorTypeInformation. @@ -27,7 +30,7 @@ public void notRemindable() { class A extends AbstractActor implements MyActor { A() { - super(timers); + super(null, null); } } @@ -49,7 +52,17 @@ public void remindable() { class A extends AbstractActor implements MyActor, Remindable { A() { - super(timers); + super(null, null); + } + + @Override + public Class getReminderStateType() { + return null; + } + + @Override + public Mono receiveReminder(String reminderName, Object state, Duration dueTime, Duration period) { + return null; } } @@ -72,7 +85,7 @@ public void renamedWithAnnotation() { @ActorType(Name = "B") class A extends AbstractActor implements MyActor { A() { - super(timers); + super(null, null); } } @@ -93,7 +106,7 @@ class A extends AbstractActor implements MyActor { public void nonActorParentClass() { abstract class MyAbstractClass extends AbstractActor implements MyActor { MyAbstractClass() { - super(timers); + super(null, null); } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java index f760cd4c82..43d6f8e8ea 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java @@ -21,13 +21,13 @@ public class DefaultActorFactoryTest { */ static class MyActor extends AbstractActor implements Actor { - ActorService actorService; + ActorRuntimeContext context; ActorId actorId; - public MyActor(ActorService actorService, ActorId actorId) { - super(timers); - this.actorService = actorService; + public MyActor(ActorRuntimeContext context, ActorId actorId) { + super(context, actorId); + this.context = context; this.actorId = actorId; } } @@ -35,9 +35,9 @@ public MyActor(ActorService actorService, ActorId actorId) { /** * A non-compliant implementation of Actor to be used in the tests below. */ - static class InvalidActor extends AbstractActor { + static class InvalidActor extends AbstractActor implements Actor { InvalidActor() { - super(timers); + super(null, null); } } @@ -46,13 +46,13 @@ static class InvalidActor extends AbstractActor { */ @Test public void happyActor() { - DefaultActorFactory factory = new DefaultActorFactory(ActorTypeInformation.tryCreate(MyActor.class)); + DefaultActorFactory factory = new DefaultActorFactory<>(); ActorId actorId = ActorId.createRandom(); - MyActor actor = factory.createActor(mock(ActorService.class), actorId); + MyActor actor = factory.createActor(createActorRuntimeContext(MyActor.class), actorId); Assert.assertEquals(actorId, actor.actorId); - Assert.assertNotNull(actor.actorService); + Assert.assertNotNull(actor.context); } /** @@ -60,12 +60,21 @@ public void happyActor() { */ @Test public void noValidConstructor() { - DefaultActorFactory factory = new DefaultActorFactory(ActorTypeInformation.tryCreate(InvalidActor.class)); + DefaultActorFactory factory = new DefaultActorFactory<>(); ActorId actorId = ActorId.createRandom(); - InvalidActor actor = factory.createActor(mock(ActorService.class), actorId); + InvalidActor actor = factory.createActor(createActorRuntimeContext(InvalidActor.class), actorId); Assert.assertNull(actor); } + private static ActorRuntimeContext createActorRuntimeContext(Class clazz) { + return new ActorRuntimeContext( + mock(ActorRuntime.class), + mock(ActorStateSerializer.class), + mock(ActorFactory.class), + ActorTypeInformation.create(clazz), + mock(AppToDaprAsyncClient.class)); + } + } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ReminderInfoTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ReminderInfoTest.java deleted file mode 100644 index 4f56ba404a..0000000000 --- a/sdk/src/test/java/io/dapr/actors/runtime/ReminderInfoTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.dapr.actors.runtime; - -import org.junit.Assert; -import org.junit.Test; -import java.time.Duration; -import java.util.Arrays; - -public class ReminderInfoTest { - @Test(expected = IllegalArgumentException.class) - public void outOfRangeDueTime() { - ReminderInfo info = new ReminderInfo(null, Duration.ZERO.plusSeconds(-10), Duration.ZERO.plusMinutes(1)); - } - - @Test - public void negativePeriod() { - // this is ok - ReminderInfo info = new ReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMillis(-1)); - } - - @Test(expected = IllegalArgumentException.class) - public void outOfRangePeriod() { - ReminderInfo info = new ReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMinutes(-10)); - } - - @Test - public void noState() { - ReminderInfo original = new ReminderInfo(null, Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); - ReminderInfo recreated = null; - try { - String serialized = original.serialize(); - recreated = ReminderInfo.deserialize(serialized.getBytes()); - } - catch(Exception e) { - System.out.println("The error is: " + e); - Assert.fail(); - } - - Assert.assertEquals(original.data, recreated.data); - Assert.assertEquals(original.dueTime, recreated.dueTime); - Assert.assertEquals(original.period, recreated.period); - } - - @Test - public void withState() { - ReminderInfo original = new ReminderInfo("maru".getBytes(), Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); - ReminderInfo recreated = null; - try { - String serialized = original.serialize(); - recreated = ReminderInfo.deserialize(serialized.getBytes()); - } - catch(Exception e) { - System.out.println("The error is: " + e); - Assert.fail(); - } - - Assert.assertTrue(Arrays.equals(original.data, recreated.data)); - Assert.assertEquals(original.dueTime, recreated.dueTime); - Assert.assertEquals(original.period, recreated.period); - } -} From 72e410ae44784fc52527216d10cf54dad053cf59 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 23 Dec 2019 20:40:23 -0800 Subject: [PATCH 3/7] Adding example for actor runtime service. --- examples/pom.xml | 17 +- .../dapr/examples/actors/http/DemoActor.java | 3 +- .../examples/actors/http/DemoActorImpl.java | 34 +++- .../actors/http/DemoActorService.java | 183 +++++++++++++++++- .../invoke/grpc/HelloWorldService.java | 2 +- .../io/dapr/actors/runtime/AbstractActor.java | 4 +- .../io/dapr/actors/runtime/ActorRuntime.java | 10 +- 7 files changed, 240 insertions(+), 13 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 08f79158a3..e833af8740 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -18,10 +18,17 @@ ${project.build.directory}/generated-sources ${project.parent.basedir}/proto - 1.11 + 11 + ${java.version} + ${java.version} + + io.undertow + undertow-servlet + 2.0.26.Final + commons-cli commons-cli @@ -101,6 +108,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 11 + + 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 e50fb2d94d..95d56a1720 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 @@ -9,5 +9,6 @@ * Example of implementation of an Actor. */ public interface DemoActor { - // TODO. + + String say(String something); } 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 3e72d9124f..301bb6a429 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 @@ -5,9 +5,39 @@ package io.dapr.examples.actors.http; +import io.dapr.actors.ActorId; +import io.dapr.actors.runtime.AbstractActor; +import io.dapr.actors.runtime.Actor; +import io.dapr.actors.runtime.ActorRuntimeContext; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + /** * Implementation of the DemoActor for the server side. */ -public class DemoActorImpl { - // TODO. +public class DemoActorImpl extends AbstractActor implements DemoActor, Actor { + + /** + * Format to output date and time. + */ + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) { + super(runtimeContext, id); + } + + @Override + public String say(String something) { + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + // Handles the request by printing message. + System.out.println("Server: " + something == null ? "" : something + " @ " + utcNowAsString); + + // Now respond with current timestamp. + return utcNowAsString; + } } diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java index 8f763e96bc..3e0cdccea1 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java @@ -5,12 +5,193 @@ package io.dapr.examples.actors.http; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.actors.runtime.ActorRuntime; +import io.undertow.Undertow; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.RoutingHandler; +import io.undertow.util.Headers; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Deque; +import java.util.Map; + /** * Service for Actor runtime. + * 1. Build and install jars: + * mvn clean install + * 2. Run in server mode: + * dapr run --app-id hellogrpc --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000" */ public class DemoActorService { + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final HttpHandler ROUTES = new RoutingHandler() + .get("/", DemoActorService::handleDaprConfig) + .get("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/dapr/config", DemoActorService::handleDaprConfig) + .post("/actors/{actorType}/{id}", DemoActorService::handleActorActivate) + .delete("/actors/{actorType}/{id}", DemoActorService::handleActorDeactivate) + .put("/actors/{actorType}/{id}/method/{methodName}", DemoActorService::handleActorInvoke); + + private final int port; + + private final Undertow server; + + private DemoActorService(int port) { + this.port = port; + this.server = Undertow + .builder() + .addHttpListener(port, "localhost") + .setHandler(ROUTES) + .build(); + ActorRuntime.getInstance().registerActor(DemoActorImpl.class); + } + + private void start() { + // Now we handle ctrl+c (or any other JVM shutdown) + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() { + System.out.println("Server: shutting down gracefully ..."); + DemoActorService.this.server.stop(); + System.out.println("Server: Bye."); + } + }); + + System.out.println(String.format("Server: listening on port %d ...", this.port)); + this.server.start(); + } + + private static void handleDaprConfig(HttpServerExchange exchange) throws IOException { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); + String result = ""; + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + generator.writeArrayFieldStart("entities"); + for(String actorClass : ActorRuntime.getInstance().getRegisteredActorTypes()) { + generator.writeString(actorClass); + } + generator.writeEndArray(); + generator.writeStringField("actorIdleTimeout", "10s"); + generator.writeStringField("actorScanInterval", "1s"); + generator.writeStringField("drainOngoingCallTimeout", "1s"); + generator.writeBooleanField("drainBalancedActors", true); + generator.writeEndObject(); + generator.close(); + writer.flush(); + result = writer.toString(); + } + + exchange.getResponseSender().send(result); + } + + private static void handleActorActivate(HttpServerExchange exchange) { + if (exchange.isInIoThread()) { + exchange.dispatch(DemoActorService::handleActorActivate); + return; + } + + String actorType = findParamValueOrNull(exchange, "actorType"); + String actorId = findParamValueOrNull(exchange, "id"); + ActorRuntime.getInstance().activate(actorType, actorId).block(); + exchange.getResponseSender().send(""); + } + + private static void handleActorDeactivate(HttpServerExchange exchange) { + if (exchange.isInIoThread()) { + exchange.dispatch(DemoActorService::handleActorDeactivate); + return; + } + + String actorType = findParamValueOrNull(exchange, "actorType"); + String actorId = findParamValueOrNull(exchange, "id"); + ActorRuntime.getInstance().deactivate(actorType, actorId).block(); + } + + private static void handleActorInvoke(HttpServerExchange exchange) throws IOException { + if (exchange.isInIoThread()) { + exchange.dispatch(DemoActorService::handleActorInvoke); + return; + } + + String actorType = findParamValueOrNull(exchange, "actorType"); + String actorId = findParamValueOrNull(exchange, "id"); + String methodName = findParamValueOrNull(exchange, "methodName"); + exchange.startBlocking(); + String data = findData(exchange.getInputStream()); + String result = ActorRuntime.getInstance().invoke(actorType, actorId, methodName, data).block(); + exchange.getResponseSender().send(buildResponse(result)); + } + + private static String findParamValueOrNull(HttpServerExchange exchange, String name) { + Map> params = exchange.getQueryParameters(); + if (params == null) { + return null; + } + + Deque values = params.get(name); + if ((values == null) || (values.isEmpty())) { + return null; + } + + return values.getFirst(); + } + + private static String findData(InputStream stream) throws IOException { + JsonNode root = OBJECT_MAPPER.readTree(stream); + if (root == null) { + return null; + } + + JsonNode dataNode = root.get("data"); + if (dataNode == null) { + return null; + } + + return new String(dataNode.binaryValue(), StandardCharsets.UTF_8); + } + + private static String buildResponse(String data) throws IOException { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + if (data != null) { + generator.writeBinaryField("data", data.getBytes()); + } + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } + public static void main(String[] args) throws Exception { - // TODO + Options options = new Options(); + options.addRequiredOption("p", "port", true, "Port to listen to."); + + CommandLineParser parser = new DefaultParser(); + CommandLine cmd = parser.parse(options, args); + + // If port string is not valid, it will throw an exception. + int port = Integer.parseInt(cmd.getOptionValue("port")); + final DemoActorService service = new DemoActorService(port); + service.start(); } } diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java index d16edc33b4..f674c6430f 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java @@ -125,7 +125,7 @@ public SayResponse say(SayRequest request) { public static void main(String[] args) throws Exception { Options options = new Options(); - options.addRequiredOption("p", "port", true, "Port to listen or send event to."); + options.addRequiredOption("p", "port", true, "Port to listen to."); CommandLineParser parser = new DefaultParser(); CommandLine cmd = parser.parse(options, args); diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 5767132042..9a3521f263 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -31,10 +31,10 @@ public abstract class AbstractActor { private final Map> timers; - protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { + protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { this.actorRuntimeContext = runtimeContext; this.id = id; - this.actorStateManager = new ActorStateManager(runtimeContext.getActorTypeInformation().getName(), id); + this.actorStateManager = new ActorStateManager(runtimeContext.getActorTypeInformation().getName(), id); this.actorTrace = runtimeContext.getActorTrace(); this.timers = Collections.synchronizedMap(new HashMap<>()); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java index 8a92459050..f04eed5fa9 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -139,7 +139,7 @@ public Mono registerActor(Class clazz, ActorF * @param actorId Actor id for the actor to be activated. * @return Async void task. */ - Mono activate(String actorTypeName, String actorId) { + public Mono activate(String actorTypeName, String actorId) { return this.getActorManager(actorTypeName).flatMap(m -> m.activateActor(new ActorId(actorId))); } @@ -150,7 +150,7 @@ Mono activate(String actorTypeName, String actorId) { * @param actorId Actor id for the actor to be deactivated. * @return Async void task. */ - Mono deactivate(String actorTypeName, String actorId) { + public Mono deactivate(String actorTypeName, String actorId) { return this.getActorManager(actorTypeName).flatMap(m -> m.deactivateActor(new ActorId(actorId))); } @@ -164,7 +164,7 @@ Mono deactivate(String actorTypeName, String actorId) { * @param request Payload for the actor method. * @return Response for the actor method. */ - Mono invoke(String actorTypeName, String actorId, String actorMethodName, String request) { + public Mono invoke(String actorTypeName, String actorId, String actorMethodName, String request) { return this.getActorManager(actorTypeName).flatMap(m -> m.invokeMethod(new ActorId(actorId), actorMethodName, request)); } @@ -177,7 +177,7 @@ Mono invoke(String actorTypeName, String actorId, String actorMethodName * @param request Payload for the actor method * @return Async void task. */ - Mono invokeReminder(String actorTypeName, String actorId, String reminderName, String request) { + public Mono invokeReminder(String actorTypeName, String actorId, String reminderName, String request) { return this.getActorManager(actorTypeName).flatMap(m -> m.invokeReminder(new ActorId(actorId), reminderName, request)); } @@ -189,7 +189,7 @@ Mono invokeReminder(String actorTypeName, String actorId, String reminderN * @param timerName The name of timer provided during registration. * @return Async void task. */ - Mono invokeTimer(String actorTypeName, String actorId, String timerName) { + public Mono invokeTimer(String actorTypeName, String actorId, String timerName) { return this.getActorManager(actorTypeName).flatMap(m -> m.invokeTimer(new ActorId(actorId), timerName)); } From f866b4b42fb01f660b362b195c982973b4c18095 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Thu, 26 Dec 2019 18:04:28 -0800 Subject: [PATCH 4/7] Implements ActorStateManager + fixes + javadocs. --- .../io/dapr/actors/AbstractDaprClient.java | 9 +- sdk/src/main/java/io/dapr/actors/ActorId.java | 108 +++-- .../main/java/io/dapr/actors/ActorTrace.java | 43 +- .../io/dapr/actors/runtime/AbstractActor.java | 161 +++++-- .../io/dapr/actors/runtime/ActorManager.java | 97 ++++- .../actors/runtime/ActorMethodInfoMap.java | 10 + ...nderInfo.java => ActorReminderParams.java} | 49 ++- .../io/dapr/actors/runtime/ActorRuntime.java | 3 +- .../actors/runtime/ActorRuntimeContext.java | 69 ++- .../dapr/actors/runtime/ActorStateChange.java | 9 +- .../actors/runtime/ActorStateManager.java | 287 +++++++++++- .../actors/runtime/ActorStateSerializer.java | 56 ++- .../io/dapr/actors/runtime/ActorType.java | 4 + .../runtime/DaprStateAsyncProvider.java | 13 +- ...ConverterUtils.java => DurationUtils.java} | 14 +- .../test/java/io/dapr/actors/ActorIdTest.java | 2 +- ...Test.java => ActorReminderParamsTest.java} | 20 +- .../runtime/DaprStateAsyncProviderTest.java | 407 +++++++++--------- .../runtime/DefaultActorFactoryTest.java | 3 +- ...rUtilsTest.java => DurationUtilsTest.java} | 48 +-- 20 files changed, 1030 insertions(+), 382 deletions(-) rename sdk/src/main/java/io/dapr/actors/runtime/{ActorReminderInfo.java => ActorReminderParams.java} (50%) rename sdk/src/main/java/io/dapr/actors/runtime/{ConverterUtils.java => DurationUtils.java} (94%) rename sdk/src/test/java/io/dapr/actors/runtime/{ActorReminderInfoTest.java => ActorReminderParamsTest.java} (64%) rename sdk/src/test/java/io/dapr/actors/runtime/{ConverterUtilsTest.java => DurationUtilsTest.java} (51%) diff --git a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java index 48a91b02e6..f35e4d75ce 100644 --- a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java +++ b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java @@ -5,13 +5,16 @@ package io.dapr.actors; import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; +import reactor.core.publisher.Mono; + import java.io.IOException; import java.net.URL; import java.util.UUID; -import okhttp3.*; -import reactor.core.publisher.Mono; -// base class of hierarchy +/** + * Base for Dapr HTTP Client. + */ public abstract class AbstractDaprClient { /** diff --git a/sdk/src/main/java/io/dapr/actors/ActorId.java b/sdk/src/main/java/io/dapr/actors/ActorId.java index 4148aa1f8f..aa1cf50381 100644 --- a/sdk/src/main/java/io/dapr/actors/ActorId.java +++ b/sdk/src/main/java/io/dapr/actors/ActorId.java @@ -35,41 +35,14 @@ public ActorId(String id) { } /** - * Returns the id of the actor as {link #java.lang.String} * - * @return ActorID as {link #java.lang.String} + * @return The String representation of this ActorId */ - public String getStringId() { + @Override + public String toString() { return this.stringId; } - /** - * Creates a new ActorId with a random id. - * - * @return A new ActorId with a random id. - */ - public static ActorId createRandom() { - UUID id = UUID.randomUUID(); - return new ActorId(id.toString()); - } - - /** - * Determines whether two specified actorIds have the same id. - * - * @param id1 The first actorId to compare, or null - * @param id2 The second actorId to compare, or null. - * @return true if the id is same for both objects; otherwise, false. - */ - static public boolean equals(ActorId id1, ActorId id2) { - if (id1 == null && id2 == null) { - return true; - } else if (id2 == null || id1 == null) { - return false; - } else { - return hasEqualContent(id1, id2); - } - } - /** * Compares this instance with a specified {link #ActorId} object and * indicates whether this instance precedes, follows, or appears in the same @@ -90,44 +63,25 @@ public int compareTo(ActorId other) { /** * - * @param id1 - * @param id2 - * @return true if the two ActorId's are equal + * @return The hash code of this ActorId */ - static private boolean hasEqualContent(ActorId id1, ActorId id2) { - return id1.getStringId().equalsIgnoreCase(id2.getStringId()); + @Override + public int hashCode() { + return this.stringId.hashCode(); } /** - * - * @param id1 - * @param id2 + * Compare if the content of two ids are the same. + * @param id1 One identifier. + * @param id2 Another identifier. * @return -1, 0, or 1 depending on the compare result of the stringId member. */ private int compareContent(ActorId id1, ActorId id2) { - return id1.getStringId().compareToIgnoreCase(id2.getStringId()); - } - - /** - * - * @return The String representation of this ActorId - */ - @Override - public String toString() { - return this.stringId; + return id1.stringId.compareTo(id2.stringId); } /** - * - * @return The hash code of this ActorId - */ - @Override - public int hashCode() { - return this.stringId.hashCode(); - } - - /** - * + * Checks if this instance is equals to the other instance. * @return true if the 2 ActorId's are equal. */ @Override @@ -146,4 +100,42 @@ public boolean equals(Object obj) { return hasEqualContent(this, (ActorId) obj); } + + /** + * Creates a new ActorId with a random id. + * + * @return A new ActorId with a random id. + */ + public static ActorId createRandom() { + UUID id = UUID.randomUUID(); + return new ActorId(id.toString()); + } + + /** + * Determines whether two specified actorIds have the same id. + * + * @param id1 The first actorId to compare, or null + * @param id2 The second actorId to compare, or null. + * @return true if the id is same for both objects; otherwise, false. + */ + private static boolean equals(ActorId id1, ActorId id2) { + if (id1 == null && id2 == null) { + return true; + } else if (id2 == null || id1 == null) { + return false; + } else { + return hasEqualContent(id1, id2); + } + } + + /** + * Compares if two actors have the same content. + * + * @param id1 One identifier. + * @param id2 Another identifier. + * @return true if the two ActorId's are equal + */ + private static boolean hasEqualContent(ActorId id1, ActorId id2) { + return id1.stringId.equals(id2.stringId); + } } diff --git a/sdk/src/main/java/io/dapr/actors/ActorTrace.java b/sdk/src/main/java/io/dapr/actors/ActorTrace.java index 6ab8699932..487579e1af 100644 --- a/sdk/src/main/java/io/dapr/actors/ActorTrace.java +++ b/sdk/src/main/java/io/dapr/actors/ActorTrace.java @@ -9,23 +9,59 @@ import java.util.logging.Logger; // TODO: Implement distributed tracing. -// TODO: Make this generic to thw SDK and not only for Actors. +// TODO: Make this generic to the SDK and not only for Actors. + +/** + * Class to emit trace log messages. + */ public final class ActorTrace { + /** + * Gets the default Logger. + */ private static final Logger LOGGER = Logger.getLogger(ActorTrace.class.getName()); + /** + * Writes an information trace log. + * @param type Type of log. + * @param id Instance identifier. + * @param msgFormat Message or message format (with type and id input as well). + * @param params Params for the message. + */ public void writeInfo(String type, String id, String msgFormat, Object... params) { this.write(Level.INFO, type, id, msgFormat, params); } + /** + * Writes an warning trace log. + * @param type Type of log. + * @param id Instance identifier. + * @param msgFormat Message or message format (with type and id input as well). + * @param params Params for the message. + */ public void writeWarning(String type, String id, String msgFormat, Object... params) { this.write(Level.WARNING, type, id, msgFormat, params); } + /** + * Writes an error trace log. + * @param type Type of log. + * @param id Instance identifier. + * @param msgFormat Message or message format (with type and id input as well). + * @param params Params for the message. + */ public void writeError(String type, String id, String msgFormat, Object... params) { this.write(Level.SEVERE, type, id, msgFormat, params); } + /** + * Writes a trace log. + * @param level Severity level of the log. + * @param type Type of log. + * @param id Instance identifier. + * @param msgFormat Message or message format (with type and id input as well). + * @param params Params for the message. + */ private void write(Level level, String type, String id, String msgFormat, Object... params) { String formatString = String.format("%s:%s %s", emptyIfNul(type), emptyIfNul(id), emptyIfNul(msgFormat)); if ((params == null) || (params.length == 0)) { @@ -35,6 +71,11 @@ private void write(Level level, String type, String id, String msgFormat, Object } } + /** + * Utility method that returns empty if String is null. + * @param s String to be checked. + * @return String (if not null) or empty (if null). + */ private static String emptyIfNul(String s) { if (s == null) { return ""; diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 9a3521f263..c1462f029a 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -15,41 +15,84 @@ import java.util.Map; /** - * TODO - this is the base class Actor implementations (user code) will extend. + * Represents the base class for actors. + * + * The base type for actors, that provides the common functionality + * for actors that derive from {@link Actor}. + * The state is preserved across actor garbage collections and fail-overs. */ public abstract class AbstractActor { + /** + * Type of tracing messages. + */ private static final String TRACE_TYPE = "Actor"; + /** + * Context for the Actor runtime. + */ private final ActorRuntimeContext actorRuntimeContext; + /** + * Actor identifier. + */ private final ActorId id; - private final ActorStateManager actorStateManager; + /** + * Manager for the states in Actors. + */ + private final ActorStateManager actorStateManager; + /** + * Emits trace messages for Actors. + */ private final ActorTrace actorTrace; + /** + * Registered timers for this Actor. + */ private final Map> timers; + /** + * Instantiates a new Actor. + * @param runtimeContext Context for the runtime. + * @param id Actor identifier. + */ protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { this.actorRuntimeContext = runtimeContext; this.id = id; - this.actorStateManager = new ActorStateManager(runtimeContext.getActorTypeInformation().getName(), id); + this.actorStateManager = new ActorStateManager( + runtimeContext.getStateProvider(), + runtimeContext.getActorTypeInformation().getName(), + id); this.actorTrace = runtimeContext.getActorTrace(); this.timers = Collections.synchronizedMap(new HashMap<>()); } - protected Mono registerReminder( + /** + * Registers a reminder for this Actor. + * @param reminderName Name of the reminder. + * @param data Data to be send along with reminder triggers. + * @param dueTime Due time for the first trigger. + * @param period Frequency for the triggers. + * @return Asynchronous void response. + */ + protected Mono registerReminder( String reminderName, - S data, + String data, Duration dueTime, - Duration period) throws IOException { - String serialized = this.actorRuntimeContext.getActorSerializer().serialize(data); - return this.actorRuntimeContext.getDaprClient().registerReminder( - this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.getStringId(), - reminderName, - serialized); + Duration period) { + try { + ActorReminderParams params = new ActorReminderParams(data, dueTime, period); + String serialized = this.actorRuntimeContext.getActorSerializer().serialize(params); + return this.actorRuntimeContext.getDaprClient().registerReminder( + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.toString(), + reminderName, + serialized); + } catch (IOException e) { + return Mono.error(e); + } } /** @@ -65,92 +108,162 @@ protected Mono registerReminder( * Specify negative one (-1) milliseconds to disable periodic signaling. * @param Type for the state object. * @return Asynchronous result. - * @throws IOException in case cannot parse or register with Dapr. */ protected Mono registerActorTimer( String timerName, String methodName, S state, Duration dueTime, - Duration period) throws IOException { + Duration period) { String name = timerName; if ((timerName == null) || (timerName.isEmpty())) { - name = String.format("%s_Timer_%d", this.id.getStringId(), this.timers.size() + 1); + name = String.format("%s_Timer_%d", this.id.toString(), this.timers.size() + 1); } ActorTimer actorTimer = new ActorTimer(this, name, methodName, state, dueTime, period); - String serializedTimer = this.actorRuntimeContext.getActorSerializer().serialize(actorTimer); + String serializedTimer = null; + try { + serializedTimer = this.actorRuntimeContext.getActorSerializer().serialize(actorTimer); + } catch (IOException e) { + return Mono.error(e); + } this.timers.put(name, actorTimer); return this.actorRuntimeContext.getDaprClient().registerTimer( this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.getStringId(), + this.id.toString(), name, serializedTimer); } + /** + * Unregisters an Actor timer. + * @param actorTimer Timer to be unregistered. + * @return Asynchronous void response. + */ protected Mono unregister(ActorTimer actorTimer) { return this.actorRuntimeContext.getDaprClient().unregisterTimer( this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.getStringId(), + this.id.toString(), actorTimer.getName()) .then(this.onUnregisteredTimer(actorTimer)); } + /** + * Callback function invoked after an Actor has been activated. + * @return Asynchronous void response. + */ protected Mono onActivate() { return Mono.empty(); } + /** + * Callback function invoked after an Actor has been deactivated. + * @return Asynchronous void response. + */ protected Mono onDeactivate() { return Mono.empty(); } + /** + * Callback function invoked before method is invoked. + * @param actorMethodContext Method context. + * @return Asynchronous void response. + */ protected Mono onPreActorMethod(ActorMethodContext actorMethodContext) { return Mono.empty(); } + /** + * Callback function invoked after method is invoked. + * @param actorMethodContext Method context. + * @return Asynchronous void response. + */ protected Mono onPostActorMethod(ActorMethodContext actorMethodContext) { return Mono.empty(); } + /** + * Saves the state of this Actor. + * @return Asynchronous void response. + */ protected Mono saveState() { - return this.actorStateManager.saveState(); + return this.actorStateManager.save(); } - Mono resetState() { return this.actorStateManager.clearCache(); } + /** + * Resets the state of this Actor. + * @return Asynchronous void response. + */ + Mono resetState() { return this.actorStateManager.clear(); } + /** + * Gets a given timer by name. + * @param timerName Timer name. + * @return Asynchronous void response. + */ ActorTimer getActorTimer(String timerName) { return timers.getOrDefault(timerName, null); } + /** + * Internal callback when an Actor is activated. + * @return Asynchronous void response. + */ Mono onActivateInternal() { - this.actorTrace.writeInfo(TRACE_TYPE, this.id.getStringId(), "Activating ..."); + this.actorTrace.writeInfo(TRACE_TYPE, this.id.toString(), "Activating ..."); return this.resetState() .then(this.onActivate()) - .then(this.doWriteInfo(TRACE_TYPE, this.id.getStringId(), "Activated")) + .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Activated")) .then(this.saveState()); } + /** + * Internal callback when an Actor is deactivated. + * @return Asynchronous void response. + */ Mono onDeactivateInternal() { - this.actorTrace.writeInfo(TRACE_TYPE, this.id.getStringId(), "Deactivating ..."); + this.actorTrace.writeInfo(TRACE_TYPE, this.id.toString(), "Deactivating ..."); return this.resetState() .then(this.onDeactivate()) - .then(this.doWriteInfo(TRACE_TYPE, this.id.getStringId(), "Deactivated")) + .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Deactivated")) .then(this.saveState()); } + /** + * Internal callback prior to method be invoked. + * @param actorMethodContext Method context. + * @return Asynchronous void response. + */ Mono onPreActorMethodInternal(ActorMethodContext actorMethodContext) { return this.onPreActorMethod(actorMethodContext); } + /** + * Internal callback after method is invoked. + * @param actorMethodContext Method context. + * @return Asynchronous void response. + */ Mono onPostActorMethodInternal(ActorMethodContext actorMethodContext) { return this.onPostActorMethod(actorMethodContext) .then(this.saveState()); } + /** + * Internal callback for when Actor timer is unregistered. + * @param timer Timer being unregistered. + * @return Asynchronous void response. + */ Mono onUnregisteredTimer(ActorTimer timer) { this.timers.remove(timer.getName()); return Mono.empty(); } + /** + * Internal method to emit a trace message. + * @param type Type of trace message. + * @param id Identifier of entity relevant for the trace message. + * @param message Message to be logged. + * @return Asynchronous void response. + */ private Mono doWriteInfo(String type, String id, String message) { this.actorTrace.writeInfo(type, id, message); return Mono.empty(); diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java index 9e1e1f68b2..90cfa2a4be 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorManager.java @@ -1,7 +1,6 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.io.IOException; @@ -13,28 +12,50 @@ /** * Manages actors of a specific type. - * */ class ActorManager { + /** + * Context for the Actor runtime. + */ private final ActorRuntimeContext runtimeContext; + /** + * Methods found in Actors. + */ private final ActorMethodInfoMap actorMethods; + /** + * Active Actor instances. + */ private final Map activeActors; + /** + * Instantiates a new manager for a given actor referenced in the runtimeContext. + * @param runtimeContext Runtime context for the Actor. + */ ActorManager(ActorRuntimeContext runtimeContext) { this.runtimeContext = runtimeContext; this.actorMethods = new ActorMethodInfoMap(runtimeContext.getActorTypeInformation().getInterfaces()); this.activeActors = Collections.synchronizedMap(new HashMap<>()); } + /** + * Activates an Actor. + * @param actorId Actor identifier. + * @return Asynchronous void response. + */ Mono activateActor(ActorId actorId) { T actor = this.runtimeContext.getActorFactory().createActor(runtimeContext, actorId); return actor.onActivateInternal().then(this.onActivatedActor(actorId, actor)); } + /** + * Deactivates an Actor. + * @param actorId Actor identifier. + * @return Asynchronous void response. + */ Mono deactivateActor(ActorId actorId) { T actor = this.activeActors.remove(actorId); if (actor != null) { @@ -44,17 +65,31 @@ Mono deactivateActor(ActorId actorId) { return Mono.empty(); } + /** + * Invokes a given method in the Actor. + * @param actorId Identifier for Actor being invoked. + * @param methodName Name of method being invoked. + * @param request Input object for the method being invoked. + * @return Asynchronous void response. + */ Mono invokeMethod(ActorId actorId, String methodName, String request) { return invokeMethod(actorId, null, methodName, request); } + /** + * Invokes reminder for Actor. + * @param actorId Identifier for Actor being invoked. + * @param reminderName Name of reminder being invoked. + * @param request Input object for the reminder being invoked. + * @return Asynchronous void response. + */ Mono invokeReminder(ActorId actorId, String reminderName, String request) { if (!this.runtimeContext.getActorTypeInformation().isRemindable()) { return Mono.empty(); } try { - ActorReminderInfo reminder = this.runtimeContext.getActorSerializer().deserialize(request, ActorReminderInfo.class); + ActorReminderParams reminder = this.runtimeContext.getActorSerializer().deserialize(request, ActorReminderParams.class); return invoke( actorId, @@ -66,13 +101,19 @@ Mono invokeReminder(ActorId actorId, String reminderName, String request) } } + /** + * Invokes a timer for a given Actor. + * @param actorId Identifier for Actor. + * @param timerName Name of timer being invoked. + * @return Asynchronous void response. + */ Mono invokeTimer(ActorId actorId, String timerName) { try { AbstractActor actor = this.activeActors.getOrDefault(actorId, null); if (actor == null) { throw new IllegalArgumentException( String.format("Could not find actor %s of type %s.", - actorId.getStringId(), + actorId.toString(), this.runtimeContext.getActorTypeInformation().getName())); } @@ -95,15 +136,28 @@ Mono invokeTimer(ActorId actorId, String timerName) { } } + /** + * Internal callback for when Actor is activated. + * @param actorId Actor identifier. + * @param actor Actor's instance. + * @return Asynchronous void response. + */ private Mono onActivatedActor(ActorId actorId, T actor) { this.activeActors.put(actorId, actor); return Mono.empty(); } + /** + * Internal method to actually invoke a reminder. + * @param actor Actor that owns the reminder. + * @param reminderName Name of the reminder. + * @param reminderParams Params for the reminder. + * @return Asynchronous void response. + */ private Mono doReminderInvokation( Remindable actor, String reminderName, - ActorReminderInfo reminderParams) { + ActorReminderParams reminderParams) { try { Object data = this.runtimeContext.getActorSerializer().deserialize( reminderParams.getData(), @@ -118,6 +172,14 @@ private Mono doReminderInvokation( } } + /** + * Internal method to actually invoke Actor's method. + * @param actorId Identifier for the Actor. + * @param context Method context to be invoked. + * @param methodName Method name to be invoked. + * @param request Input object to be passed in to the invoked method. + * @return Asynchronous void response. + */ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, String methodName, Object request) { ActorMethodContext actorMethodContext = context; if (actorMethodContext == null) { @@ -126,12 +188,10 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S return this.invoke(actorId, actorMethodContext, actor -> { try { - Class clazz = this.runtimeContext.getActorTypeInformation().getImplementationClass(); - // Finds the actor method with the given name and 1 or no parameter. Method method = this.actorMethods.get(methodName); - Object response = null; + Object response; if (method.getParameterCount() == 0) { response = method.invoke(actor); @@ -172,22 +232,27 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S }).map(r -> r.toString()); } - private Mono invoke(ActorId actorId, ActorMethodContext context, Function> func) { + /** + * Internal call to invoke a method, timer or reminder for an Actor. + * @param actorId Actor identifier. + * @param context Context for the method/timer/reminder call. + * @param func Function to perform the method call. + * @param Expected return type for the function call. + * @return Asynchronous response for the returned object. + */ + private Mono invoke(ActorId actorId, ActorMethodContext context, Function> func) { try { AbstractActor actor = this.activeActors.getOrDefault(actorId, null); if (actor == null) { throw new IllegalArgumentException( String.format("Could not find actor %s of type %s.", - actorId.getStringId(), + actorId.toString(), this.runtimeContext.getActorTypeInformation().getName())); } - Mono preMethodCall = actor.onPreActorMethodInternal(context); - Mono methodCall = func.apply(actor); - Mono postMethodCall = actor.onPostActorMethodInternal(context); - - // TODO: find a way to make this generic and return Mono instead of Mono. - return Flux.concat(preMethodCall, methodCall, postMethodCall).singleOrEmpty(); + return actor.onPreActorMethodInternal(context).then( + func.apply(actor).flatMap(result -> actor.onPostActorMethodInternal(context).thenReturn(result)) + ); } catch (Exception e) { return Mono.error(e); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java index cc8b96404b..453518b1a1 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java @@ -15,6 +15,10 @@ class ActorMethodInfoMap { */ private final Map methods; + /** + * Instantiates a given Actor map based on the interfaces found in the class. + * @param interfaceTypes Interfaces found in the Actor class. + */ ActorMethodInfoMap(Collection> interfaceTypes) { Map methods = new HashMap<>(); @@ -33,6 +37,12 @@ class ActorMethodInfoMap { this.methods = Collections.unmodifiableMap(methods); } + /** + * Gets the Actor's method by name. + * @param methodName Name of the method. + * @return Method. + * @throws NoSuchMethodException If method is not found. + */ Method get(String methodName) throws NoSuchMethodException { Method method = this.methods.get(methodName); if (method == null) { diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java similarity index 50% rename from sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java rename to sdk/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java index 72ff97265b..2ac3e030a6 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderInfo.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java @@ -7,17 +7,38 @@ import java.time.Duration; -final class ActorReminderInfo { +/** + * Parameters for Actor Reminder. + */ +final class ActorReminderParams { + /** + * Minimum duration for period. + */ private static final Duration MIN_TIME_PERIOD = Duration.ofMillis(-1); + /** + * Data to be passed in as part of the reminder trigger. + */ private final String data; + /** + * Time the reminder is due for the 1st time. + */ private final Duration dueTime; + /** + * Interval between triggers. + */ private final Duration period; - ActorReminderInfo(String data, Duration dueTime, Duration period) { + /** + * Instantiates a new instance for the params of a reminder. + * @param data Data to be passed in as part of the reminder trigger. + * @param dueTime Time the reminder is due for the 1st time. + * @param period Interval between triggers. + */ + ActorReminderParams(String data, Duration dueTime, Duration period) { ValidateDueTime("DueTime", dueTime); ValidatePeriod("Period", period); this.data = data; @@ -25,18 +46,35 @@ final class ActorReminderInfo { this.period = period; } + /** + * Gets the time the reminder is due for the 1st time. + * @return Time the reminder is due for the 1st time. + */ Duration getDueTime() { return dueTime; } + /** + * Gets the interval between triggers. + * @return Interval between triggers. + */ Duration getPeriod() { return period; } + /** + * Gets the data to be passed in as part of the reminder trigger. + * @return Data to be passed in as part of the reminder trigger. + */ String getData() { return data; } + /** + * Validates due time is valid, throws {@link IllegalArgumentException}. + * @param argName Name of the argument passed in. + * @param value Vale being checked. + */ private static void ValidateDueTime(String argName, Duration value) { if (value.compareTo(Duration.ZERO) < 0) { String message = String.format( @@ -45,10 +83,15 @@ private static void ValidateDueTime(String argName, Duration value) { } } + /** + * Validates reminder period is valid, throws {@link IllegalArgumentException}. + * @param argName Name of the argument passed in. + * @param value Vale being checked. + */ private static void ValidatePeriod(String argName, Duration value) throws IllegalArgumentException { if (value.compareTo(MIN_TIME_PERIOD) < 0) { String message = String.format( - "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, MIN_TIME_PERIOD); throw new IllegalArgumentException(message); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java index f04eed5fa9..4436e41226 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -125,7 +125,8 @@ public Mono registerActor(Class clazz, ActorF this.actorSerializer, actualActorFactory, actorTypeInfo, - this.appToDaprAsyncClient); + this.appToDaprAsyncClient, + new DaprStateAsyncProvider(this.appToDaprAsyncClient, this.actorSerializer)); // Create ActorManagers, override existing entry if registered again. this.actorManagers.put(actorTypeInfo.getName(), new ActorManager(context)); diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java index 02fdd04641..b61fa7c0ec 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java @@ -7,50 +7,117 @@ import io.dapr.actors.ActorTrace; +/** + * Provides the context for the Actor's runtime. + * @param Actor's type for the context. + */ public class ActorRuntimeContext { + /** + * Runtime. + */ private final ActorRuntime actorRuntime; + /** + * Serializer. + */ private final ActorStateSerializer actorSerializer; + /** + * Actor factory. + */ private final ActorFactory actorFactory; + /** + * Information of the Actor's type. + */ private final ActorTypeInformation actorTypeInformation; + /** + * Trace for Actor logs. + */ private final ActorTrace actorTrace; + /** + * Client to communicate to Dapr's API. + */ private final AppToDaprAsyncClient daprClient; + /** + * State provider for given Actor Type. + */ + private final DaprStateAsyncProvider stateProvider; + + /** + * Instantiates a new runtime context for the Actor type. + * @param actorRuntime Runtime. + * @param actorSerializer Serializer. + * @param actorFactory Factory for Actors. + * @param actorTypeInformation Information for Actor's type. + * @param daprClient Client to communicate to Dapr. + * @param stateProvider State provider for given Actor's type. + */ ActorRuntimeContext(ActorRuntime actorRuntime, ActorStateSerializer actorSerializer, ActorFactory actorFactory, ActorTypeInformation actorTypeInformation, - AppToDaprAsyncClient daprClient) { + AppToDaprAsyncClient daprClient, DaprStateAsyncProvider stateProvider) { this.actorRuntime = actorRuntime; this.actorSerializer = actorSerializer; this.actorFactory = actorFactory; this.actorTypeInformation = actorTypeInformation; this.actorTrace = new ActorTrace(); this.daprClient = daprClient; + this.stateProvider = stateProvider; } + /** + * Gets the Actor's runtime. + * @return Actor's runtime. + */ ActorRuntime getActorRuntime() { return this.actorRuntime; } + /** + * Gets the Actor's serializer. + * @return Actor's serializer. + */ ActorStateSerializer getActorSerializer() { return this.actorSerializer; } + /** + * Gets the Actor's serializer. + * @return Actor's serializer. + */ ActorFactory getActorFactory() { return this.actorFactory; } + /** + * Gets the information about the Actor's type. + * @return Information about the Actor's type. + */ ActorTypeInformation getActorTypeInformation() { return this.actorTypeInformation; } + /** + * Gets the trace for Actor logs. + * @return Trace for Actor logs. + */ ActorTrace getActorTrace() { return this.actorTrace; } + /** + * Gets the client to communicate to Dapr's API. + * @return Client to communicate to Dapr's API. + */ AppToDaprAsyncClient getDaprClient() { return this.daprClient; } + + /** + * Gets the state provider for given Actor's type. + * @return State provider for given Actor's type. + */ + DaprStateAsyncProvider getStateProvider() { return stateProvider; } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java index 5430ebbefa..bbb5ef52be 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java @@ -7,9 +7,8 @@ /** * Represents a state change for an actor. - * @param Type of the value being changed. */ -public final class ActorStateChange { +public final class ActorStateChange { /** * Name of the state being changed. @@ -19,7 +18,7 @@ public final class ActorStateChange { /** * New value for the state being changed. */ - private final T value; + private final Object value; /** * Type of change {@link ActorStateChangeKind}. @@ -32,7 +31,7 @@ public final class ActorStateChange { * @param value New value for the state being changed. * @param changeKind Kind of change. */ - ActorStateChange(String stateName, T value, ActorStateChangeKind changeKind) { + ActorStateChange(String stateName, Object value, ActorStateChangeKind changeKind) { this.stateName = stateName; this.value = value; this.changeKind = changeKind; @@ -50,7 +49,7 @@ String getStateName() { * Gets the new value of the state being changed. * @return New value. */ - T getValue() { + Object getValue() { return value; } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java index 902aa70aa6..165aa7f511 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateManager.java @@ -8,23 +8,300 @@ import io.dapr.actors.ActorId; import reactor.core.publisher.Mono; -// TODO -class ActorStateManager { +import java.util.*; +/** + * Manages state changes of a given Actor instance. + * + * All changes are cached in-memory until save() is called. + */ +class ActorStateManager { + + /** + * Provides states using a state store. + */ + private final DaprStateAsyncProvider stateProvider; + + /** + * Name of the Actor's type. + */ private final String actorTypeName; + /** + * Actor's identifier. + */ private final ActorId actorId; - ActorStateManager(String actorTypeName, ActorId actorId) { + /** + * Cache of state changes in this Actor's instance. + */ + private final Map stateChangeTracker; + + /** + * Instantiates a new state manager for the given Actor's instance. + * @param stateProvider State store provider. + * @param actorTypeName Name of Actor's type. + * @param actorId Actor's identifier. + */ + ActorStateManager(DaprStateAsyncProvider stateProvider, String actorTypeName, ActorId actorId) { + this.stateProvider = stateProvider; this.actorTypeName = actorTypeName; this.actorId = actorId; + this.stateChangeTracker = new HashMap<>(); + } + + /** + * Adds a given key/value to the Actor's state store's cache. + * @param stateName Name of the state being added. + * @param value Value to be added. + * @param Type of the object being added. + * @return Asynchronous void operation. + */ + Mono add(String stateName, T value) { + try { + if (stateName == null) { + throw new IllegalArgumentException("State's name cannot be null."); + } + + if (this.stateChangeTracker.containsKey(stateName)) { + StateChangeMetadata metadata = this.stateChangeTracker.get(stateName); + + if (metadata.kind == ActorStateChangeKind.REMOVE) { + this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.UPDATE, value)); + return Mono.empty(); + } + + throw new IllegalStateException("Duplicate cached state: " + stateName); + } + + return this.stateProvider.contains(this.actorTypeName, this.actorId, stateName) + .flatMap(exists -> { + if (exists) { + throw new IllegalStateException("Duplicate state: " + stateName); + } + + this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.ADD, value)); + return Mono.empty(); + }); + } catch (Exception e) { + return Mono.error(e); + } + } + + /** + * Fetches the most recent value for the given state, including cached value. + * @param stateName Name of the state. + * @param clazz Class type for the value being fetched. + * @param Type being fetched. + * @return Asynchronous response with fetched object. + */ + Mono get(String stateName, Class clazz) { + try { + if (stateName == null) { + throw new IllegalArgumentException("State's name cannot be null."); + } + + if (this.stateChangeTracker.containsKey(stateName)) { + StateChangeMetadata metadata = this.stateChangeTracker.get(stateName); + + if (metadata.kind == ActorStateChangeKind.REMOVE) { + throw new NoSuchElementException("State is marked for removal: " + stateName); + } + + return Mono.just((T) metadata.value); + } + + return this.stateProvider.load(this.actorTypeName, this.actorId, stateName, clazz) + .switchIfEmpty(Mono.error(new NoSuchElementException("State not found: " + stateName))) + .map(v -> { + this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.REMOVE, v)); + return (T)v; + }); + } catch (Exception e) { + return Mono.error(e); + } + } + + /** + * Updates a given key/value pair in the state store's cache. + * @param stateName Name of the state being updated. + * @param value Value to be set for given state. + * @param Type of the value being set. + * @return Asynchronous void result. + */ + Mono set(String stateName, T value) { + try { + if (stateName == null) { + throw new IllegalArgumentException("State's name cannot be null."); + } + + if (this.stateChangeTracker.containsKey(stateName)) { + StateChangeMetadata metadata = this.stateChangeTracker.get(stateName); + + ActorStateChangeKind kind = metadata.kind; + if ((kind == ActorStateChangeKind.NONE) || (kind == ActorStateChangeKind.REMOVE)) { + kind = ActorStateChangeKind.UPDATE; + } + + this.stateChangeTracker.put(stateName, new StateChangeMetadata(kind, value)); + return Mono.empty(); + } + + return this.stateProvider.contains(this.actorTypeName, this.actorId, stateName) + .map(exists -> { + this.stateChangeTracker.put(stateName, + new StateChangeMetadata(exists ? ActorStateChangeKind.UPDATE : ActorStateChangeKind.ADD, value)); + return exists; + }) + .then(); + } catch (Exception e) { + return Mono.error(e); + } + } + + /** + * Removes a given state from state store's cache. + * @param stateName State being stored. + * @return Asynchronous void result. + */ + Mono remove(String stateName) { + try { + if (stateName == null) { + throw new IllegalArgumentException("State's name cannot be null."); + } + + if (this.stateChangeTracker.containsKey(stateName)) { + StateChangeMetadata metadata = this.stateChangeTracker.get(stateName); + + if (metadata.kind == ActorStateChangeKind.REMOVE) { + return Mono.empty(); + } + + if (metadata.kind == ActorStateChangeKind.ADD) { + this.stateChangeTracker.remove(stateName); + return Mono.empty(); + } + + this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.REMOVE, null)); + return Mono.empty(); + } + + return this.stateProvider.contains(this.actorTypeName, this.actorId, stateName) + .filter(exists -> exists) + .map(exists -> { + this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.REMOVE, null)); + return exists; + }) + .then(); + } catch (Exception e) { + return Mono.error(e); + } + } + + /** + * Checks if a given state exists in state store or cache. + * @param stateName State being checked. + * @return Asynchronous boolean result indicating whether state is present. + */ + Mono contains(String stateName) { + try { + if (stateName == null) { + throw new IllegalArgumentException("State's name cannot be null."); + } + + if (this.stateChangeTracker.containsKey(stateName)) { + StateChangeMetadata metadata = this.stateChangeTracker.get(stateName); + + if (metadata.kind == ActorStateChangeKind.REMOVE) { + return Mono.just(false); + } + + return Mono.just(true); + } + + return this.stateProvider.contains(this.actorTypeName, this.actorId, stateName); + } catch (Exception e) { + return Mono.error(e); + } } - Mono saveState() { + /** + * Saves all changes to state store. + * @return Asynchronous void result. + */ + Mono save() { + if (this.stateChangeTracker.isEmpty()) { + return Mono.empty(); + } + + List changes = new ArrayList<>(); + List removed = new ArrayList<>(); + for (Map.Entry tuple : this.stateChangeTracker.entrySet()) { + if (tuple.getValue().kind == ActorStateChangeKind.NONE) { + continue; + } + + if (tuple.getValue().kind == ActorStateChangeKind.REMOVE) { + removed.add(tuple.getKey()); + } + + changes.add(new ActorStateChange(tuple.getKey(), tuple.getValue().value, tuple.getValue().kind)); + } + + return this.stateProvider.apply(this.actorTypeName, this.actorId, changes.toArray(new ActorStateChange[0])) + .then(this.flush()); + } + + /** + * Clears all changes not yet saved to state store. + * @return + */ + Mono clear() { + this.stateChangeTracker.clear(); return Mono.empty(); } - public Mono clearCache() { + /** + * Commits the current cached values after successful save. + * @return + */ + private Mono flush() { + for (Map.Entry tuple : this.stateChangeTracker.entrySet()) { + String stateName = tuple.getKey(); + if (tuple.getValue().kind == ActorStateChangeKind.REMOVE) { + this.stateChangeTracker.remove(stateName); + } else { + StateChangeMetadata metadata = new StateChangeMetadata(ActorStateChangeKind.NONE, tuple.getValue().value); + this.stateChangeTracker.put(stateName, metadata); + } + } + return Mono.empty(); } + + /** + * Internal class to represent value and change kind. + */ + private static final class StateChangeMetadata { + + /** + * Kind of change cached. + */ + private final ActorStateChangeKind kind; + + /** + * Value cached. + */ + private final Object value; + + /** + * Creates a new instance of the metadata on state change. + * @param kind Kind of change. + * @param value Value to be set. + */ + private StateChangeMetadata(ActorStateChangeKind kind, Object value) { + this.kind = kind; + this.value = value; + } + } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java index c2d12cd510..0aa99b7075 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java @@ -50,9 +50,9 @@ String serialize(T state) throws IOException { return serialize((ActorTimer) state); } - if (state.getClass() == ActorReminderInfo.class) { + if (state.getClass() == ActorReminderParams.class) { // Special serializer for this internal classes. - return serialize((ActorReminderInfo) state); + return serialize((ActorReminderParams) state); } if (isPrimitiveOrEquivalent(state.getClass())) { @@ -77,9 +77,9 @@ T deserialize(String value, Class clazz) throws IOException { return (T) value; } - if (clazz == ActorReminderInfo.class) { + if (clazz == ActorReminderParams.class) { // Special serializer for this internal classes. - return (T) deserialize(value); + return (T) deserializeActorReminder(value); } if (isPrimitiveOrEquivalent(clazz)) { @@ -94,6 +94,11 @@ T deserialize(String value, Class clazz) throws IOException { return OBJECT_MAPPER.readValue(value, clazz); } + /** + * Checks if the class is a primitive or equivalent. + * @param clazz Class to be checked. + * @return True if primitive or equivalent. + */ private static boolean isPrimitiveOrEquivalent(Class clazz) { if (clazz == null) { return false; @@ -111,6 +116,13 @@ private static boolean isPrimitiveOrEquivalent(Class clazz) { (clazz == Void.class)); } + /** + * Parses a given String to the corresponding object defined by class. + * @param value String to be parsed. + * @param clazz Class of the expected result type. + * @param Result type. + * @return Result as corresponding type. + */ private static T parse(String value, Class clazz) { if (value == null) { if (boolean.class == clazz) return (T) Boolean.FALSE; @@ -135,12 +147,18 @@ private static T parse(String value, Class clazz) { return null; } + /** + * Faster serialization for Actor's timer. + * @param timer Timer to be serialized. + * @return JSON String. + * @throws IOException If cannot generate JSON. + */ private static String serialize(ActorTimer timer) throws IOException { try (Writer writer = new StringWriter()) { JsonGenerator generator = JSON_FACTORY.createGenerator(writer); generator.writeStartObject(); - generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(timer.getDueTime())); - generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(timer.getPeriod())); + generator.writeStringField("dueTime", DurationUtils.ConvertDurationToDaprFormat(timer.getDueTime())); + generator.writeStringField("period", DurationUtils.ConvertDurationToDaprFormat(timer.getPeriod())); generator.writeEndObject(); generator.close(); writer.flush(); @@ -148,12 +166,18 @@ private static String serialize(ActorTimer timer) throws IOException { } } - private static String serialize(ActorReminderInfo reminder) throws IOException { + /** + * Faster serialization for Actor's reminder. + * @param reminder Reminder to be serialized. + * @return JSON String. + * @throws IOException If cannot generate JSON. + */ + private static String serialize(ActorReminderParams reminder) throws IOException { try (Writer writer = new StringWriter()) { JsonGenerator generator = JSON_FACTORY.createGenerator(writer); generator.writeStartObject(); - generator.writeStringField("dueTime", ConverterUtils.ConvertDurationToDaprFormat(reminder.getDueTime())); - generator.writeStringField("period", ConverterUtils.ConvertDurationToDaprFormat(reminder.getPeriod())); + generator.writeStringField("dueTime", DurationUtils.ConvertDurationToDaprFormat(reminder.getDueTime())); + generator.writeStringField("period", DurationUtils.ConvertDurationToDaprFormat(reminder.getPeriod())); if (reminder.getData() != null) { generator.writeStringField("data", reminder.getData()); } @@ -164,16 +188,22 @@ private static String serialize(ActorReminderInfo reminder) throws IOException { } } - private static ActorReminderInfo deserialize(String value) throws IOException { + /** + * Deserializes an Actor Reminder. + * @param value String to be deserialized. + * @return Actor Reminder. + * @throws IOException If cannot parse JSON. + */ + private static ActorReminderParams deserializeActorReminder(String value) throws IOException { if (value == null) { return null; } JsonNode node = OBJECT_MAPPER.readTree(value); - Duration dueTime = ConverterUtils.ConvertDurationFromDaprFormat(node.get("dueTime").asText()); - Duration period = ConverterUtils.ConvertDurationFromDaprFormat(node.get("period").asText()); + Duration dueTime = DurationUtils.ConvertDurationFromDaprFormat(node.get("dueTime").asText()); + Duration period = DurationUtils.ConvertDurationFromDaprFormat(node.get("period").asText()); String data = node.get("data") != null ? node.get("data").asText() : null; - return new ActorReminderInfo(data, dueTime, period); + return new ActorReminderParams(data, dueTime, period); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java index 72d5f2713d..b450b759b9 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java @@ -14,6 +14,10 @@ @Retention(RetentionPolicy.RUNTIME) public @interface ActorType { + /** + * Overrides Actor's name. + * @return Actor's name. + */ String Name(); } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java b/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java index 8d9212e756..8a0136cbc8 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import io.dapr.actors.ActorId; import reactor.core.publisher.Mono; import java.io.IOException; @@ -32,8 +33,8 @@ class DaprStateAsyncProvider { this.serializer = serializer; } - Mono load(String actorType, String actorId, String stateName, Class clazz) { - Mono result = this.daprAsyncClient.getState(actorType, actorId, stateName); + Mono load(String actorType, ActorId actorId, String stateName, Class clazz) { + Mono result = this.daprAsyncClient.getState(actorType, actorId.toString(), stateName); return result .filter(s -> (s != null) && (!s.isEmpty())) @@ -46,8 +47,8 @@ Mono load(String actorType, String actorId, String stateName, Class cl }); } - Mono contains(String actorType, String actorId, String stateName) { - Mono result = this.daprAsyncClient.getState(actorType, actorId, stateName); + Mono contains(String actorType, ActorId actorId, String stateName) { + Mono result = this.daprAsyncClient.getState(actorType, actorId.toString(), stateName); return result.map(s -> { return (s != null) && (s.length() > 0); @@ -76,7 +77,7 @@ Mono contains(String actorType, String actorId, String stateName) { * @param stateChanges Collection of changes to be performed transactionally. * @return Void. */ - Mono apply(String actorType, String actorId, ActorStateChange... stateChanges) + Mono apply(String actorType, ActorId actorId, ActorStateChange... stateChanges) { if ((stateChanges == null) || stateChanges.length == 0) { return Mono.empty(); @@ -135,6 +136,6 @@ Mono apply(String actorType, String actorId, ActorStateChange... stateChan Mono.empty(); } - return this.daprAsyncClient.saveStateTransactionally(actorType, actorId, payload); + return this.daprAsyncClient.saveStateTransactionally(actorType, actorId.toString(), payload); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java b/sdk/src/main/java/io/dapr/actors/runtime/DurationUtils.java similarity index 94% rename from sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java rename to sdk/src/main/java/io/dapr/actors/runtime/DurationUtils.java index 4446c78ae0..d12d2d0e8f 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ConverterUtils.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/DurationUtils.java @@ -7,7 +7,7 @@ import java.time.Duration; -public class ConverterUtils { +public class DurationUtils { /** * Converts time from the String format used by Dapr into a Duration. @@ -84,8 +84,8 @@ public static String ConvertDurationToDaprFormat(Duration value) { /** * Helper to get the "days" part of the Duration. For example if the duration is 26 hours, this returns 1. * - * @param d - * @return + * @param d Duration + * @return Number of days. */ static long getDaysPart(Duration d) { long t = d.getSeconds() / 60 / 60 / 24; @@ -95,7 +95,7 @@ static long getDaysPart(Duration d) { /** * Helper to get the "hours" part of the Duration. For example if the duration is 26 hours, this is 1 day, 2 hours, so this returns 2. * - * @param The duration to parse + * @param d The duration to parse * @return the hour part of the duration */ static long getHoursPart(Duration d) { @@ -107,7 +107,7 @@ static long getHoursPart(Duration d) { /** * Helper to get the "minutes" part of the Duration. * - * @param The duration to parse + * @param d The duration to parse * @return the minutes part of the duration */ static long getMinutesPart(Duration d) { @@ -119,7 +119,7 @@ static long getMinutesPart(Duration d) { /** * Helper to get the "seconds" part of the Duration. * - * @param The duration to parse + * @param d The duration to parse * @return the seconds part of the duration */ static long getSecondsPart(Duration d) { @@ -131,7 +131,7 @@ static long getSecondsPart(Duration d) { /** * Helper to get the "millis" part of the Duration. * - * @param The duration to parse + * @param d The duration to parse * @return the milliseconds part of the duration */ static long getMilliSecondsPart(Duration d) { diff --git a/sdk/src/test/java/io/dapr/actors/ActorIdTest.java b/sdk/src/test/java/io/dapr/actors/ActorIdTest.java index fba941450d..ab62f0e33b 100644 --- a/sdk/src/test/java/io/dapr/actors/ActorIdTest.java +++ b/sdk/src/test/java/io/dapr/actors/ActorIdTest.java @@ -23,7 +23,7 @@ public void initializeNewActorIdObjectWithNullId() { public void getId() { String id = "123"; ActorId actorId = new ActorId(id); - Assert.assertEquals(id, actorId.getStringId()); + Assert.assertEquals(id, actorId.toString()); } @Test diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java similarity index 64% rename from sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java rename to sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java index 88e22dcb48..666d3b787b 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderInfoTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java @@ -4,33 +4,33 @@ import org.junit.Test; import java.time.Duration; -public class ActorReminderInfoTest { +public class ActorReminderParamsTest { private static final ActorStateSerializer SERIALIZER = new ActorStateSerializer(); @Test(expected = IllegalArgumentException.class) public void outOfRangeDueTime() { - ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusSeconds(-10), Duration.ZERO.plusMinutes(1)); + ActorReminderParams info = new ActorReminderParams(null, Duration.ZERO.plusSeconds(-10), Duration.ZERO.plusMinutes(1)); } @Test public void negativePeriod() { // this is ok - ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMillis(-1)); + ActorReminderParams info = new ActorReminderParams(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMillis(-1)); } @Test(expected = IllegalArgumentException.class) public void outOfRangePeriod() { - ActorReminderInfo info = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMinutes(-10)); + ActorReminderParams info = new ActorReminderParams(null, Duration.ZERO.plusMinutes(1), Duration.ZERO.plusMinutes(-10)); } @Test public void noState() { - ActorReminderInfo original = new ActorReminderInfo(null, Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); - ActorReminderInfo recreated = null; + ActorReminderParams original = new ActorReminderParams(null, Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); + ActorReminderParams recreated = null; try { String serialized = SERIALIZER.serialize(original); - recreated = SERIALIZER.deserialize(serialized, ActorReminderInfo.class); + recreated = SERIALIZER.deserialize(serialized, ActorReminderParams.class); } catch(Exception e) { System.out.println("The error is: " + e); @@ -44,11 +44,11 @@ public void noState() { @Test public void withState() { - ActorReminderInfo original = new ActorReminderInfo("maru", Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); - ActorReminderInfo recreated = null; + ActorReminderParams original = new ActorReminderParams("maru", Duration.ZERO.plusMinutes(2), Duration.ZERO.plusMinutes((5))); + ActorReminderParams recreated = null; try { String serialized = SERIALIZER.serialize(original); - recreated = SERIALIZER.deserialize(serialized, ActorReminderInfo.class); + recreated = SERIALIZER.deserialize(serialized, ActorReminderParams.class); } catch(Exception e) { System.out.println("The error is: " + e); diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java index 81d16a6f49..2123d30733 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.actors.ActorId; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.Mono; @@ -24,225 +25,225 @@ */ public class DaprStateAsyncProviderTest { - private static final ActorStateSerializer SERIALIZER = new ActorStateSerializer(); + private static final ActorStateSerializer SERIALIZER = new ActorStateSerializer(); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final double EPSILON = 1e-10; + private static final double EPSILON = 1e-10; - /** - * Class used to test JSON serialization. - */ - public static final class Customer { + /** + * Class used to test JSON serialization. + */ + public static final class Customer { - private int id; + private int id; - private String name; - - public int getId() { - return id; - } - - public Customer setId(int id) { - this.id = id; - return this; - } - - public String getName() { - return name; - } - - public Customer setName(String name) { - this.name = name; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Customer customer = (Customer) o; - return id == customer.id && - Objects.equals(name, customer.name); - } - - @Override - public int hashCode() { - return Objects.hash(id, name); - } + private String name; + public int getId() { + return id; } - @Test - public void happyCaseApply() { - AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); - when(daprAsyncClient - .saveStateTransactionally( - eq("MyActor"), - eq("123"), - argThat(s -> { - try { - JsonNode node = OBJECT_MAPPER.readTree(s); - if (node == null) { - return false; - } - - if (node.size() != 3) { - return false; - } - - boolean foundInsertName = false; - boolean foundUpdateZipcode = false; - boolean foundDeleteFlag = false; - for (JsonNode operation : node) { - if (operation.get("operation") == null) { - return false; - } - if (operation.get("request") == null) { - return false; - } - - String opName = operation.get("operation").asText(); - String key = operation.get("request").get("key").asText(); - JsonNode valueNode = operation.get("request").get("value"); - - foundInsertName |= "upsert".equals(opName) && - "name".equals(key) && - "Jon Doe".equals(valueNode.asText()); - foundUpdateZipcode |= "upsert".equals(opName) && - "zipcode".equals(key) && - "98011".equals(valueNode.asText()); - foundDeleteFlag |= "delete".equals(opName) && - "flag".equals(key) && - (valueNode == null); - } - - return foundInsertName && foundUpdateZipcode && foundDeleteFlag; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - }))) - .thenReturn(Mono.empty()); - - DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); - provider.apply("MyActor", - "123", - createInsertChange("name", "Jon Doe"), - createUpdateChange("zipcode", "98011"), - createDeleteChange("flag")) - .block(); - - verify(daprAsyncClient).saveStateTransactionally(eq("MyActor"), eq("123"), any()); + public Customer setId(int id) { + this.id = id; + return this; } - @Test - public void happyCaseLoad() { - AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); - when(daprAsyncClient - .getState(any(), any(), eq("name"))) - .thenReturn(Mono.just("Jon Doe")); - when(daprAsyncClient - .getState(any(), any(), eq("zipcode"))) - .thenReturn(Mono.just("98021")); - when(daprAsyncClient - .getState(any(), any(), eq("goals"))) - .thenReturn(Mono.just("98")); - when(daprAsyncClient - .getState(any(), any(), eq("balance"))) - .thenReturn(Mono.just("46.55")); - when(daprAsyncClient - .getState(any(), any(), eq("active"))) - .thenReturn(Mono.just("true")); - when(daprAsyncClient - .getState(any(), any(), eq("customer"))) - .thenReturn(Mono.just("{ \"id\": 1000, \"name\": \"Roxane\"}")); - when(daprAsyncClient - .getState(any(), any(), eq("anotherCustomer"))) - .thenReturn(Mono.just("{ \"id\": 2000, \"name\": \"Max\"}")); - when(daprAsyncClient - .getState(any(), any(), eq("nullCustomer"))) - .thenReturn(Mono.just("")); - - DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); - - Assert.assertEquals("Jon Doe", - provider.load("MyActor", "123", "name", String.class).block()); - Assert.assertEquals("98021", - provider.load("MyActor", "123", "zipcode", String.class).block()); - Assert.assertEquals(98, - (int) provider.load("MyActor", "123", "goals", int.class).block()); - Assert.assertEquals(98, - (int) provider.load("MyActor", "123", "goals", int.class).block()); - Assert.assertEquals(46.55, - (double) provider.load("MyActor", "123", "balance", double.class).block(), - EPSILON); - Assert.assertEquals(true, - (boolean) provider.load("MyActor", "123", "active", boolean.class).block()); - Assert.assertEquals(new Customer().setId(1000).setName("Roxane"), - provider.load("MyActor", "123", "customer", Customer.class).block()); - Assert.assertNotEquals(new Customer().setId(1000).setName("Roxane"), - provider.load("MyActor", "123", "anotherCustomer", Customer.class).block()); - Assert.assertNull(provider.load("MyActor", "123", "nullCustomer", Customer.class).block()); + public String getName() { + return name; } - @Test - public void happyCaseContains() { - AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); - - // Keys that exists. - when(daprAsyncClient - .getState(any(), any(), eq("name"))) - .thenReturn(Mono.just("Jon Doe")); - when(daprAsyncClient - .getState(any(), any(), eq("zipcode"))) - .thenReturn(Mono.just("98021")); - when(daprAsyncClient - .getState(any(), any(), eq("goals"))) - .thenReturn(Mono.just("98")); - when(daprAsyncClient - .getState(any(), any(), eq("balance"))) - .thenReturn(Mono.just("46.55")); - when(daprAsyncClient - .getState(any(), any(), eq("active"))) - .thenReturn(Mono.just("true")); - when(daprAsyncClient - .getState(any(), any(), eq("customer"))) - .thenReturn(Mono.just("{ \"id\": \"3000\", \"name\": \"Ely\" }")); - - // Keys that do not exist. - when(daprAsyncClient - .getState(any(), any(), eq("Does not exist"))) - .thenReturn(Mono.just("")); - when(daprAsyncClient - .getState(any(), any(), eq("NAME"))) - .thenReturn(Mono.just("")); - when(daprAsyncClient - .getState(any(), any(), eq(null))) - .thenReturn(Mono.just("")); - - DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); - - Assert.assertTrue(provider.contains("MyActor", "123", "name").block()); - Assert.assertFalse(provider.contains("MyActor", "123", "NAME").block()); - Assert.assertTrue(provider.contains("MyActor", "123", "zipcode").block()); - Assert.assertTrue(provider.contains("MyActor", "123", "goals").block()); - Assert.assertTrue(provider.contains("MyActor", "123", "balance").block()); - Assert.assertTrue(provider.contains("MyActor", "123", "active").block()); - Assert.assertTrue(provider.contains("MyActor", "123", "customer").block()); - Assert.assertFalse(provider.contains("MyActor", "123", "Does not exist").block()); - Assert.assertFalse(provider.contains("MyActor", "123", null).block()); + public Customer setName(String name) { + this.name = name; + return this; } - private final ActorStateChange createInsertChange(String name, T value) { - return new ActorStateChange(name, value, ActorStateChangeKind.ADD); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Customer customer = (Customer) o; + return id == customer.id && + Objects.equals(name, customer.name); } - private final ActorStateChange createUpdateChange(String name, T value) { - return new ActorStateChange(name, value, ActorStateChangeKind.UPDATE); + @Override + public int hashCode() { + return Objects.hash(id, name); } - private final ActorStateChange createDeleteChange(String name) { - return new ActorStateChange(name, null, ActorStateChangeKind.REMOVE); - } + } + + @Test + public void happyCaseApply() { + AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); + when(daprAsyncClient + .saveStateTransactionally( + eq("MyActor"), + eq("123"), + argThat(s -> { + try { + JsonNode node = OBJECT_MAPPER.readTree(s); + if (node == null) { + return false; + } + + if (node.size() != 3) { + return false; + } + + boolean foundInsertName = false; + boolean foundUpdateZipcode = false; + boolean foundDeleteFlag = false; + for (JsonNode operation : node) { + if (operation.get("operation") == null) { + return false; + } + if (operation.get("request") == null) { + return false; + } + + String opName = operation.get("operation").asText(); + String key = operation.get("request").get("key").asText(); + JsonNode valueNode = operation.get("request").get("value"); + + foundInsertName |= "upsert".equals(opName) && + "name".equals(key) && + "Jon Doe".equals(valueNode.asText()); + foundUpdateZipcode |= "upsert".equals(opName) && + "zipcode".equals(key) && + "98011".equals(valueNode.asText()); + foundDeleteFlag |= "delete".equals(opName) && + "flag".equals(key) && + (valueNode == null); + } + + return foundInsertName && foundUpdateZipcode && foundDeleteFlag; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + }))) + .thenReturn(Mono.empty()); + + DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); + provider.apply("MyActor", + new ActorId("123"), + createInsertChange("name", "Jon Doe"), + createUpdateChange("zipcode", "98011"), + createDeleteChange("flag")) + .block(); + + verify(daprAsyncClient).saveStateTransactionally(eq("MyActor"), eq("123"), any()); + } + + @Test + public void happyCaseLoad() { + AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); + when(daprAsyncClient + .getState(any(), any(), eq("name"))) + .thenReturn(Mono.just("Jon Doe")); + when(daprAsyncClient + .getState(any(), any(), eq("zipcode"))) + .thenReturn(Mono.just("98021")); + when(daprAsyncClient + .getState(any(), any(), eq("goals"))) + .thenReturn(Mono.just("98")); + when(daprAsyncClient + .getState(any(), any(), eq("balance"))) + .thenReturn(Mono.just("46.55")); + when(daprAsyncClient + .getState(any(), any(), eq("active"))) + .thenReturn(Mono.just("true")); + when(daprAsyncClient + .getState(any(), any(), eq("customer"))) + .thenReturn(Mono.just("{ \"id\": 1000, \"name\": \"Roxane\"}")); + when(daprAsyncClient + .getState(any(), any(), eq("anotherCustomer"))) + .thenReturn(Mono.just("{ \"id\": 2000, \"name\": \"Max\"}")); + when(daprAsyncClient + .getState(any(), any(), eq("nullCustomer"))) + .thenReturn(Mono.just("")); + + DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); + + Assert.assertEquals("Jon Doe", + provider.load("MyActor", new ActorId("123"), "name", String.class).block()); + Assert.assertEquals("98021", + provider.load("MyActor", new ActorId("123"), "zipcode", String.class).block()); + Assert.assertEquals(98, + (int) provider.load("MyActor", new ActorId("123"), "goals", int.class).block()); + Assert.assertEquals(98, + (int) provider.load("MyActor", new ActorId("123"), "goals", int.class).block()); + Assert.assertEquals(46.55, + (double) provider.load("MyActor", new ActorId("123"), "balance", double.class).block(), + EPSILON); + Assert.assertEquals(true, + (boolean) provider.load("MyActor", new ActorId("123"), "active", boolean.class).block()); + Assert.assertEquals(new Customer().setId(1000).setName("Roxane"), + provider.load("MyActor", new ActorId("123"), "customer", Customer.class).block()); + Assert.assertNotEquals(new Customer().setId(1000).setName("Roxane"), + provider.load("MyActor", new ActorId("123"), "anotherCustomer", Customer.class).block()); + Assert.assertNull(provider.load("MyActor", new ActorId("123"), "nullCustomer", Customer.class).block()); + } + + @Test + public void happyCaseContains() { + AppToDaprAsyncClient daprAsyncClient = mock(AppToDaprAsyncClient.class); + + // Keys that exists. + when(daprAsyncClient + .getState(any(), any(), eq("name"))) + .thenReturn(Mono.just("Jon Doe")); + when(daprAsyncClient + .getState(any(), any(), eq("zipcode"))) + .thenReturn(Mono.just("98021")); + when(daprAsyncClient + .getState(any(), any(), eq("goals"))) + .thenReturn(Mono.just("98")); + when(daprAsyncClient + .getState(any(), any(), eq("balance"))) + .thenReturn(Mono.just("46.55")); + when(daprAsyncClient + .getState(any(), any(), eq("active"))) + .thenReturn(Mono.just("true")); + when(daprAsyncClient + .getState(any(), any(), eq("customer"))) + .thenReturn(Mono.just("{ \"id\": \"3000\", \"name\": \"Ely\" }")); + + // Keys that do not exist. + when(daprAsyncClient + .getState(any(), any(), eq("Does not exist"))) + .thenReturn(Mono.just("")); + when(daprAsyncClient + .getState(any(), any(), eq("NAME"))) + .thenReturn(Mono.just("")); + when(daprAsyncClient + .getState(any(), any(), eq(null))) + .thenReturn(Mono.just("")); + + DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprAsyncClient, SERIALIZER); + + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "name").block()); + Assert.assertFalse(provider.contains("MyActor", new ActorId("123"), "NAME").block()); + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "zipcode").block()); + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "goals").block()); + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "balance").block()); + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "active").block()); + Assert.assertTrue(provider.contains("MyActor", new ActorId("123"), "customer").block()); + Assert.assertFalse(provider.contains("MyActor", new ActorId("123"), "Does not exist").block()); + Assert.assertFalse(provider.contains("MyActor", new ActorId("123"), null).block()); + } + + private final ActorStateChange createInsertChange(String name, T value) { + return new ActorStateChange(name, value, ActorStateChangeKind.ADD); + } + + private final ActorStateChange createUpdateChange(String name, T value) { + return new ActorStateChange(name, value, ActorStateChangeKind.UPDATE); + } + + private final ActorStateChange createDeleteChange(String name) { + return new ActorStateChange(name, null, ActorStateChangeKind.REMOVE); + } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java index 43d6f8e8ea..2a907bf72e 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java @@ -74,7 +74,8 @@ private static ActorRuntimeContext createActorRunti mock(ActorStateSerializer.class), mock(ActorFactory.class), ActorTypeInformation.create(clazz), - mock(AppToDaprAsyncClient.class)); + mock(AppToDaprAsyncClient.class), + mock(DaprStateAsyncProvider.class)); } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ConverterUtilsTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DurationUtilsTest.java similarity index 51% rename from sdk/src/test/java/io/dapr/actors/runtime/ConverterUtilsTest.java rename to sdk/src/test/java/io/dapr/actors/runtime/DurationUtilsTest.java index b90f84cc67..8b475eef4a 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ConverterUtilsTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DurationUtilsTest.java @@ -5,14 +5,14 @@ import java.time.Duration; -public class ConverterUtilsTest { +public class DurationUtilsTest { @Test public void convertTimeBothWays() { String s = "4h15m50s60ms"; - Duration d1 = ConverterUtils.ConvertDurationFromDaprFormat(s); + Duration d1 = DurationUtils.ConvertDurationFromDaprFormat(s); - String t = ConverterUtils.ConvertDurationToDaprFormat(d1); + String t = DurationUtils.ConvertDurationToDaprFormat(d1); Assert.assertEquals(s, t); } @@ -20,82 +20,82 @@ public void convertTimeBothWays() { public void largeHours() { // hours part is larger than 24 String s = "31h15m50s60ms"; - Duration d1 = ConverterUtils.ConvertDurationFromDaprFormat(s); + Duration d1 = DurationUtils.ConvertDurationFromDaprFormat(s); - String t = ConverterUtils.ConvertDurationToDaprFormat(d1); + String t = DurationUtils.ConvertDurationToDaprFormat(d1); Assert.assertEquals(s, t); } @Test public void negativeDuration() { Duration d = Duration.ofSeconds(-99); - String t = ConverterUtils.ConvertDurationToDaprFormat(d); + String t = DurationUtils.ConvertDurationToDaprFormat(d); Assert.assertEquals("", t); } @Test public void testGetHoursPart() { Duration d1 = Duration.ZERO.plusHours(26); - Assert.assertEquals(2, ConverterUtils.getHoursPart(d1)); + Assert.assertEquals(2, DurationUtils.getHoursPart(d1)); Duration d2 = Duration.ZERO.plusHours(23); - Assert.assertEquals(23, ConverterUtils.getHoursPart(d2)); + Assert.assertEquals(23, DurationUtils.getHoursPart(d2)); Duration d3 = Duration.ZERO.plusHours(24); - Assert.assertEquals(0, ConverterUtils.getHoursPart(d3)); + Assert.assertEquals(0, DurationUtils.getHoursPart(d3)); } @Test public void testGetMinutesPart() { Duration d1 = Duration.ZERO.plusMinutes(61); - Assert.assertEquals(1, ConverterUtils.getMinutesPart(d1)); + Assert.assertEquals(1, DurationUtils.getMinutesPart(d1)); Duration d2 = Duration.ZERO.plusMinutes(60); - Assert.assertEquals(0, ConverterUtils.getMinutesPart(d2)); + Assert.assertEquals(0, DurationUtils.getMinutesPart(d2)); Duration d3 = Duration.ZERO.plusMinutes(59); - Assert.assertEquals(59, ConverterUtils.getMinutesPart(d3)); + Assert.assertEquals(59, DurationUtils.getMinutesPart(d3)); Duration d4 = Duration.ZERO.plusMinutes(3600); - Assert.assertEquals(0, ConverterUtils.getMinutesPart(d4)); + Assert.assertEquals(0, DurationUtils.getMinutesPart(d4)); } @Test public void testGetSecondsPart() { Duration d1 = Duration.ZERO.plusSeconds(61); - Assert.assertEquals(1, ConverterUtils.getSecondsPart(d1)); + Assert.assertEquals(1, DurationUtils.getSecondsPart(d1)); Duration d2 = Duration.ZERO.plusSeconds(60); - Assert.assertEquals(0, ConverterUtils.getSecondsPart(d2)); + Assert.assertEquals(0, DurationUtils.getSecondsPart(d2)); Duration d3 = Duration.ZERO.plusSeconds(59); - Assert.assertEquals(59, ConverterUtils.getSecondsPart(d3)); + Assert.assertEquals(59, DurationUtils.getSecondsPart(d3)); Duration d4 = Duration.ZERO.plusSeconds(3600); - Assert.assertEquals(0, ConverterUtils.getSecondsPart(d4)); + Assert.assertEquals(0, DurationUtils.getSecondsPart(d4)); } @Test public void testGetMillisecondsPart() { Duration d1 = Duration.ZERO.plusMillis(61); - Assert.assertEquals(61, ConverterUtils.getMilliSecondsPart(d1)); + Assert.assertEquals(61, DurationUtils.getMilliSecondsPart(d1)); Duration d2 = Duration.ZERO.plusMillis(60); - Assert.assertEquals(60, ConverterUtils.getMilliSecondsPart(d2)); + Assert.assertEquals(60, DurationUtils.getMilliSecondsPart(d2)); Duration d3 = Duration.ZERO.plusMillis(59); - Assert.assertEquals(59, ConverterUtils.getMilliSecondsPart(d3)); + Assert.assertEquals(59, DurationUtils.getMilliSecondsPart(d3)); Duration d4 = Duration.ZERO.plusMillis(999); - Assert.assertEquals(999, ConverterUtils.getMilliSecondsPart(d4)); + Assert.assertEquals(999, DurationUtils.getMilliSecondsPart(d4)); Duration d5 = Duration.ZERO.plusMillis(1001); - Assert.assertEquals(1, ConverterUtils.getMilliSecondsPart(d5)); + Assert.assertEquals(1, DurationUtils.getMilliSecondsPart(d5)); Duration d6 = Duration.ZERO.plusMillis(1000); - Assert.assertEquals(0, ConverterUtils.getMilliSecondsPart(d6)); + Assert.assertEquals(0, DurationUtils.getMilliSecondsPart(d6)); Duration d7 = Duration.ZERO.plusMillis(10000); - Assert.assertEquals(0, ConverterUtils.getMilliSecondsPart(d7)); + Assert.assertEquals(0, DurationUtils.getMilliSecondsPart(d7)); } } From 153a139d54289a4ab47f7be675c4f4e3ca03ab21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Herrera=20de=20la=20Garza?= Date: Mon, 23 Dec 2019 23:30:17 -0600 Subject: [PATCH 5/7] Fix OrderManager example in order to process http 201 Change the implement the calls to DAPR using the class AbstractDaprClient because before the change the class always return Mono.Empty Implementation of the ActorAsyncProxy Change the name of the Actor Dapr Http Async Client --- .../examples/state/http/OrderManager.java | 2 +- .../io/dapr/actors/AbstractDaprClient.java | 70 +++--------- .../actors/client/ActorProxyAsyncClient.java | 55 +++++++++- .../client/ActorProxyClientBuilder.java | 6 +- .../client/ActorProxyHttpAsyncClient.java | 102 +++++++++++++++++- ...ntIT.java => DaprHttpAsyncClientTest.java} | 23 ++-- 6 files changed, 181 insertions(+), 77 deletions(-) rename sdk/src/test/java/io/dapr/actors/client/{DaprHttpAsyncClientIT.java => DaprHttpAsyncClientTest.java} (61%) diff --git a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java index 6cb7eaf9fb..ae07200503 100644 --- a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java +++ b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java @@ -89,7 +89,7 @@ public static void main(String[] args) throws IOException { out.printf("Writing to state: %s\n", state.toString()); post(stateUrl, state.toString()).thenAccept(response -> { - int resCode = response.statusCode() == 200 ? 200 : 500; + int resCode = response.statusCode() == 201 ? 201 : 500; String body = response.body(); try { e.sendResponseHeaders(resCode, body.getBytes().length); diff --git a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java index f35e4d75ce..7258fefdd7 100644 --- a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java +++ b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java @@ -75,25 +75,11 @@ protected final Mono invokeAPIVoid(String method, String urlString, String * @return Asynchronous text */ public final Mono invokeAPI(String method, String urlString, String json) throws RuntimeException { - - DaprHttpCallback cb = new DaprHttpCallback() { - - @Override - public void onFailure(Call call, Exception e) { - Mono.error(e); - } - - @Override - public void onSuccess(String response) { - Mono.just(response); - } - }; try { - tryInvokeAPI(method, urlString, json, cb); + return tryInvokeAPI(method, urlString, json); } catch (Exception e) { throw new RuntimeException(e); } - return Mono.empty(); } /** @@ -104,7 +90,7 @@ public void onSuccess(String response) { * @param json JSON payload or null. * @return text */ - private final void tryInvokeAPI(String method, String urlString, String json, final DaprHttpCallback cb) throws IOException, DaprException { + private final Mono tryInvokeAPI(String method, String urlString, String json) throws IOException, DaprException { String requestId = UUID.randomUUID().toString(); RequestBody body = json != null ? RequestBody.create(MEDIA_TYPE_APPLICATION_JSON, json) : REQUEST_BODY_EMPTY_JSON; @@ -114,30 +100,21 @@ private final void tryInvokeAPI(String method, String urlString, String json, fi .addHeader(Constants.HEADER_DAPR_REQUEST_ID, requestId) .build(); - this.httpClient.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - cb.onFailure(call, e); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) { - DaprError error = parseDaprError(response.body().string()); - response.close(); - if ((error != null) && (error.getErrorCode() != null) && (error.getMessage() != null)) { - throw new DaprException(error); - } - } else { - String respBodyString = responseBody.string(); - cb.onSuccess(respBodyString); - response.close(); + try (Response response = this.httpClient.newCall(request).execute()) { + try (ResponseBody responseBody = response.body()) { + if (!response.isSuccessful()) { + DaprError error = parseDaprError(response.body().string()); + if ((error != null) && (error.getErrorCode() != null) && (error.getMessage() != null)) { + return Mono.error(new DaprException(error)); } + return Mono.empty(); + } else { + String respBodyString = responseBody.string(); + return Mono.just(respBodyString); } } - }); + } } @@ -160,25 +137,6 @@ protected static DaprError parseDaprError(String json) { } } - public interface DaprHttpCallback { - - /** - * Called when the server response was not 2xx or when an exception was - * thrown in the process - * - * @param call - in case of server error (4xx, 5xx) this contains the server - * response in case of IO exception this is null - * @param e - contains the exception. in case of server error (4xx, 5xx) - * this is null - */ - public void onFailure(Call call, Exception e); - - /** - * Contains the server response - * - * @param response Success response. - */ - public void onSuccess(String response); - } + } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java index b86e3d6950..d0d9a6bbaf 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java @@ -4,6 +4,8 @@ */ package io.dapr.actors.client; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.dapr.actors.ActorId; import reactor.core.publisher.Mono; /** @@ -11,14 +13,59 @@ */ interface ActorProxyAsyncClient { + /** + * Returns the ActorId associated with the proxy object. + * + * @return An ActorId object. + */ + ActorId getActorId(); + + + + /** + * Returns actor implementation type of the actor associated with the proxy object. + * + * @return An String object. + */ + String getActorType(); + + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param clazz The type of the return class. + * @return Asynchronous result with the Actor's response. + */ + Mono invokeActorMethod(String methodName, Class clazz); + /** * Invokes an Actor method on Dapr. * - * @param actorType Type of actor. - * @param actorId Actor Identifier. * @param methodName Method name to invoke. - * @param jsonPayload Serialized body. + * @param data Object with the data. + * @param clazz The type of the return class. * @return Asynchronous result with the Actor's response. */ - Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload); + Mono invokeActorMethod(String methodName, Object data, Class clazz) throws JsonProcessingException; + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @return Asynchronous result with the Actor's response. + */ + public Mono invokeActorMethod(String methodName) ; + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param data Object with the data. + * @return Asynchronous result with the Actor's response. + */ + public Mono invokeActorMethod(String methodName, Object data) throws JsonProcessingException; + + + } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java index 514776af33..dcf7ac70af 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java @@ -22,9 +22,11 @@ class ActorProxyClientBuilder extends AbstractClientBuilder { * * @return Builds an async client. */ - public ActorProxyAsyncClient buildAsyncClient() { + public ActorProxyAsyncClient buildAsyncClient(ActorId actorId, String actorType) { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // TODO: Expose configurations for OkHttpClient or com.microsoft.rest.RestClient. - return new ActorProxyHttpAsyncClient(this.port, builder.build()); + return new ActorProxyHttpAsyncClient(this.port, builder.build(),actorId,actorType); } + + } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java index 482712407d..1eee3cd99e 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java @@ -4,31 +4,127 @@ */ package io.dapr.actors.client; +import com.fasterxml.jackson.core.JsonProcessingException; import io.dapr.actors.*; import okhttp3.*; +import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; +import java.io.IOException; + /** * Http client to call actors methods. */ -class ActorProxyHttpAsyncClient extends AbstractDaprClient implements ActorProxyAsyncClient { +public class ActorProxyHttpAsyncClient extends AbstractDaprClient implements ActorProxyAsyncClient { + + /** + * Shared Json serializer/deserializer as per Jackson's documentation. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private ActorId actorId; + private String actorType; /** * Creates a new instance of {@link ActorProxyHttpAsyncClient}. * * @param port Port for calling Dapr. (e.g. 3500) * @param httpClient RestClient used for all API calls in this new instance. + * @param actorId The actorId associated with the proxy + * @param actorType actor implementation type of the actor associated with the proxy object. */ - public ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient) { + ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient,ActorId actorId, String actorType) { super(port, httpClient); + this.setActorId(actorId); + this.setActorType(actorType); } + /** * {@inheritDoc} */ @Override - public Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { + public Mono invokeActorMethod(String methodName, Object data, Class clazz) throws JsonProcessingException { + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> { + try { + return OBJECT_MAPPER.readValue(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Class clazz){ + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> { + try { + return OBJECT_MAPPER.readValue(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName) { + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> s); + } + + @Override + public Mono invokeActorMethod(String methodName, Object data) throws JsonProcessingException { + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> s); + } + + + protected Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { String url = String.format(Constants.ACTOR_METHOD_RELATIVE_URL_FORMAT, actorType, actorId, methodName); return super.invokeAPI("PUT", url, jsonPayload); } + + + + + /** + * {@inheritDoc} + */ + public ActorId getActorId() { + return actorId; + } + + private void setActorId(ActorId actorId) { + this.actorId = actorId; + } + + /** + * {@inheritDoc} + */ + public String getActorType() { + return actorType; + } + + private void setActorType(String actorType) { + this.actorType = actorType; + } } diff --git a/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java b/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java similarity index 61% rename from sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java rename to sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java index cccccd717f..597296e001 100644 --- a/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java +++ b/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java @@ -4,7 +4,8 @@ */ package io.dapr.actors.client; -import io.dapr.actors.*; +import io.dapr.actors.ActorId; +import io.dapr.actors.DaprException; import org.junit.Assert; import org.junit.Test; @@ -13,7 +14,7 @@ *

* Requires Dapr running. */ -public class DaprHttpAsyncClientIT { +public class DaprHttpAsyncClientTest { /** * Checks if the error is correctly parsed when trying to invoke a function on @@ -21,23 +22,23 @@ public class DaprHttpAsyncClientIT { */ @Test(expected = RuntimeException.class) public void invokeUnknownActor() { - ActorProxyAsyncClient daprAsyncClient = new ActorProxyClientBuilder().buildAsyncClient(); + ActorProxyAsyncClient daprAsyncClient = new ActorProxyClientBuilder().buildAsyncClient(new ActorId("200"),"DemoActor"); daprAsyncClient - .invokeActorMethod("ActorThatDoesNotExist", "100", "GetData", null) + .invokeActorMethod("GetData" ) .doOnError(x -> { - Assert.assertTrue(x instanceof RuntimeException); - RuntimeException runtimeException = (RuntimeException) x; - - Throwable cause = runtimeException.getCause(); - Assert.assertTrue(cause instanceof DaprException); - DaprException daprException = (DaprException) cause; + Assert.assertTrue(x instanceof DaprException); + DaprException daprException = (DaprException) x; Assert.assertNotNull(daprException); Assert.assertEquals("ERR_INVOKE_ACTOR", daprException.getErrorCode()); Assert.assertNotNull(daprException.getMessage()); Assert.assertFalse(daprException.getMessage().isEmpty()); }) - .doOnSuccess(x -> Assert.fail("This call should fail.")) + .doOnSuccess(x -> + Assert.fail("This call should fail.")) .block(); + } + + } From 48fe1939d2dc7473532ba84de98208a959c28c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Herrera=20de=20la=20Garza?= Date: Tue, 24 Dec 2019 19:04:01 -0600 Subject: [PATCH 6/7] Update code with the changes proposed by Artur in the code review --- .../examples/state/http/OrderManager.java | 5 +- .../io/dapr/actors/client/ActorProxy.java | 63 +++++++++ .../actors/client/ActorProxyAsyncClient.java | 55 +------- .../client/ActorProxyClientBuilder.java | 6 +- .../client/ActorProxyHttpAsyncClient.java | 102 +-------------- .../io/dapr/actors/client/ActorProxyImpl.java | 121 ++++++++++++++++++ .../actors/client/ActorProxyImplTest.java | 16 +++ ...ntTest.java => DaprHttpAsyncClientIT.java} | 23 ++-- 8 files changed, 224 insertions(+), 167 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/actors/client/ActorProxy.java create mode 100644 sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java create mode 100644 sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java rename sdk/src/test/java/io/dapr/actors/client/{DaprHttpAsyncClientTest.java => DaprHttpAsyncClientIT.java} (61%) diff --git a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java index ae07200503..184f03c274 100644 --- a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java +++ b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java @@ -20,6 +20,8 @@ import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.Charset; import java.time.Duration; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -42,6 +44,7 @@ public class OrderManager { static HttpClient httpClient; + static final List httpOkStatus = Arrays.asList(200,201); public static void main(String[] args) throws IOException { int httpPort = 3000; @@ -89,7 +92,7 @@ public static void main(String[] args) throws IOException { out.printf("Writing to state: %s\n", state.toString()); post(stateUrl, state.toString()).thenAccept(response -> { - int resCode = response.statusCode() == 201 ? 201 : 500; + int resCode = httpOkStatus.contains(response.statusCode()) ? 201 : 500; String body = response.body(); try { e.sendResponseHeaders(resCode, body.getBytes().length); diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java new file mode 100644 index 0000000000..1ac2338e04 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java @@ -0,0 +1,63 @@ +package io.dapr.actors.client; + +import io.dapr.actors.ActorId; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +public interface ActorProxy { + + /** + * Returns the ActorId associated with the proxy object. + * + * @return An ActorId object. + */ + ActorId getActorId(); + + + + /** + * Returns actor implementation type of the actor associated with the proxy object. + * + * @return An String object. + */ + String getActorType(); + + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param clazz The type of the return class. + * @return Asynchronous result with the Actor's response. + */ + Mono invokeActorMethod(String methodName, Class clazz); + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param data Object with the data. + * @param clazz The type of the return class. + * @return Asynchronous result with the Actor's response. + */ + Mono invokeActorMethod(String methodName, Object data, Class clazz) throws IOException; + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @return Asynchronous result with the Actor's response. + */ + public Mono invokeActorMethod(String methodName) ; + + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param data Object with the data. + * @return Asynchronous result with the Actor's response. + */ + public Mono invokeActorMethod(String methodName, Object data) throws IOException; + +} diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java index d0d9a6bbaf..b86e3d6950 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyAsyncClient.java @@ -4,8 +4,6 @@ */ package io.dapr.actors.client; -import com.fasterxml.jackson.core.JsonProcessingException; -import io.dapr.actors.ActorId; import reactor.core.publisher.Mono; /** @@ -13,59 +11,14 @@ */ interface ActorProxyAsyncClient { - /** - * Returns the ActorId associated with the proxy object. - * - * @return An ActorId object. - */ - ActorId getActorId(); - - - - /** - * Returns actor implementation type of the actor associated with the proxy object. - * - * @return An String object. - */ - String getActorType(); - - - /** - * Invokes an Actor method on Dapr. - * - * @param methodName Method name to invoke. - * @param clazz The type of the return class. - * @return Asynchronous result with the Actor's response. - */ - Mono invokeActorMethod(String methodName, Class clazz); - /** * Invokes an Actor method on Dapr. * + * @param actorType Type of actor. + * @param actorId Actor Identifier. * @param methodName Method name to invoke. - * @param data Object with the data. - * @param clazz The type of the return class. + * @param jsonPayload Serialized body. * @return Asynchronous result with the Actor's response. */ - Mono invokeActorMethod(String methodName, Object data, Class clazz) throws JsonProcessingException; - - /** - * Invokes an Actor method on Dapr. - * - * @param methodName Method name to invoke. - * @return Asynchronous result with the Actor's response. - */ - public Mono invokeActorMethod(String methodName) ; - - /** - * Invokes an Actor method on Dapr. - * - * @param methodName Method name to invoke. - * @param data Object with the data. - * @return Asynchronous result with the Actor's response. - */ - public Mono invokeActorMethod(String methodName, Object data) throws JsonProcessingException; - - - + Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload); } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java index dcf7ac70af..514776af33 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java @@ -22,11 +22,9 @@ class ActorProxyClientBuilder extends AbstractClientBuilder { * * @return Builds an async client. */ - public ActorProxyAsyncClient buildAsyncClient(ActorId actorId, String actorType) { + public ActorProxyAsyncClient buildAsyncClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // TODO: Expose configurations for OkHttpClient or com.microsoft.rest.RestClient. - return new ActorProxyHttpAsyncClient(this.port, builder.build(),actorId,actorType); + return new ActorProxyHttpAsyncClient(this.port, builder.build()); } - - } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java index 1eee3cd99e..482712407d 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java @@ -4,127 +4,31 @@ */ package io.dapr.actors.client; -import com.fasterxml.jackson.core.JsonProcessingException; import io.dapr.actors.*; import okhttp3.*; -import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; -import java.io.IOException; - /** * Http client to call actors methods. */ -public class ActorProxyHttpAsyncClient extends AbstractDaprClient implements ActorProxyAsyncClient { - - /** - * Shared Json serializer/deserializer as per Jackson's documentation. - */ - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private ActorId actorId; - private String actorType; +class ActorProxyHttpAsyncClient extends AbstractDaprClient implements ActorProxyAsyncClient { /** * Creates a new instance of {@link ActorProxyHttpAsyncClient}. * * @param port Port for calling Dapr. (e.g. 3500) * @param httpClient RestClient used for all API calls in this new instance. - * @param actorId The actorId associated with the proxy - * @param actorType actor implementation type of the actor associated with the proxy object. */ - ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient,ActorId actorId, String actorType) { + public ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient) { super(port, httpClient); - this.setActorId(actorId); - this.setActorType(actorType); } - /** * {@inheritDoc} */ @Override - public Mono invokeActorMethod(String methodName, Object data, Class clazz) throws JsonProcessingException { - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> { - try { - return OBJECT_MAPPER.readValue(s, clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeActorMethod(String methodName, Class clazz){ - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> { - try { - return OBJECT_MAPPER.readValue(s, clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeActorMethod(String methodName) { - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> s); - } - - @Override - public Mono invokeActorMethod(String methodName, Object data) throws JsonProcessingException { - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> s); - } - - - protected Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { + public Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { String url = String.format(Constants.ACTOR_METHOD_RELATIVE_URL_FORMAT, actorType, actorId, methodName); return super.invokeAPI("PUT", url, jsonPayload); } - - - - - /** - * {@inheritDoc} - */ - public ActorId getActorId() { - return actorId; - } - - private void setActorId(ActorId actorId) { - this.actorId = actorId; - } - - /** - * {@inheritDoc} - */ - public String getActorType() { - return actorType; - } - - private void setActorType(String actorType) { - this.actorType = actorType; - } } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java new file mode 100644 index 0000000000..efbfbed9df --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java @@ -0,0 +1,121 @@ +package io.dapr.actors.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.actors.ActorId; +import io.dapr.actors.Constants; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +public class ActorProxyImpl implements ActorProxy { + + /** + * Shared Json serializer/deserializer as per Jackson's documentation. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private ActorId actorId; + private String actorType; + private ActorProxyHttpAsyncClient abstractDaprClient; + + /** + * Creates a new instance of {@link ActorProxyHttpAsyncClient}. + * + * @param actorId The actorId associated with the proxy + * @param actorType actor implementation type of the actor associated with the proxy object. + */ + ActorProxyImpl(ActorId actorId, String actorType, ActorProxyHttpAsyncClient abstractDaprClient) { + this.abstractDaprClient= abstractDaprClient; + this.setActorId(actorId); + this.setActorType(actorType); + } + + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Object data, Class clazz) throws IOException { + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> { + try { + return OBJECT_MAPPER.readValue(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Class clazz){ + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> { + try { + return OBJECT_MAPPER.readValue(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName) { + + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> s); + } + + @Override + public Mono invokeActorMethod(String methodName, Object data) throws IOException { + Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> s); + } + + + protected Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { + String url = String.format(Constants.ACTOR_METHOD_RELATIVE_URL_FORMAT, actorType, actorId, methodName); + return this.abstractDaprClient.invokeAPI("PUT", url, jsonPayload); + } + + + + + /** + * {@inheritDoc} + */ + public ActorId getActorId() { + return actorId; + } + + private void setActorId(ActorId actorId) { + this.actorId = actorId; + } + + /** + * {@inheritDoc} + */ + public String getActorType() { + return actorType; + } + + private void setActorType(String actorType) { + this.actorType = actorType; + } +} diff --git a/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java b/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java new file mode 100644 index 0000000000..dc7619ce20 --- /dev/null +++ b/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java @@ -0,0 +1,16 @@ +package io.dapr.actors.client; + +import io.dapr.actors.ActorId; +import org.junit.Assert; +import org.junit.Test; + +public class ActorProxyImplTest { + + @Test() + public void constructorActorProxyTest() { + final ActorProxyHttpAsyncClient actorProxyAsyncClient = (ActorProxyHttpAsyncClient)new ActorProxyClientBuilder().buildAsyncClient(); + final ActorProxyImpl actorProxy= new ActorProxyImpl(new ActorId("100"),"myActorType",actorProxyAsyncClient); + Assert.assertEquals(actorProxy.getActorId().getStringId(),"100"); + Assert.assertEquals(actorProxy.getActorType(),"myActorType"); + } +} diff --git a/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java b/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java similarity index 61% rename from sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java rename to sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java index 597296e001..cccccd717f 100644 --- a/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientTest.java +++ b/sdk/src/test/java/io/dapr/actors/client/DaprHttpAsyncClientIT.java @@ -4,8 +4,7 @@ */ package io.dapr.actors.client; -import io.dapr.actors.ActorId; -import io.dapr.actors.DaprException; +import io.dapr.actors.*; import org.junit.Assert; import org.junit.Test; @@ -14,7 +13,7 @@ *

* Requires Dapr running. */ -public class DaprHttpAsyncClientTest { +public class DaprHttpAsyncClientIT { /** * Checks if the error is correctly parsed when trying to invoke a function on @@ -22,23 +21,23 @@ public class DaprHttpAsyncClientTest { */ @Test(expected = RuntimeException.class) public void invokeUnknownActor() { - ActorProxyAsyncClient daprAsyncClient = new ActorProxyClientBuilder().buildAsyncClient(new ActorId("200"),"DemoActor"); + ActorProxyAsyncClient daprAsyncClient = new ActorProxyClientBuilder().buildAsyncClient(); daprAsyncClient - .invokeActorMethod("GetData" ) + .invokeActorMethod("ActorThatDoesNotExist", "100", "GetData", null) .doOnError(x -> { - Assert.assertTrue(x instanceof DaprException); - DaprException daprException = (DaprException) x; + Assert.assertTrue(x instanceof RuntimeException); + RuntimeException runtimeException = (RuntimeException) x; + + Throwable cause = runtimeException.getCause(); + Assert.assertTrue(cause instanceof DaprException); + DaprException daprException = (DaprException) cause; Assert.assertNotNull(daprException); Assert.assertEquals("ERR_INVOKE_ACTOR", daprException.getErrorCode()); Assert.assertNotNull(daprException.getMessage()); Assert.assertFalse(daprException.getMessage().isEmpty()); }) - .doOnSuccess(x -> - Assert.fail("This call should fail.")) + .doOnSuccess(x -> Assert.fail("This call should fail.")) .block(); - } - - } From 0cb757a89c1649b4fb2f5eb1f6f74f89323a3d4d Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Thu, 26 Dec 2019 20:32:05 -0800 Subject: [PATCH 7/7] Changes to support ActorProxy + Fixes. --- .../examples/actors/http/ActorClient.java | 16 -- .../examples/actors/http/DemoActorClient.java | 70 +++++ .../examples/actors/http/DemoActorImpl.java | 2 + .../actors/http/DemoActorService.java | 4 +- .../examples/state/http/OrderManager.java | 5 +- .../io/dapr/actors/AbstractClientBuilder.java | 11 +- .../io/dapr/actors/AbstractDaprClient.java | 93 ++++-- .../actors/client/ActorMethodEnvelope.java | 33 +++ .../io/dapr/actors/client/ActorProxy.java | 14 +- .../dapr/actors/client/ActorProxyBuilder.java | 86 ++++++ .../client/ActorProxyClientBuilder.java | 7 +- .../client/ActorProxyHttpAsyncClient.java | 2 +- .../io/dapr/actors/client/ActorProxyImpl.java | 271 +++++++++++------- .../actors/runtime/ActorStateSerializer.java | 116 +------- .../runtime/AppToDaprClientBuilder.java | 7 +- .../dapr/actors/utils/ObjectSerializer.java | 129 +++++++++ .../actors/client/ActorProxyImplTest.java | 9 +- .../runtime/ActorReminderParamsTest.java | 1 + .../runtime/DaprStateAsyncProviderTest.java | 4 +- .../runtime/DefaultActorFactoryTest.java | 2 +- 20 files changed, 598 insertions(+), 284 deletions(-) delete mode 100644 examples/src/main/java/io/dapr/examples/actors/http/ActorClient.java create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java create mode 100644 sdk/src/main/java/io/dapr/actors/client/ActorMethodEnvelope.java create mode 100644 sdk/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java create mode 100644 sdk/src/main/java/io/dapr/actors/utils/ObjectSerializer.java diff --git a/examples/src/main/java/io/dapr/examples/actors/http/ActorClient.java b/examples/src/main/java/io/dapr/examples/actors/http/ActorClient.java deleted file mode 100644 index 5bd05f46ae..0000000000 --- a/examples/src/main/java/io/dapr/examples/actors/http/ActorClient.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -package io.dapr.examples.actors.http; - -/** - * Client that will use Actor. - */ -public class ActorClient { - // TODO. - - public static void main(String[] args) throws Exception { - } -} 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 new file mode 100644 index 0000000000..0639537756 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import io.dapr.actors.ActorId; +import io.dapr.actors.client.ActorProxy; +import io.dapr.actors.client.ActorProxyBuilder; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Client for Actor runtime. + * 1. Build and install jars: + * mvn clean install + * 2. Run the client: + * dapr run --app-id demoactorclient --port 3006 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorClient + */ +public class DemoActorClient { + + private static final int NUM_ACTORS = 3; + + private static final int NUM_MESSAGES_PER_ACTOR = 10; + + private static final String METHOD_NAME = "say"; + + private static final ExecutorService POOL = Executors.newFixedThreadPool(NUM_ACTORS); + + public static void main(String[] args) throws Exception { + ActorProxyBuilder builder = new ActorProxyBuilder(); + + List> futures = new ArrayList<>(NUM_ACTORS); + + for (int i = 0; i < NUM_ACTORS; i++) { + ActorProxy actor = builder.withActorType("DemoActor").withActorId(ActorId.createRandom()).build(); + futures.add(callActorNTimes(actor)); + } + + futures.forEach(CompletableFuture::join); + POOL.shutdown(); + POOL.awaitTermination(1, TimeUnit.MINUTES); + + System.out.println("Done."); + } + + private static final CompletableFuture callActorNTimes(ActorProxy actor) { + return CompletableFuture.runAsync(() -> { + for (int i = 0; i < NUM_MESSAGES_PER_ACTOR; i++) { + String result = actor.invokeActorMethod(METHOD_NAME, + String.format("Actor %s said message #%d", actor.getActorId().toString(), i)).block(); + System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result)); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + return; + } + } + }, POOL); + } +} 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 301bb6a429..bade8db1e9 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 @@ -9,6 +9,7 @@ import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.Actor; import io.dapr.actors.runtime.ActorRuntimeContext; +import io.dapr.actors.runtime.ActorType; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -18,6 +19,7 @@ /** * Implementation of the DemoActor for the server side. */ +@ActorType(Name = "DemoActor") public class DemoActorImpl extends AbstractActor implements DemoActor, Actor { /** diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java index 3e0cdccea1..35363de035 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java @@ -32,8 +32,8 @@ * Service for Actor runtime. * 1. Build and install jars: * mvn clean install - * 2. Run in server mode: - * dapr run --app-id hellogrpc --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000" + * 2. Run the server: + * dapr run --app-id demoactorservice --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000" */ public class DemoActorService { diff --git a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java index 184f03c274..dd05ee4f10 100644 --- a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java +++ b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java @@ -44,7 +44,6 @@ public class OrderManager { static HttpClient httpClient; - static final List httpOkStatus = Arrays.asList(200,201); public static void main(String[] args) throws IOException { int httpPort = 3000; @@ -59,7 +58,7 @@ public static void main(String[] args) throws IOException { out.println("Fetching order!"); fetch(stateUrl + "/order").thenAccept(response -> { int resCode = response.statusCode() == 200 ? 200 : 500; - String body = response.statusCode() == 200 ? response.body() : "Could not get state."; + String body = (response.statusCode() == 200) || (response.statusCode() == 201) ? response.body() : "Could not get state."; try { e.sendResponseHeaders(resCode, body.getBytes().length); @@ -92,7 +91,7 @@ public static void main(String[] args) throws IOException { out.printf("Writing to state: %s\n", state.toString()); post(stateUrl, state.toString()).thenAccept(response -> { - int resCode = httpOkStatus.contains(response.statusCode()) ? 201 : 500; + int resCode = (response.statusCode() == 200) || (response.statusCode() == 201) ? 201 : 500; String body = response.body(); try { e.sendResponseHeaders(resCode, body.getBytes().length); diff --git a/sdk/src/main/java/io/dapr/actors/AbstractClientBuilder.java b/sdk/src/main/java/io/dapr/actors/AbstractClientBuilder.java index c5eaf7fe9f..7de34461f8 100644 --- a/sdk/src/main/java/io/dapr/actors/AbstractClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/AbstractClientBuilder.java @@ -25,12 +25,20 @@ public AbstractClientBuilder withPort(int port) { return this; } + /** + * Returns configured port. + * @return + */ + protected int getPort() { + return this.port; + } + /** * Tries to get a valid port from environment variable or returns default. * * @return Port defined in env variable or default. */ - protected static int GetEnvPortOrDefault() { + private static int GetEnvPortOrDefault() { String envPort = System.getenv(Constants.ENV_DAPR_HTTP_PORT); if (envPort == null) { return Constants.DEFAULT_PORT; @@ -44,4 +52,5 @@ protected static int GetEnvPortOrDefault() { return Constants.DEFAULT_PORT; } + } diff --git a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java index 7258fefdd7..7ce645fb8b 100644 --- a/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java +++ b/sdk/src/main/java/io/dapr/actors/AbstractDaprClient.java @@ -5,16 +5,13 @@ package io.dapr.actors; import com.fasterxml.jackson.databind.ObjectMapper; -import okhttp3.*; -import reactor.core.publisher.Mono; - import java.io.IOException; import java.net.URL; import java.util.UUID; +import okhttp3.*; +import reactor.core.publisher.Mono; -/** - * Base for Dapr HTTP Client. - */ +// base class of hierarchy public abstract class AbstractDaprClient { /** @@ -75,11 +72,25 @@ protected final Mono invokeAPIVoid(String method, String urlString, String * @return Asynchronous text */ public final Mono invokeAPI(String method, String urlString, String json) throws RuntimeException { + + DaprHttpCallback cb = new DaprHttpCallback() { + + @Override + public void onFailure(Call call, Exception e) { + Mono.error(e); + } + + @Override + public void onSuccess(String response) { + Mono.just(response); + } + }; try { - return tryInvokeAPI(method, urlString, json); + tryInvokeAPI(method, urlString, json, cb); } catch (Exception e) { throw new RuntimeException(e); } + return Mono.empty(); } /** @@ -90,31 +101,40 @@ public final Mono invokeAPI(String method, String urlString, String json * @param json JSON payload or null. * @return text */ - private final Mono tryInvokeAPI(String method, String urlString, String json) throws IOException, DaprException { + private final void tryInvokeAPI(String method, String urlString, String json, final DaprHttpCallback cb) throws IOException, DaprException { String requestId = UUID.randomUUID().toString(); RequestBody body = json != null ? RequestBody.create(MEDIA_TYPE_APPLICATION_JSON, json) : REQUEST_BODY_EMPTY_JSON; Request request = new Request.Builder() - .url(new URL(this.baseUrl + urlString)) - .method(method, body) - .addHeader(Constants.HEADER_DAPR_REQUEST_ID, requestId) - .build(); - - - try (Response response = this.httpClient.newCall(request).execute()) { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) { - DaprError error = parseDaprError(response.body().string()); - if ((error != null) && (error.getErrorCode() != null) && (error.getMessage() != null)) { - return Mono.error(new DaprException(error)); + .url(new URL(this.baseUrl + urlString)) + .method(method, body) + .addHeader(Constants.HEADER_DAPR_REQUEST_ID, requestId) + .build(); + + this.httpClient.newCall(request).enqueue(new Callback() { + + @Override + public void onFailure(Call call, IOException e) { + cb.onFailure(call, e); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (!response.isSuccessful()) { + DaprError error = parseDaprError(response.body().string()); + response.close(); + if ((error != null) && (error.getErrorCode() != null) && (error.getMessage() != null)) { + throw new DaprException(error); + } + } else { + String respBodyString = responseBody.string(); + cb.onSuccess(respBodyString); + response.close(); } - return Mono.empty(); - } else { - String respBodyString = responseBody.string(); - return Mono.just(respBodyString); } } - } + }); } @@ -137,6 +157,25 @@ protected static DaprError parseDaprError(String json) { } } + public interface DaprHttpCallback { + + /** + * Called when the server response was not 2xx or when an exception was + * thrown in the process + * + * @param call - in case of server error (4xx, 5xx) this contains the server + * response in case of IO exception this is null + * @param e - contains the exception. in case of server error (4xx, 5xx) + * this is null + */ + public void onFailure(Call call, Exception e); + + /** + * Contains the server response + * + * @param response Success response. + */ + public void onSuccess(String response); + } - -} +} \ No newline at end of file diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorMethodEnvelope.java b/sdk/src/main/java/io/dapr/actors/client/ActorMethodEnvelope.java new file mode 100644 index 0000000000..857f8f31d4 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/client/ActorMethodEnvelope.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.client; + +/** + * Request and Response object used to talk to Actors. + */ +public class ActorMethodEnvelope { + + /** + * Data serialized for input/output of Actor methods. + */ + private byte[] data; + + /** + * Gets the data serialized for input/output of Actor methods. + * @return Data serialized for input/output of Actor methods. + */ + public byte[] getData() { + return data; + } + + /** + * Sets the data serialized for input/output of Actor methods. + * @param data Data serialized for input/output of Actor methods. + */ + public void setData(byte[] data) { + this.data = data; + } +} diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java index 1ac2338e04..6f35b3429d 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxy.java @@ -5,6 +5,9 @@ import java.io.IOException; +/** + * Proxy to communicate to a given Actor instance in Dapr. + */ public interface ActorProxy { /** @@ -14,16 +17,13 @@ public interface ActorProxy { */ ActorId getActorId(); - - /** * Returns actor implementation type of the actor associated with the proxy object. * - * @return An String object. + * @return Actor's type name. */ String getActorType(); - /** * Invokes an Actor method on Dapr. * @@ -41,7 +41,7 @@ public interface ActorProxy { * @param clazz The type of the return class. * @return Asynchronous result with the Actor's response. */ - Mono invokeActorMethod(String methodName, Object data, Class clazz) throws IOException; + Mono invokeActorMethod(String methodName, Object data, Class clazz); /** * Invokes an Actor method on Dapr. @@ -49,7 +49,7 @@ public interface ActorProxy { * @param methodName Method name to invoke. * @return Asynchronous result with the Actor's response. */ - public Mono invokeActorMethod(String methodName) ; + Mono invokeActorMethod(String methodName); /** * Invokes an Actor method on Dapr. @@ -58,6 +58,6 @@ public interface ActorProxy { * @param data Object with the data. * @return Asynchronous result with the Actor's response. */ - public Mono invokeActorMethod(String methodName, Object data) throws IOException; + Mono invokeActorMethod(String methodName, Object data); } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java new file mode 100644 index 0000000000..7507167c05 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java @@ -0,0 +1,86 @@ +package io.dapr.actors.client; + +import io.dapr.actors.ActorId; +import io.dapr.actors.runtime.ActorStateSerializer; +import io.dapr.actors.utils.ObjectSerializer; + +/** + * Builder to generate an ActorProxy instance. + */ +public class ActorProxyBuilder { + + /** + * Serializer for content to be sent back and forth between actors. + */ + private static final ObjectSerializer SERIALIZER = new ActorStateSerializer(); + + /** + * Builder for the Dapr client. + */ + private final ActorProxyClientBuilder clientBuilder = new ActorProxyClientBuilder(); + + /** + * Actor's type. + */ + private String actorType; + + /** + * Actor's identifier. + */ + private ActorId actorId; + + /** + * Changes build config to use specific port. + * + * @param port Port to be used. + * @return Same builder object. + */ + public ActorProxyBuilder withPort(int port) { + this.clientBuilder.withPort(port); + return this; + } + + /** + * Changes build config to use given Actor's type. + * + * @param actorType Actor's type. + * @return Same builder object. + */ + public ActorProxyBuilder withActorType(String actorType) { + this.actorType = actorType; + return this; + } + + /** + * Changes build config to use given Actor's identifier. + * + * @param actorId Actor's identifier. + * @return Same builder object. + */ + public ActorProxyBuilder withActorId(ActorId actorId) { + this.actorId = actorId; + return this; + } + + /** + * Instantiates a new ActorProxy. + * + * @return New instance of ActorProxy. + */ + public ActorProxy build() { + if ((this.actorType == null) || this.actorType.isEmpty()) { + throw new IllegalArgumentException("Cannot instantiate an Actor without type."); + } + + if (this.actorId == null) { + throw new IllegalArgumentException("Cannot instantiate an Actor without Id."); + } + + return new ActorProxyImpl( + this.actorType, + this.actorId, + SERIALIZER, + this.clientBuilder.buildAsyncClient()); + } + +} diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java index 514776af33..64af557b7c 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyClientBuilder.java @@ -12,11 +12,6 @@ */ class ActorProxyClientBuilder extends AbstractClientBuilder { - /** - * Default port for Dapr after checking environment variable. - */ - private int port = ActorProxyClientBuilder.GetEnvPortOrDefault(); - /** * Builds an async client. * @@ -25,6 +20,6 @@ class ActorProxyClientBuilder extends AbstractClientBuilder { public ActorProxyAsyncClient buildAsyncClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // TODO: Expose configurations for OkHttpClient or com.microsoft.rest.RestClient. - return new ActorProxyHttpAsyncClient(this.port, builder.build()); + return new ActorProxyHttpAsyncClient(super.getPort(), builder.build()); } } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java index 482712407d..96075290ec 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyHttpAsyncClient.java @@ -19,7 +19,7 @@ class ActorProxyHttpAsyncClient extends AbstractDaprClient implements ActorProxy * @param port Port for calling Dapr. (e.g. 3500) * @param httpClient RestClient used for all API calls in this new instance. */ - public ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient) { + ActorProxyHttpAsyncClient(int port, OkHttpClient httpClient) { super(port, httpClient); } diff --git a/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java b/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java index efbfbed9df..44b3093f42 100644 --- a/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java +++ b/sdk/src/main/java/io/dapr/actors/client/ActorProxyImpl.java @@ -1,121 +1,180 @@ package io.dapr.actors.client; -import com.fasterxml.jackson.databind.ObjectMapper; import io.dapr.actors.ActorId; -import io.dapr.actors.Constants; +import io.dapr.actors.utils.ObjectSerializer; import reactor.core.publisher.Mono; import java.io.IOException; - -public class ActorProxyImpl implements ActorProxy { - - /** - * Shared Json serializer/deserializer as per Jackson's documentation. - */ - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private ActorId actorId; - private String actorType; - private ActorProxyHttpAsyncClient abstractDaprClient; - - /** - * Creates a new instance of {@link ActorProxyHttpAsyncClient}. - * - * @param actorId The actorId associated with the proxy - * @param actorType actor implementation type of the actor associated with the proxy object. - */ - ActorProxyImpl(ActorId actorId, String actorType, ActorProxyHttpAsyncClient abstractDaprClient) { - this.abstractDaprClient= abstractDaprClient; - this.setActorId(actorId); - this.setActorType(actorType); +import java.nio.charset.StandardCharsets; + +/** + * Implements a proxy client for an Actor's instance. + */ +class ActorProxyImpl implements ActorProxy { + + /** + * EMPTY data for null response. + */ + private static final byte[] EMPTY_DATA = new byte[0]; + + /** + * Actor's identifier for this Actor instance. + */ + private final ActorId actorId; + + /** + * Actor's type for this Actor instance. + */ + private final String actorType; + + /** + * Serializer/deserialzier to exchange message for Actors. + */ + private final ObjectSerializer serializer; + + /** + * Client to talk to the Dapr's API. + */ + private final ActorProxyAsyncClient daprClient; + + /** + * Creates a new instance of {@link ActorProxyAsyncClient}. + * + * @param actorType actor implementation type of the actor associated with the proxy object. + * @param actorId The actorId associated with the proxy + * @param serializer Serializer and deserializer for method calls. + * @param daprClient Dapr client. + */ + ActorProxyImpl(String actorType, ActorId actorId, ObjectSerializer serializer, ActorProxyAsyncClient daprClient) { + this.actorType = actorType; + this.actorId = actorId; + this.daprClient = daprClient; + this.serializer = serializer; + } + + /** + * {@inheritDoc} + */ + public ActorId getActorId() { + return actorId; + } + + /** + * {@inheritDoc} + */ + public String getActorType() { + return actorType; + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Object data, Class clazz) { + try { + Mono result = this.daprClient.invokeActorMethod( + actorType, + actorId.toString(), + methodName, + this.wrap(data)); + + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> unwrap(s, clazz)); + } catch (IOException e) { + return Mono.error(e); } - - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeActorMethod(String methodName, Object data, Class clazz) throws IOException { - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> { - try { - return OBJECT_MAPPER.readValue(s, clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeActorMethod(String methodName, Class clazz){ - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> { - try { - return OBJECT_MAPPER.readValue(s, clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Class clazz) { + Mono result = this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> unwrap(s, clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName) { + Mono result = this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, null); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> this.unwrap(s, String.class)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Object data) { + try { + Mono result = this.daprClient.invokeActorMethod( + actorType, + actorId.toString(), + methodName, + this.wrap(data)); + return result + .filter(s -> (s != null) && (!s.isEmpty())) + .map(s -> unwrap(s, String.class)); + } catch (IOException e) { + return Mono.error(e); } - - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeActorMethod(String methodName) { - - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,null); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> s); + } + + /** + * Extracts the response object from the Actor's method result. + * + * @param response String returned by API. + * @param clazz Expected response class. + * @param Expected response type. + * @return Response object, null or RuntimeException. + */ + private T unwrap(final String response, Class clazz) { + if (response == null) { + return null; } - @Override - public Mono invokeActorMethod(String methodName, Object data) throws IOException { - Mono result=this.invokeActorMethod(actorType,actorId.toString(),methodName,OBJECT_MAPPER.writeValueAsString(data)); - return result - .filter(s -> (s != null) && (!s.isEmpty())) - .map(s -> s); + try { + ActorMethodEnvelope res = serializer.deserialize(response, ActorMethodEnvelope.class); + if (res == null) { + return null; + } + + byte[] data = res.getData(); + if (data == null) { + return null; + } + + return this.serializer.deserialize(new String(data, StandardCharsets.UTF_8), clazz); + } catch (IOException e) { + // Wrap it to make Mono happy. + throw new RuntimeException(e); } - - - protected Mono invokeActorMethod(String actorType, String actorId, String methodName, String jsonPayload) { - String url = String.format(Constants.ACTOR_METHOD_RELATIVE_URL_FORMAT, actorType, actorId, methodName); - return this.abstractDaprClient.invokeAPI("PUT", url, jsonPayload); + } + + /** + * Builds the request to invoke an API for Actors. + * + * @param request Request object for the original Actor's method. + * @param Type for the original Actor's method request. + * @return String to be sent to Dapr's API. + * @throws IOException In case it cannot generate String. + */ + private String wrap(final T request) throws IOException { + if (request == null) { + return null; } + String json = this.serializer.serialize(request); + ActorMethodEnvelope req = new ActorMethodEnvelope(); + req.setData(json == null ? EMPTY_DATA : json.getBytes()); + return serializer.serialize(req); + } - - - /** - * {@inheritDoc} - */ - public ActorId getActorId() { - return actorId; - } - - private void setActorId(ActorId actorId) { - this.actorId = actorId; - } - - /** - * {@inheritDoc} - */ - public String getActorType() { - return actorType; - } - - private void setActorType(String actorType) { - this.actorType = actorType; - } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java index 0aa99b7075..7649b4be17 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateSerializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.actors.utils.ObjectSerializer; import java.io.IOException; import java.io.StringWriter; @@ -15,36 +16,19 @@ import java.time.Duration; /** - * Serializes and deserializes an object. + * Serializes and deserializes special objects for Actors. */ -class ActorStateSerializer { +public class ActorStateSerializer extends ObjectSerializer { /** - * Shared Json Factory as per Jackson's documentation, used only for this class. + * {@inheritDoc} */ - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - /** - * Shared Json serializer/deserializer as per Jackson's documentation. - */ - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - /** - * Serializes a given state object into byte array. - * - * @param state State object to be serialized. - * @return Array of bytes[] with the serialized content. - * @throws IOException - */ - String serialize(T state) throws IOException { + @Override + public String serialize(T state) throws IOException { if (state == null) { return null; } - if (state.getClass() == String.class) { - return state.toString(); - } - if (state.getClass() == ActorTimer.class) { // Special serializer for this internal classes. return serialize((ActorTimer) state); @@ -55,96 +39,22 @@ String serialize(T state) throws IOException { return serialize((ActorReminderParams) state); } - if (isPrimitiveOrEquivalent(state.getClass())) { - return state.toString(); - } - - // Not string, not primitive, so it is a complex type: we use JSON for that. - return OBJECT_MAPPER.writeValueAsString(state); + // Is not an special case. + return super.serialize(state); } /** - * Deserializes the byte array into the original object. - * - * @param value String to be parsed. - * @param clazz Type of the object being deserialized. - * @param Generic type of the object being deserialized. - * @return Object of type T. - * @throws IOException + * {@inheritDoc} */ - T deserialize(String value, Class clazz) throws IOException { - if (clazz == String.class) { - return (T) value; - } - + @Override + public T deserialize(String value, Class clazz) throws IOException { if (clazz == ActorReminderParams.class) { // Special serializer for this internal classes. return (T) deserializeActorReminder(value); } - if (isPrimitiveOrEquivalent(clazz)) { - return parse(value, clazz); - } - - if (value == null) { - return (T) null; - } - - // Not string, not primitive, so it is a complex type: we use JSON for that. - return OBJECT_MAPPER.readValue(value, clazz); - } - - /** - * Checks if the class is a primitive or equivalent. - * @param clazz Class to be checked. - * @return True if primitive or equivalent. - */ - private static boolean isPrimitiveOrEquivalent(Class clazz) { - if (clazz == null) { - return false; - } - - return (clazz.isPrimitive() || - (clazz == Boolean.class) || - (clazz == Character.class) || - (clazz == Byte.class) || - (clazz == Short.class) || - (clazz == Integer.class) || - (clazz == Long.class) || - (clazz == Float.class) || - (clazz == Double.class) || - (clazz == Void.class)); - } - - /** - * Parses a given String to the corresponding object defined by class. - * @param value String to be parsed. - * @param clazz Class of the expected result type. - * @param Result type. - * @return Result as corresponding type. - */ - private static T parse(String value, Class clazz) { - if (value == null) { - if (boolean.class == clazz) return (T) Boolean.FALSE; - if (byte.class == clazz) return (T) Byte.valueOf((byte) 0); - if (short.class == clazz) return (T) Short.valueOf((short) 0); - if (int.class == clazz) return (T) Integer.valueOf(0); - if (long.class == clazz) return (T) Long.valueOf(0L); - if (float.class == clazz) return (T) Float.valueOf(0); - if (double.class == clazz) return (T) Double.valueOf(0); - - return null; - } - - if ((Boolean.class == clazz) || (boolean.class == clazz)) return (T) Boolean.valueOf(value); - if ((Byte.class == clazz) || (byte.class == clazz)) return (T) Byte.valueOf(value); - if ((Short.class == clazz) || (short.class == clazz)) return (T) Short.valueOf(value); - if ((Integer.class == clazz) || (int.class == clazz)) return (T) Integer.valueOf(value); - if ((Long.class == clazz) || (long.class == clazz)) return (T) Long.valueOf(value); - if ((Float.class == clazz) || (float.class == clazz)) return (T) Float.valueOf(value); - if ((Double.class == clazz) || (double.class == clazz)) return (T) Double.valueOf(value); - - return null; + // Is not one the special cases. + return super.deserialize(value, clazz); } /** diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java index e4cef9f487..7f4299f65c 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/AppToDaprClientBuilder.java @@ -12,11 +12,6 @@ */ class AppToDaprClientBuilder extends AbstractClientBuilder { - /** - * Default port for Dapr after checking environment variable. - */ - private int port = AppToDaprClientBuilder.GetEnvPortOrDefault(); - /** * Builds an async client. * @@ -25,6 +20,6 @@ class AppToDaprClientBuilder extends AbstractClientBuilder { public AppToDaprAsyncClient buildAsyncClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // TODO: Expose configurations for OkHttpClient or com.microsoft.rest.RestClient. - return new AppToDaprHttpAsyncClient(this.port, builder.build()); + return new AppToDaprHttpAsyncClient(super.getPort(), builder.build()); } } diff --git a/sdk/src/main/java/io/dapr/actors/utils/ObjectSerializer.java b/sdk/src/main/java/io/dapr/actors/utils/ObjectSerializer.java new file mode 100644 index 0000000000..251daae75c --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/utils/ObjectSerializer.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ +package io.dapr.actors.utils; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +/** + * Serializes and deserializes an object. + */ +public class ObjectSerializer { + + /** + * Shared Json Factory as per Jackson's documentation, used only for this class. + */ + protected static final JsonFactory JSON_FACTORY = new JsonFactory(); + + /** + * Shared Json serializer/deserializer as per Jackson's documentation. + */ + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * Serializes a given state object into byte array. + * + * @param state State object to be serialized. + * @return Array of bytes[] with the serialized content. + * @throws IOException + */ + public String serialize(T state) throws IOException { + if (state == null) { + return null; + } + + if (state.getClass() == String.class) { + return state.toString(); + } + + if (isPrimitiveOrEquivalent(state.getClass())) { + return state.toString(); + } + + // Not string, not primitive, so it is a complex type: we use JSON for that. + return OBJECT_MAPPER.writeValueAsString(state); + } + + /** + * Deserializes the byte array into the original object. + * + * @param value String to be parsed. + * @param clazz Type of the object being deserialized. + * @param Generic type of the object being deserialized. + * @return Object of type T. + * @throws IOException + */ + public T deserialize(String value, Class clazz) throws IOException { + if (clazz == String.class) { + return (T) value; + } + + if (isPrimitiveOrEquivalent(clazz)) { + return parse(value, clazz); + } + + if (value == null) { + return (T) null; + } + + // Not string, not primitive, so it is a complex type: we use JSON for that. + return OBJECT_MAPPER.readValue(value, clazz); + } + + /** + * Checks if the class is a primitive or equivalent. + * @param clazz Class to be checked. + * @return True if primitive or equivalent. + */ + private static boolean isPrimitiveOrEquivalent(Class clazz) { + if (clazz == null) { + return false; + } + + return (clazz.isPrimitive() || + (clazz == Boolean.class) || + (clazz == Character.class) || + (clazz == Byte.class) || + (clazz == Short.class) || + (clazz == Integer.class) || + (clazz == Long.class) || + (clazz == Float.class) || + (clazz == Double.class) || + (clazz == Void.class)); + } + + /** + * Parses a given String to the corresponding object defined by class. + * @param value String to be parsed. + * @param clazz Class of the expected result type. + * @param Result type. + * @return Result as corresponding type. + */ + private static T parse(String value, Class clazz) { + if (value == null) { + if (boolean.class == clazz) return (T) Boolean.FALSE; + if (byte.class == clazz) return (T) Byte.valueOf((byte) 0); + if (short.class == clazz) return (T) Short.valueOf((short) 0); + if (int.class == clazz) return (T) Integer.valueOf(0); + if (long.class == clazz) return (T) Long.valueOf(0L); + if (float.class == clazz) return (T) Float.valueOf(0); + if (double.class == clazz) return (T) Double.valueOf(0); + + return null; + } + + if ((Boolean.class == clazz) || (boolean.class == clazz)) return (T) Boolean.valueOf(value); + if ((Byte.class == clazz) || (byte.class == clazz)) return (T) Byte.valueOf(value); + if ((Short.class == clazz) || (short.class == clazz)) return (T) Short.valueOf(value); + if ((Integer.class == clazz) || (int.class == clazz)) return (T) Integer.valueOf(value); + if ((Long.class == clazz) || (long.class == clazz)) return (T) Long.valueOf(value); + if ((Float.class == clazz) || (float.class == clazz)) return (T) Float.valueOf(value); + if ((Double.class == clazz) || (double.class == clazz)) return (T) Double.valueOf(value); + + return null; + } +} diff --git a/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java b/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java index dc7619ce20..8c49d664b2 100644 --- a/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java +++ b/sdk/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java @@ -1,6 +1,7 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; +import io.dapr.actors.runtime.ActorStateSerializer; import org.junit.Assert; import org.junit.Test; @@ -9,8 +10,12 @@ public class ActorProxyImplTest { @Test() public void constructorActorProxyTest() { final ActorProxyHttpAsyncClient actorProxyAsyncClient = (ActorProxyHttpAsyncClient)new ActorProxyClientBuilder().buildAsyncClient(); - final ActorProxyImpl actorProxy= new ActorProxyImpl(new ActorId("100"),"myActorType",actorProxyAsyncClient); - Assert.assertEquals(actorProxy.getActorId().getStringId(),"100"); + final ActorProxyImpl actorProxy= new ActorProxyImpl( + "myActorType", + new ActorId("100"), + new ActorStateSerializer(), + actorProxyAsyncClient); + Assert.assertEquals(actorProxy.getActorId().toString(),"100"); Assert.assertEquals(actorProxy.getActorType(),"myActorType"); } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java index 666d3b787b..6e61b7e669 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java @@ -2,6 +2,7 @@ import org.junit.Assert; import org.junit.Test; + import java.time.Duration; public class ActorReminderParamsTest { diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java index 2123d30733..b620bf59ae 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java @@ -15,9 +15,7 @@ import java.io.IOException; import java.util.Objects; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java index 2a907bf72e..c9b23f00a0 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java @@ -9,7 +9,7 @@ import org.junit.Assert; import org.junit.Test; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; /** * Testing the default constructor of an Actor.