From 2a21891fd3cab301231580fb362d987e320afa2a Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 16 Dec 2019 18:03:49 -0800 Subject: [PATCH] ActorService + DaprStateProvider + some more. --- .../java/io/dapr/actors/runtime/Actor.java | 3 +- .../io/dapr/actors/runtime/ActorFactory.java | 24 ++++ .../io/dapr/actors/runtime/ActorRuntime.java | 15 +- .../io/dapr/actors/runtime/ActorService.java | 15 +- .../dapr/actors/runtime/ActorServiceImpl.java | 67 +++++++++ .../dapr/actors/runtime/ActorStateChange.java | 67 +++++++++ .../actors/runtime/ActorStateChangeKind.java | 54 ++++++++ .../runtime/ActorStateProviderSerializer.java | 10 +- .../dapr/actors/runtime/ActorTimerImpl.java | 72 +++++++--- .../actors/runtime/ActorTypeInformation.java | 10 +- .../runtime/DaprStateAsyncProvider.java | 128 ++++++++++++++++++ .../actors/runtime/DefaultActorFactory.java | 52 +++++++ .../actors/runtime/ActorTimerImplTest.java | 14 +- .../runtime/DefaultActorFactoryTest.java | 67 +++++++++ 14 files changed, 552 insertions(+), 46 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/ActorStateChangeKind.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java create mode 100644 sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java create mode 100644 sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java diff --git a/sdk/src/main/java/io/dapr/actors/runtime/Actor.java b/sdk/src/main/java/io/dapr/actors/runtime/Actor.java index ea13bd6448..ba80afc6c6 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/Actor.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/Actor.java @@ -5,8 +5,7 @@ package io.dapr.actors.runtime; /** - * TODO - this is the interface user Actor methods should implement to receive - * calls. + * Base interface for inheriting reliable actor interfaces. */ public interface Actor { } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java new file mode 100644 index 0000000000..8a34af0763 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import io.dapr.actors.ActorId; + +/** + * Creates an actor of a given type. + * @param Actor Type to be created. + */ +@FunctionalInterface +public interface ActorFactory { + + /** + * Creates an Actor. + * @param actorService Actor Service. + * @param actorId Actor Id. + * @return Actor or null it failed. + */ + T createActor(ActorService actorService, ActorId actorId); +} 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 be4deb584a..acc56190df 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -8,7 +8,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.function.Function; /** * Contains methods to register actor types. Registering the types allows the @@ -88,18 +87,16 @@ public void RegisterActor(Class clazz) { * Registers an actor with the runtime. * * @param clazz The type of actor. - * @param actorServiceFactory An optional delegate to create actor service. + * @param actorFactory An optional factory to create actors. * This can be used for dependency injection into actors. */ - public void RegisterActor(Class clazz, Function actorServiceFactory) { + public void RegisterActor(Class clazz, ActorFactory actorFactory) { ActorTypeInformation actorTypeInfo = ActorTypeInformation.create(clazz); - ActorService actorService; - if (actorServiceFactory != null) { - actorService = actorServiceFactory.apply(actorTypeInfo); - } else { - actorService = new ActorService(actorTypeInfo); - } + ActorFactory actualActorFactory = actorFactory != null ? actorFactory : new DefaultActorFactory(actorTypeInfo); + // TODO: Refactor into a Builder class. + DaprStateAsyncProvider stateProvider = new DaprStateAsyncProvider(this.appToDaprAsyncClient, new ActorStateProviderSerializer()); + ActorService actorService = new ActorServiceImpl(actorTypeInfo, stateProvider, actualActorFactory); // Create ActorManagers, override existing entry if registered again. synchronized (this.actorManagers) { 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 385ff32884..124e51f04c 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorService.java @@ -1,9 +1,16 @@ package io.dapr.actors.runtime; -// stub -public class ActorService { +import io.dapr.actors.ActorId; - public ActorService(ActorTypeInformation actorTypeInformation) { +/** + * 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. + */ + AbstractActor 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 new file mode 100644 index 0000000000..18f67dc10e --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorServiceImpl.java @@ -0,0 +1,67 @@ +/* + * 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; + } + + /** + * Creates an {@link Actor} for this service. + * @param actorId Identifier for the Actor to be created. + * @return New {@link Actor} instance. + */ + @Override + public AbstractActor createActor(ActorId actorId) { + return this.actorFactory.createActor(this, actorId); + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java new file mode 100644 index 0000000000..799a9cdb42 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChange.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import java.io.IOException; + +/** + * Represents a state change for an actor. + * @param Type of the value being changed. + */ +public final class ActorStateChange { + + /** + * Name of the state being changed. + */ + private final String stateName; + + /** + * New value for the state being changed. + */ + private final T value; + + /** + * Type of change {@link ActorStateChangeKind}. + */ + private final ActorStateChangeKind changeKind; + + /** + * Creates an actor state change. + * @param stateName Name of the state being changed. + * @param value New value for the state being changed. + * @param changeKind Kind of change. + */ + ActorStateChange(String stateName, T value, ActorStateChangeKind changeKind) { + this.stateName = stateName; + this.value = value; + this.changeKind = changeKind; + } + + /** + * Gets the name of the state being changed. + * @return Name of the state. + */ + String getStateName() { + return stateName; + } + + /** + * Gets the new value of the state being changed. + * @return New value. + */ + T getValue() { + return value; + } + + /** + * Gets the kind of change. + * @return Kind of change. + */ + ActorStateChangeKind getChangeKind() { + return changeKind; + } + +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChangeKind.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChangeKind.java new file mode 100644 index 0000000000..103b46f09b --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateChangeKind.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +/** + * Represents an actor's state change + */ +public enum ActorStateChangeKind { + + /** + * No change in state. + */ + NONE(""), + + /** + * State needs to be added. + */ + ADD("upsert"), + + /** + * State needs to be updated. + */ + UPDATE("upsert"), + + /** + * State needs to be removed. + */ + REMOVE("delete"); + + /** + * Operation name in Dapr's state management. + */ + private final String daprStateChangeOperation; + + /** + * Creates a kind of actor state change. + * @param daprStateChangeOperation Equivalent operation name Dapr's state management + */ + ActorStateChangeKind(String daprStateChangeOperation) { + this.daprStateChangeOperation = daprStateChangeOperation; + } + + /** + * Gets equivalent operation name Dapr's state management + * @return Equivalent operation name Dapr's state management + */ + String getDaprStateChangeOperation() { + return daprStateChangeOperation; + } + +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateProviderSerializer.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateProviderSerializer.java index 3a52c45fc8..75d940d3db 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorStateProviderSerializer.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorStateProviderSerializer.java @@ -24,21 +24,21 @@ class ActorStateProviderSerializer { * @return Array of bytes[] with the serialized content. * @throws IOException */ - byte[] serialize(Object state) throws IOException { - return OBJECT_MAPPER.writeValueAsBytes(state); + String serialize(Object state) throws IOException { + return OBJECT_MAPPER.writeValueAsString(state); } /** * Deserializes the byte array into the original object. * - * @param buffer Array of bytes to be parsed. + * @param json 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(byte[] buffer, Class clazz) throws IOException { - return OBJECT_MAPPER.readValue(buffer, clazz); + T deserialize(String json, Class clazz) throws IOException { + return OBJECT_MAPPER.readValue(json, clazz); } } diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java index ebd1f9d155..33d3a23fe9 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTimerImpl.java @@ -1,6 +1,10 @@ package io.dapr.actors.runtime; -import org.json.JSONObject; +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.time.Duration; import java.util.function.Function; @@ -9,23 +13,55 @@ */ class ActorTimerImpl implements ActorTimer { + /** + * Shared Json serializer/deserializer as per Jackson's documentation, used only for this class. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * 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 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. + * @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) { + public ActorTimerImpl(AbstractActor owner, + String timerName, + Function asyncCallback, + Object state, + Duration dueTime, + Duration period) { this.owner = owner; this.name = timerName; this.asyncCallback = asyncCallback; @@ -36,6 +72,7 @@ public ActorTimerImpl(AbstractActor owner, String timerName, Function getAsyncCallback() { return this.asyncCallback; @@ -60,6 +98,7 @@ public Function getAsyncCallback() { /** * Gets the periodic time when timer will be invoked. + * * @return Periodic time as Duration when timer will be invoked. */ public Duration getPeriod() { @@ -67,7 +106,6 @@ public Duration getPeriod() { } /** - * * @return Gets state containing information to be used by the callback method, or null. */ public Object getState() { @@ -75,14 +113,14 @@ public Object getState() { } /** + * Generates JSON representation of this timer. * - * @return + * @return JSON. */ - String serialize() - { - JSONObject j = new JSONObject(); - j.put("dueTime", ConverterUtils.ConvertDurationToDaprFormat(this.getDueTime())); - j.put("period", ConverterUtils.ConvertDurationToDaprFormat(this.getPeriod())); - return j.toString(); + String serialize() throws IOException { + ObjectNode objectNode = OBJECT_MAPPER.createObjectNode(); + objectNode.put("dueTime", ConverterUtils.ConvertDurationToDaprFormat(this.dueTime)); + objectNode.put("period", ConverterUtils.ConvertDurationToDaprFormat(this.period)); + return OBJECT_MAPPER.writeValueAsString(objectNode); } } 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 97564a5cc7..55f4b7a6aa 100644 --- a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java @@ -12,7 +12,7 @@ /** * Contains the information about the class implementing an actor. */ -final class ActorTypeInformation { +final class ActorTypeInformation { /** * Actor type's name. @@ -22,7 +22,7 @@ final class ActorTypeInformation { /** * Actor's implementation class. */ - private final Class implementationClass; + private final Class implementationClass; /** * Actor's immediate interfaces. @@ -49,7 +49,7 @@ final class ActorTypeInformation { * @param remindable Whether Actor type is remindable. */ private ActorTypeInformation(String name, - Class implementationClass, + Class implementationClass, Collection interfaces, boolean abstractClass, boolean remindable) { @@ -74,7 +74,7 @@ public String getName() { * * @return The {@link Class} of implementing the actor. */ - public Class getImplementationClass() { + public Class getImplementationClass() { return this.implementationClass; } @@ -129,7 +129,7 @@ public static ActorTypeInformation tryCreate(Class actorClass) { * ActorTypeInformation for. * @return {@link #ActorTypeInformation} created from actorType. */ - public static ActorTypeInformation create(Class actorClass) { + public static ActorTypeInformation create(Class actorClass) { if (!ActorTypeUtilities.isActor(actorClass)) { throw new IllegalArgumentException( String.format( diff --git a/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java b/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java new file mode 100644 index 0000000000..15b4aab8b4 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.util.Collection; + +/** + * State Provider to interact with Dapr runtime to handle state. + */ +class DaprStateAsyncProvider { + + /** + * Shared Json serializer/deserializer as per Jackson's documentation, used only for this class. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final AppToDaprAsyncClient daprAsyncClient; + + private final ActorStateProviderSerializer serializer; + + DaprStateAsyncProvider(AppToDaprAsyncClient daprAsyncClient, ActorStateProviderSerializer serializer) { + this.daprAsyncClient = daprAsyncClient; + this.serializer = serializer; + } + + Mono load(String actorType, String actorId, String stateName, Class clazz) { + Mono result = this.daprAsyncClient.getState(actorType, actorId, stateName); + + return result.map(s -> { + if (s == null) { + return (T)null; + } + + try { + return this.serializer.deserialize(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + Mono contains(String actorType, String actorId, String stateName) { + Mono result = this.daprAsyncClient.getState(actorType, actorId, stateName); + + return result.map(s -> { + return (s != null) && (s.length() > 0); + }); + } + + /** + * Saves state changes transactionally. + * [ + * { + * "operation": "upsert", + * "request": { + * "key": "key1", + * "value": "myData" + * } + * }, + * { + * "operation": "delete", + * "request": { + * "key": "key2" + * } + * } + * ] + * @param actorType Name of the actor being changed. + * @param actorId Identifier of the actor being changed. + * @param stateChanges Collection of changes to be performed transactionally. + * @return Void. + */ + Mono apply(String actorType, String actorId, Collection stateChanges) + { + if ((stateChanges == null) || stateChanges.isEmpty()) { + return Mono.just(null); + } + + // Constructing the JSON "manually" to avoid creating transient classes to be parsed. + ArrayNode operations = OBJECT_MAPPER.createArrayNode(); + for (ActorStateChange stateChange : stateChanges) { + if ((stateChange == null) || (stateChange.getChangeKind() == null)) { + continue; + } + + String operationName = stateChange.getChangeKind().getDaprStateChangeOperation(); + if ((operationName == null) || (operationName.length() == 0)) { + continue; + } + + try { + ObjectNode operation = OBJECT_MAPPER.createObjectNode(); + operation.set("operation", operation.textNode(operationName)); + ObjectNode request = OBJECT_MAPPER.createObjectNode(); + request.put("key", stateChange.getStateName()); + if ((stateChange.getChangeKind() == ActorStateChangeKind.UPDATE) || (stateChange.getChangeKind() == ActorStateChangeKind.ADD)) { + request.put("value", this.serializer.serialize(stateChange.getValue())); + } + + operations.add(operation); + } catch (IOException e) { + e.printStackTrace(); + return Mono.error(e); + } + } + + if (operations.size() == 0) { + // No-op since there is no operation to be performed. + Mono.just(null); + } + + try { + return this.daprAsyncClient.saveStateTransactionally(actorType, actorId, OBJECT_MAPPER.writeValueAsString(operations)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return Mono.error(e); + } + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java new file mode 100644 index 0000000000..94ac51f206 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import io.dapr.actors.ActorId; + +import java.lang.reflect.Constructor; + +/** + * Instantiates actors by calling their constructor with {@link ActorService} and {@link ActorId}. + * @param Actor Type to be created. + */ +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) { + try { + if (this.actorTypeInformation == null) { + return null; + } + + Constructor constructor = this + .actorTypeInformation + .getImplementationClass() + .getConstructor(ActorService.class, ActorId.class); + return constructor.newInstance(actorService, actorId); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java index 52bdc5c437..5bf6a48f5b 100644 --- a/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorTimerImplTest.java @@ -1,14 +1,18 @@ package io.dapr.actors.runtime; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Test; +import java.io.IOException; import java.time.Duration; public class ActorTimerImplTest { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @Test - public void serialize() { + public void serialize() throws IOException { Duration dueTime = Duration.ZERO .plusMinutes(7) .plusSeconds(17); @@ -27,11 +31,12 @@ public void serialize() { String s = timer.serialize(); String expected = "{\"period\":\"1h0m3s0ms\",\"dueTime\":\"0h7m17s0ms\"}"; - Assert.assertEquals(expected, s); + // Deep comparison via JsonNode.equals method. + Assert.assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(s)); } @Test - public void serializeWithOneTimePeriod() { + public void serializeWithOneTimePeriod() throws IOException { Duration dueTime = Duration.ZERO .plusMinutes(7) .plusSeconds(17); @@ -52,6 +57,7 @@ public void serializeWithOneTimePeriod() { // 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\"}"; - Assert.assertEquals(expected, s); + // Deep comparison via JsonNode.equals method. + Assert.assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(s)); } } diff --git a/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java new file mode 100644 index 0000000000..642d076fdc --- /dev/null +++ b/sdk/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import io.dapr.actors.ActorId; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.*; + +/** + * Testing the default constructor of an Actor. + */ +public class DefaultActorFactoryTest { + + /** + * A compliant implementation of Actor to be used in the tests below. + */ + static class MyActor extends AbstractActor implements Actor { + + ActorService actorService; + + ActorId actorId; + + public MyActor(ActorService actorService, ActorId actorId) { + this.actorService = actorService; + this.actorId = actorId; + } + } + + /** + * A non-compliant implementation of Actor to be used in the tests below. + */ + static class InvalidActor extends AbstractActor { + } + + /** + * Happy case. + */ + @Test + public void happyActor() { + DefaultActorFactory factory = new DefaultActorFactory(ActorTypeInformation.tryCreate(MyActor.class)); + + ActorId actorId = ActorId.createRandom(); + MyActor actor = factory.createActor(mock(ActorService.class), actorId); + + Assert.assertEquals(actorId, actor.actorId); + Assert.assertNotNull(actor.actorService); + } + + /** + * Class is not an actor. + */ + @Test + public void noValidConstructor() { + DefaultActorFactory factory = new DefaultActorFactory(ActorTypeInformation.tryCreate(InvalidActor.class)); + + ActorId actorId = ActorId.createRandom(); + InvalidActor actor = factory.createActor(mock(ActorService.class), actorId); + + Assert.assertNull(actor); + } + +}