From b2675d2eb601b1134e1fe91f044e2dfbcfc7678d Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Mon, 22 Nov 2021 22:46:10 +0100 Subject: [PATCH 01/19] Make ActorTimerParams fields final Signed-off-by: Giovanni van der Schelde --- .../dapr/actors/runtime/ActorTimerParams.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index a3d1bb755a..41bc459e36 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -17,37 +17,36 @@ /** * Represents the timer set on an Actor, to be called once after due time and then every period. - * */ final class ActorTimerParams { /** * Name of the method to be called for this timer. */ - private String callback; + private final String callback; /** * State to be sent in the timer. */ - private byte[] data; + private final byte[] data; /** * Due time for the timer's first trigger. */ - private Duration dueTime; + private final Duration dueTime; /** * Period at which the timer will be triggered. */ - private Duration period; + private final Duration period; /** * Instantiates a new Actor Timer. * - * @param callback The name of the method to be called for this timer. - * @param data The state 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 callback The name of the method to be called for this timer. + * @param data The state 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. */ ActorTimerParams(String callback, byte[] data, @@ -87,9 +86,9 @@ public Duration getPeriod() { } /** - * Gets state containing information to be used by the callback method, or null. + * 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. + * @return State containing information to be used by the callback method, or null. */ public byte[] getData() { return this.data; From 6c7ae75563ef9edde6f7f2dc3a0a4f4f562788b7 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Mon, 22 Nov 2021 23:15:12 +0100 Subject: [PATCH 02/19] Add RepeatedDuration to ActorTimerParams Signed-off-by: Giovanni van der Schelde --- .../java/io/dapr/actors/RepeatedDuration.java | 78 +++++++++++++++++++ .../dapr/actors/runtime/ActorTimerParams.java | 55 ++++++++++++- .../io/dapr/actors/RepeatedDurationTest.java | 19 +++++ .../actors/runtime/ActorTimerParamsTest.java | 41 ++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java diff --git a/sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java b/sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java new file mode 100644 index 0000000000..50cb0ae416 --- /dev/null +++ b/sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java @@ -0,0 +1,78 @@ +package io.dapr.actors; + +import java.time.Duration; +import java.util.Optional; + +public final class RepeatedDuration { + + /** + * The minimum amount of repetitions TTL and Period can have. + */ + private static final Integer MIN_AMOUNT_REPETITIONS = 0; + + /** + * The duration. + */ + private final Duration duration; + + /** + * The amount of times the duration will be repeated. + * This is an optional field. + */ + private final Integer repetitions; + + /** + * Instantiates a new instance for a repeated duration + * + * @param duration The interval until an action + */ + public RepeatedDuration(Duration duration) { + this(duration, null); + } + + /** + * Instantiates a new instance for a repeated duration + * + * @param duration The interval until an action + * @param repetitions The amount of times to invoke the action. May be null. + */ + public RepeatedDuration(Duration duration, Integer repetitions) { + validateDuration(duration); + validateRepetitions(repetitions); + this.duration = duration; + this.repetitions = repetitions; + } + + /** + * Validates the duration. + * + * @param duration The duration to validate. + */ + private static void validateDuration(Duration duration) { + if (duration == null) { + throw new IllegalArgumentException("Duration can not be null."); + } + } + + /** + * Validates the repetitions of TTL and Period. + * + * @param repetitions The amount of repetitions checked. + */ + private static void validateRepetitions(Integer repetitions) { + if (repetitions != null && repetitions < MIN_AMOUNT_REPETITIONS) { + String message = String.format( + "argName: Amount of repetitions - specified value must be greater than %s", + MIN_AMOUNT_REPETITIONS); + throw new IllegalArgumentException(message); + } + } + + public Duration getDuration() { + return duration; + } + + public Optional getRepetitions() { + return Optional.ofNullable(repetitions); + } +} diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index 41bc459e36..d43d8b1512 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -13,7 +13,10 @@ package io.dapr.actors.runtime; +import io.dapr.actors.RepeatedDuration; + import java.time.Duration; +import java.util.Optional; /** * Represents the timer set on an Actor, to be called once after due time and then every period. @@ -38,7 +41,13 @@ final class ActorTimerParams { /** * Period at which the timer will be triggered. */ - private final Duration period; + private final RepeatedDuration period; + + /** + * Time at which or time interval after which the timer will be expired and deleted. + * If ttl is omitted, no restrictions are applied. + */ + private final RepeatedDuration ttl; /** * Instantiates a new Actor Timer. @@ -52,10 +61,43 @@ final class ActorTimerParams { byte[] data, Duration dueTime, Duration period) { + this(callback, data, dueTime, new RepeatedDuration(period), null); + } + + /** + * Instantiates a new Actor Timer. + * + * @param callback The name of the method to be called for this timer. + * @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. + */ + ActorTimerParams(String callback, + byte[] data, + Duration dueTime, + RepeatedDuration period) { + this(callback, data, dueTime, period, null); + } + + /** + * Instantiates a new Actor Timer. + * + * @param callback The name of the method to be called for this timer. + * @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. + * @param ttl Time at which or time interval after which the reminder will be expired and deleted. + */ + ActorTimerParams(String callback, + byte[] data, + Duration dueTime, + RepeatedDuration period, + RepeatedDuration ttl) { this.callback = callback; this.data = data; this.dueTime = dueTime; this.period = period; + this.ttl = ttl; } /** @@ -82,7 +124,7 @@ public Duration getDueTime() { * @return Periodic time as Duration when timer will be invoked. */ public Duration getPeriod() { - return this.period; + return this.period.getDuration(); } /** @@ -94,4 +136,13 @@ public byte[] getData() { return this.data; } + /** + * Gets the time at which or time interval after which the timer will be expired and deleted. + * This is an optional field and may return null. + * + * @return Time at which or time interval after which the timer will be expired and deleted, or null. + */ + public Optional getTtl() { + return Optional.ofNullable(this.ttl); + } } diff --git a/sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java b/sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java new file mode 100644 index 0000000000..c5c4090aad --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java @@ -0,0 +1,19 @@ +package io.dapr.actors; + +import org.junit.Test; + +import java.time.Duration; + + +public class RepeatedDurationTest { + + @Test(expected = IllegalArgumentException.class) + public void invalidAmountOfRepetitions() { + new RepeatedDuration(Duration.ZERO, -1); + } + + @Test(expected = IllegalArgumentException.class) + public void durationCanNotBeNull() { + new RepeatedDuration(null); + } +} diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java new file mode 100644 index 0000000000..4f733f69a8 --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java @@ -0,0 +1,41 @@ +package io.dapr.actors.runtime; + +import io.dapr.actors.RepeatedDuration; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.time.Duration; + +public class ActorTimerParamsTest { + private static final ActorObjectSerializer SERIALIZER = new ActorObjectSerializer(); + + @Test + public void ttlShouldBeOptional() throws IOException { + // Arrange + ActorTimerParams params = + new ActorTimerParams("callback", new byte[1], Duration.ZERO, new RepeatedDuration(Duration.ZERO, 1)); + + // Act + byte[] serialized = SERIALIZER.serialize(params); + ActorTimerParams deserialized = SERIALIZER.deserialize(serialized, ActorTimerParams.class); + + // Assert + Assert.assertFalse(deserialized.getTtl().isPresent()); + } + + @Test + public void ttlRepetitionsNotRequired() throws IOException { + // Arrange + ActorTimerParams params = + new ActorTimerParams("callback", new byte[1], Duration.ZERO, new RepeatedDuration(Duration.ZERO)); + + // Act + byte[] serialized = SERIALIZER.serialize(params); + ActorTimerParams deserialized = SERIALIZER.deserialize(serialized, ActorTimerParams.class); + + // Assert + Assert.assertTrue(deserialized.getTtl().isPresent()); + Assert.assertFalse(deserialized.getTtl().get().getRepetitions().isPresent()); + } +} From 75ac62b680be3ff04a81350db16f621c5278e116 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Mon, 22 Nov 2021 23:42:37 +0100 Subject: [PATCH 03/19] Refactor registerActorTimer() to expose ttl Signed-off-by: Giovanni van der Schelde --- .../io/dapr/actors/runtime/AbstractActor.java | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 9febda8f9a..aba0658fc1 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -13,8 +13,10 @@ package io.dapr.actors.runtime; +import com.google.common.base.Strings; import io.dapr.actors.ActorId; import io.dapr.actors.ActorTrace; +import io.dapr.actors.RepeatedDuration; import reactor.core.publisher.Mono; import java.io.IOException; @@ -147,23 +149,70 @@ protected Mono registerReminder( * @return Asynchronous result with timer's name. */ protected Mono registerActorTimer( - String timerName, - String callback, - T state, - Duration dueTime, - Duration period) { + String timerName, + String callback, + T state, + Duration dueTime, + Duration period) { + return registerActorTimer(timerName, callback, state, dueTime, new RepeatedDuration(period)); + } + + /** + * 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 callback Name of the method to be called. + * @param state State 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 to be passed in to timer. + * @return Asynchronous result with timer's name. + */ + protected Mono registerActorTimer( + String timerName, + String callback, + T state, + Duration dueTime, + RepeatedDuration period) { + return registerActorTimer(timerName, callback, state, dueTime, period, null); + } + + /** + * 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 callback Name of the method to be called. + * @param state State 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 ttl The time at which or time interval after which the timer will be expired and deleted. + * @param Type for the state to be passed in to timer. + * @return Asynchronous result with timer's name. + */ + protected Mono registerActorTimer( + String timerName, + String callback, + T state, + Duration dueTime, + RepeatedDuration period, + RepeatedDuration ttl) { try { - if ((callback == null) || callback.isEmpty()) { + if (Strings.isNullOrEmpty(callback)) { throw new IllegalArgumentException("Timer requires a callback function."); } - String name = timerName; - if ((timerName == null) || (timerName.isEmpty())) { - name = String.format("%s_Timer_%s", this.id.toString(), UUID.randomUUID().toString()); - } + String name = Strings.isNullOrEmpty(timerName) + ? String.format("%s_Timer_%s", this.id.toString(), UUID.randomUUID()) : timerName; byte[] data = this.actorRuntimeContext.getObjectSerializer().serialize(state); - ActorTimerParams actorTimer = new ActorTimerParams(callback, data, dueTime, period); + ActorTimerParams actorTimer = + new ActorTimerParams(callback, data, dueTime, period, ttl); return this.actorRuntimeContext.getDaprClient().registerTimer( this.actorRuntimeContext.getActorTypeInformation().getName(), From 4189c30d38270fdfabd712f6e74f67782e77eec3 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Mon, 22 Nov 2021 23:44:50 +0100 Subject: [PATCH 04/19] Apply project styling Signed-off-by: Giovanni van der Schelde --- .../io/dapr/actors/runtime/AbstractActor.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java index aba0658fc1..478b1c957f 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -72,9 +72,9 @@ protected AbstractActor(ActorRuntimeContext runtimeContext, ActorId id) { this.actorRuntimeContext = runtimeContext; this.id = id; this.actorStateManager = new ActorStateManager( - runtimeContext.getStateProvider(), - runtimeContext.getActorTypeInformation().getName(), - id); + runtimeContext.getStateProvider(), + runtimeContext.getActorTypeInformation().getName(), + id); this.actorTrace = runtimeContext.getActorTrace(); this.started = false; } @@ -117,18 +117,18 @@ protected ActorStateManager getActorStateManager() { * @return Asynchronous void response. */ protected Mono registerReminder( - String reminderName, - T state, - Duration dueTime, - Duration period) { + String reminderName, + T state, + Duration dueTime, + Duration period) { try { byte[] data = this.actorRuntimeContext.getObjectSerializer().serialize(state); ActorReminderParams params = new ActorReminderParams(data, dueTime, period); return this.actorRuntimeContext.getDaprClient().registerReminder( - this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.toString(), - reminderName, - params); + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.toString(), + reminderName, + params); } catch (IOException e) { return Mono.error(e); } @@ -232,9 +232,9 @@ protected Mono registerActorTimer( */ protected Mono unregisterTimer(String timerName) { return this.actorRuntimeContext.getDaprClient().unregisterTimer( - this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.toString(), - timerName); + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.toString(), + timerName); } /** @@ -245,9 +245,9 @@ protected Mono unregisterTimer(String timerName) { */ protected Mono unregisterReminder(String reminderName) { return this.actorRuntimeContext.getDaprClient().unregisterReminder( - this.actorRuntimeContext.getActorTypeInformation().getName(), - this.id.toString(), - reminderName); + this.actorRuntimeContext.getActorTypeInformation().getName(), + this.id.toString(), + reminderName); } /** @@ -326,8 +326,8 @@ Mono onActivateInternal() { this.actorTrace.writeInfo(TRACE_TYPE, this.id.toString(), "Activating ..."); this.resetState(); }).then(this.onActivate()) - .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Activated")) - .then(this.saveState()); + .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Activated")) + .then(this.saveState()); } /** @@ -339,8 +339,8 @@ Mono onDeactivateInternal() { this.actorTrace.writeInfo(TRACE_TYPE, this.id.toString(), "Deactivating ..."); return Mono.fromRunnable(() -> this.resetState()) - .then(this.onDeactivate()) - .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Deactivated")); + .then(this.onDeactivate()) + .then(this.doWriteInfo(TRACE_TYPE, this.id.toString(), "Deactivated")); } /** @@ -371,10 +371,10 @@ Mono onPostActorMethodInternal(ActorMethodContext actorMethodContext) { throw new IllegalStateException("Cannot complete a method before starting a call."); } }).then(this.onPostActorMethod(actorMethodContext)) - .then(this.saveState()) - .then(Mono.fromRunnable(() -> { - this.started = false; - })); + .then(this.saveState()) + .then(Mono.fromRunnable(() -> { + this.started = false; + })); } /** From fe3af08bdcc9fa2efe9a382a95e0b4f5b4b9a806 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Tue, 23 Nov 2021 00:17:13 +0100 Subject: [PATCH 05/19] Move RepeatedDuration to Utils package Signed-off-by: Giovanni van der Schelde --- .../src/main/java/io/dapr/actors/runtime/AbstractActor.java | 2 +- .../src/main/java/io/dapr/actors/runtime/ActorTimerParams.java | 2 +- .../test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java | 2 +- .../src/main/java/io/dapr/utils}/RepeatedDuration.java | 2 +- .../src/test/java/io/dapr/utils}/RepeatedDurationTest.java | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) rename {sdk-actors/src/main/java/io/dapr/actors => sdk/src/main/java/io/dapr/utils}/RepeatedDuration.java (98%) rename {sdk-actors/src/test/java/io/dapr/actors => sdk/src/test/java/io/dapr/utils}/RepeatedDurationTest.java (85%) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 478b1c957f..91fd4212e8 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -16,7 +16,7 @@ import com.google.common.base.Strings; import io.dapr.actors.ActorId; import io.dapr.actors.ActorTrace; -import io.dapr.actors.RepeatedDuration; +import io.dapr.utils.RepeatedDuration; import reactor.core.publisher.Mono; import java.io.IOException; diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index d43d8b1512..3fe12f36ac 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -13,7 +13,7 @@ package io.dapr.actors.runtime; -import io.dapr.actors.RepeatedDuration; +import io.dapr.utils.RepeatedDuration; import java.time.Duration; import java.util.Optional; diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java index 4f733f69a8..5fc7ce90b0 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java @@ -1,6 +1,6 @@ package io.dapr.actors.runtime; -import io.dapr.actors.RepeatedDuration; +import io.dapr.utils.RepeatedDuration; import org.junit.Assert; import org.junit.Test; diff --git a/sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java similarity index 98% rename from sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java rename to sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index 50cb0ae416..930ee86959 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -1,4 +1,4 @@ -package io.dapr.actors; +package io.dapr.utils; import java.time.Duration; import java.util.Optional; diff --git a/sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java similarity index 85% rename from sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java rename to sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java index c5c4090aad..5dc89f5fee 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/RepeatedDurationTest.java +++ b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java @@ -1,5 +1,6 @@ -package io.dapr.actors; +package io.dapr.utils; +import io.dapr.utils.RepeatedDuration; import org.junit.Test; import java.time.Duration; From 8f85c8e843b49e36b20afb6eee60705c09cc4937 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Tue, 23 Nov 2021 00:30:37 +0100 Subject: [PATCH 06/19] Add RepeatedDuration to String convertion Signed-off-by: Giovanni van der Schelde --- .../java/io/dapr/utils/DurationUtils.java | 20 +++++++++++ .../utils/Iso8601TestsWithRepetitions.java | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java diff --git a/sdk/src/main/java/io/dapr/utils/DurationUtils.java b/sdk/src/main/java/io/dapr/utils/DurationUtils.java index 9a4614b3f2..7bce2845fc 100644 --- a/sdk/src/main/java/io/dapr/utils/DurationUtils.java +++ b/sdk/src/main/java/io/dapr/utils/DurationUtils.java @@ -89,6 +89,26 @@ public static String convertDurationToDaprFormat(Duration value) { return stringValue; } + /** + * This method uses the default {@link Duration#parse(CharSequence)} and {@link Duration#toString()} methods that supports the ISO-8601 standard. + * It does however not support repetitions. Therefore this method will parse the repetitions as well. + * + * @param repeatedDuration {@link RepeatedDuration} to parse to ISO-8601 format. + * @return String containing the parsed {@link RepeatedDuration} to the ISO-8601 format, possibly with repetitions. + */ + public static String convertRepeatedDurationToIso8601RepetitionFormat(RepeatedDuration repeatedDuration) { + StringBuilder sb = new StringBuilder(); + + if (repeatedDuration.getRepetitions().isPresent()) { + sb.append(String.format("R%d/", repeatedDuration.getRepetitions().get())); + } + + // Duration.ToString() returns the ISO-8601 representation of the duration. + sb.append(repeatedDuration.getDuration().toString()); + + return sb.toString(); + } + /** * Helper to get the "days" part of the Duration. For example if the duration is 26 hours, this returns 1. * diff --git a/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java b/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java new file mode 100644 index 0000000000..25a153ea12 --- /dev/null +++ b/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java @@ -0,0 +1,33 @@ +package io.dapr.utils; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.Duration; + +public class Iso8601TestsWithRepetitions { + + @Test + public void toStringWithRepetitions() { + // Arrange + RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4), 2); + + // Act + String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); + + // Assert + Assert.assertEquals("R2/PT4H", result); + } + + @Test + public void toStringNoRepetitions() { + // Arrange + RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4)); + + // Act + String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); + + // Assert + Assert.assertEquals("PT4H", result); + } +} From b4be9f32663f50193e9cde53cd48cfd896f70cd3 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Thu, 25 Nov 2021 23:45:24 +0100 Subject: [PATCH 07/19] Make DurationUtils final with private constructor There is no need to create an instance of the DurationUtils or to extend it. Signed-off-by: Giovanni van der Schelde --- sdk/src/main/java/io/dapr/utils/DurationUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/dapr/utils/DurationUtils.java b/sdk/src/main/java/io/dapr/utils/DurationUtils.java index 7bce2845fc..3baf226757 100644 --- a/sdk/src/main/java/io/dapr/utils/DurationUtils.java +++ b/sdk/src/main/java/io/dapr/utils/DurationUtils.java @@ -15,7 +15,9 @@ import java.time.Duration; -public class DurationUtils { +public final class DurationUtils { + + private DurationUtils() {} /** * Converts time from the String format used by Dapr into a Duration. From dca6b1707ace4d652866be2f4cbb5a4e839bd6b8 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Thu, 25 Nov 2021 23:50:36 +0100 Subject: [PATCH 08/19] Add String to RepeatedDuration convertion Signed-off-by: Giovanni van der Schelde --- .../java/io/dapr/utils/DurationUtils.java | 44 +++++++-- .../dapr/utils/DurationUtilsIso8601Test.java | 93 +++++++++++++++++++ .../utils/Iso8601TestsWithRepetitions.java | 33 ------- .../io/dapr/utils/RepeatedDurationTest.java | 1 - 4 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java delete mode 100644 sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java diff --git a/sdk/src/main/java/io/dapr/utils/DurationUtils.java b/sdk/src/main/java/io/dapr/utils/DurationUtils.java index 3baf226757..12309c09ea 100644 --- a/sdk/src/main/java/io/dapr/utils/DurationUtils.java +++ b/sdk/src/main/java/io/dapr/utils/DurationUtils.java @@ -13,6 +13,8 @@ package io.dapr.utils; +import com.google.common.base.Strings; + import java.time.Duration; public final class DurationUtils { @@ -92,8 +94,8 @@ public static String convertDurationToDaprFormat(Duration value) { } /** - * This method uses the default {@link Duration#parse(CharSequence)} and {@link Duration#toString()} methods that supports the ISO-8601 standard. - * It does however not support repetitions. Therefore this method will parse the repetitions as well. + * This method uses the default {@link Duration#toString()} method that supports the ISO-8601 standard. + * In addition to the default implementation, this method allows for repetitions as well. * * @param repeatedDuration {@link RepeatedDuration} to parse to ISO-8601 format. * @return String containing the parsed {@link RepeatedDuration} to the ISO-8601 format, possibly with repetitions. @@ -101,9 +103,8 @@ public static String convertDurationToDaprFormat(Duration value) { public static String convertRepeatedDurationToIso8601RepetitionFormat(RepeatedDuration repeatedDuration) { StringBuilder sb = new StringBuilder(); - if (repeatedDuration.getRepetitions().isPresent()) { - sb.append(String.format("R%d/", repeatedDuration.getRepetitions().get())); - } + repeatedDuration.getRepetitions() + .ifPresent(value -> sb.append(String.format("R%d/", value))); // Duration.ToString() returns the ISO-8601 representation of the duration. sb.append(repeatedDuration.getDuration().toString()); @@ -112,7 +113,38 @@ public static String convertRepeatedDurationToIso8601RepetitionFormat(RepeatedDu } /** - * Helper to get the "days" part of the Duration. For example if the duration is 26 hours, this returns 1. + * This method uses the default {@link Duration#parse(CharSequence)} method that supports parsing of an ISO-8601 string. + * In addition to the default implementation, this method allows for repetitions as well as the Dapr time.Duration format. + * Example inputs: 'R4/PT2H', 'P3DT2H', '4h15m50s60ms' + * + * @param value The value in ISO-8601 format to convert to a {@link RepeatedDuration}. + * @return {@link RepeatedDuration} containing the duration and possible repetitions. + */ + public static RepeatedDuration convertIso8601StringToRepeatedDuration(String value) { + if (Strings.isNullOrEmpty(value)) { + throw new IllegalArgumentException("Value can not be empty"); + } + + String[] splitOnRepetition = value.split("/"); + + if (splitOnRepetition.length == 1 && splitOnRepetition[0].charAt(0) == 'P') { + return new RepeatedDuration(Duration.parse(value)); + } else if (splitOnRepetition.length == 1) { + return new RepeatedDuration(DurationUtils.convertDurationFromDaprFormat(value)); + } + + if (splitOnRepetition[0].charAt(0) != 'R') { + throw new IllegalArgumentException("Value does not follow the ISO-8601 standard"); + } + + Integer repetitions = Integer.parseInt(splitOnRepetition[0].substring(1)); + Duration parsedDuration = Duration.parse(splitOnRepetition[1]); + + return new RepeatedDuration(parsedDuration, repetitions); + } + + /** + * Helper to get the "days" part of the Duration. For example if the duration is 26 hours, this returns 1. * * @param d Duration * @return Number of days. diff --git a/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java b/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java new file mode 100644 index 0000000000..de5ec5f7ad --- /dev/null +++ b/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java @@ -0,0 +1,93 @@ +package io.dapr.utils; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.Duration; + +public class DurationUtilsIso8601Test { + + @Test + public void convertRepeatedDurationToIso8601StringWithRepetitions() { + // Arrange + RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4), 2); + + // Act + String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); + + // Assert + Assert.assertEquals("R2/PT4H", result); + } + + @Test + public void convertRepeatedDurationToIso8601StringWithoutRepetitions() { + // Arrange + RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4)); + + // Act + String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); + + // Assert + Assert.assertEquals("PT4H", result); + } + + @Test + public void convertIso8601StringToRepeatedDurationWithRepetitions() { + // Arrange + RepeatedDuration expected = new RepeatedDuration(Duration.ofDays(1).plusMinutes(4), 5); + String value = "R5/P1DT4M"; + + // Act + RepeatedDuration result = DurationUtils.convertIso8601StringToRepeatedDuration(value); + + // Assert + Assert.assertEquals(expected.getDuration(), result.getDuration()); + Assert.assertEquals(expected.getRepetitions().get(), result.getRepetitions().get()); + } + + @Test + public void convertIso8601StringToRepeatedDurationWithoutRepetitions() { + // Arrange + RepeatedDuration expected = new RepeatedDuration(Duration.ofDays(1).plusMinutes(4)); + String value = "P1DT4M"; + + // Act + RepeatedDuration result = DurationUtils.convertIso8601StringToRepeatedDuration(value); + + // Assert + Assert.assertEquals(expected.getDuration(), result.getDuration()); + Assert.assertFalse(result.getRepetitions().isPresent()); + } + + @Test + public void convertDaprFormatStringToRepeatedDuration() { + // Arrange + String daprFormatDuration = "4h15m50s60ms"; + Duration expectedDuration = Duration.ofHours(4).plusMinutes(15).plusSeconds(50).plusMillis(60); + + // Act + RepeatedDuration result = DurationUtils.convertIso8601StringToRepeatedDuration(daprFormatDuration); + + // Assert + Assert.assertEquals(expectedDuration, result.getDuration()); + } + + @Test(expected = IllegalArgumentException.class) + public void convertWithInvalidRepetitionSyntax() { + // Arrange + String input = "Z4/PT4S"; + + // Act + DurationUtils.convertIso8601StringToRepeatedDuration(input); + } + + @Test(expected = IllegalArgumentException.class) + public void nullInputResultsInIllegalArgumentException() { + DurationUtils.convertIso8601StringToRepeatedDuration(""); + } + + @Test(expected = IllegalArgumentException.class) + public void emptyInputResultsInIllegalArgumentException() { + DurationUtils.convertIso8601StringToRepeatedDuration(null); + } +} diff --git a/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java b/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java deleted file mode 100644 index 25a153ea12..0000000000 --- a/sdk/src/test/java/io/dapr/utils/Iso8601TestsWithRepetitions.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.dapr.utils; - -import org.junit.Assert; -import org.junit.Test; - -import java.time.Duration; - -public class Iso8601TestsWithRepetitions { - - @Test - public void toStringWithRepetitions() { - // Arrange - RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4), 2); - - // Act - String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); - - // Assert - Assert.assertEquals("R2/PT4H", result); - } - - @Test - public void toStringNoRepetitions() { - // Arrange - RepeatedDuration repeatedDuration = new RepeatedDuration(Duration.ofHours(4)); - - // Act - String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(repeatedDuration); - - // Assert - Assert.assertEquals("PT4H", result); - } -} diff --git a/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java index 5dc89f5fee..dcee3aae8f 100644 --- a/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java +++ b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java @@ -1,6 +1,5 @@ package io.dapr.utils; -import io.dapr.utils.RepeatedDuration; import org.junit.Test; import java.time.Duration; From 129a4dd86de9d15c5cdfb980b7121982a57deec6 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 14:21:28 +0100 Subject: [PATCH 09/19] Follow checkstyle Signed-off-by: Giovanni van der Schelde --- .../io/dapr/actors/runtime/ActorTimerParams.java | 13 +++++++++++++ .../dapr/actors/runtime/ActorTimerParamsTest.java | 3 ++- sdk/src/main/java/io/dapr/utils/DurationUtils.java | 9 +++++---- .../main/java/io/dapr/utils/RepeatedDuration.java | 13 +++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index 3fe12f36ac..354005eddc 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -40,6 +40,7 @@ final class ActorTimerParams { /** * Period at which the timer will be triggered. + * The number of repetition can also be configured in order to limit the total number of callback invocations. */ private final RepeatedDuration period; @@ -122,11 +123,23 @@ public Duration getDueTime() { * Gets the periodic time when timer will be invoked. * * @return Periodic time as Duration when timer will be invoked. + * @deprecated As of release 1.4, replaced by {@link #getRepeatedPeriod()} */ + @Deprecated public Duration getPeriod() { return this.period.getDuration(); } + /** + * Gets the periodic time when timer will be invoked. + * Possibly contains repetitions to limit the the total number of callback invocations. + * + * @return Periodic time as {@link RepeatedDuration} when timer will be invoked. + */ + public RepeatedDuration getRepeatedPeriod() { + return this.period; + } + /** * Gets state containing information to be used by the callback method, or null. * diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java index 5fc7ce90b0..da98e9751e 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerParamsTest.java @@ -28,7 +28,8 @@ public void ttlShouldBeOptional() throws IOException { public void ttlRepetitionsNotRequired() throws IOException { // Arrange ActorTimerParams params = - new ActorTimerParams("callback", new byte[1], Duration.ZERO, new RepeatedDuration(Duration.ZERO)); + new ActorTimerParams("callback", new byte[1], Duration.ZERO, new RepeatedDuration(Duration.ZERO), + new RepeatedDuration(Duration.ZERO)); // Act byte[] serialized = SERIALIZER.serialize(params); diff --git a/sdk/src/main/java/io/dapr/utils/DurationUtils.java b/sdk/src/main/java/io/dapr/utils/DurationUtils.java index 12309c09ea..ec0c2f5366 100644 --- a/sdk/src/main/java/io/dapr/utils/DurationUtils.java +++ b/sdk/src/main/java/io/dapr/utils/DurationUtils.java @@ -19,7 +19,8 @@ public final class DurationUtils { - private DurationUtils() {} + private DurationUtils() { + } /** * Converts time from the String format used by Dapr into a Duration. @@ -113,8 +114,8 @@ public static String convertRepeatedDurationToIso8601RepetitionFormat(RepeatedDu } /** - * This method uses the default {@link Duration#parse(CharSequence)} method that supports parsing of an ISO-8601 string. - * In addition to the default implementation, this method allows for repetitions as well as the Dapr time.Duration format. + * This method uses the {@link Duration#parse(CharSequence)} method that supports parsing of an ISO-8601 string. + * In addition to the default implementation, this method allows for repetitions as well as the Dapr format. * Example inputs: 'R4/PT2H', 'P3DT2H', '4h15m50s60ms' * * @param value The value in ISO-8601 format to convert to a {@link RepeatedDuration}. @@ -134,7 +135,7 @@ public static RepeatedDuration convertIso8601StringToRepeatedDuration(String val } if (splitOnRepetition[0].charAt(0) != 'R') { - throw new IllegalArgumentException("Value does not follow the ISO-8601 standard"); + throw new IllegalArgumentException(String.format("Value: '%s' does not follow the ISO-8601 standard", value)); } Integer repetitions = Integer.parseInt(splitOnRepetition[0].substring(1)); diff --git a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index 930ee86959..d2759a6135 100644 --- a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) Microsoft Corporation and Dapr Contributors. + * Licensed under the MIT License. + */ + package io.dapr.utils; import java.time.Duration; @@ -22,18 +27,18 @@ public final class RepeatedDuration { private final Integer repetitions; /** - * Instantiates a new instance for a repeated duration + * Instantiates a new instance for a repeated duration. * - * @param duration The interval until an action + * @param duration The interval until an action. */ public RepeatedDuration(Duration duration) { this(duration, null); } /** - * Instantiates a new instance for a repeated duration + * Instantiates a new instance for a repeated duration. * - * @param duration The interval until an action + * @param duration The interval until an action. * @param repetitions The amount of times to invoke the action. May be null. */ public RepeatedDuration(Duration duration, Integer repetitions) { From e6478c969259392feac6a6b31e502595d79b9bf8 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 14:29:30 +0100 Subject: [PATCH 10/19] Add RepeatedDuration (de)serialization Signed-off-by: Giovanni van der Schelde --- .../actors/runtime/ActorObjectSerializer.java | 32 ++++++++++++++++-- .../dapr/actors/runtime/DaprGrpcClient.java | 33 ++++++++++--------- .../dapr/actors/runtime/ActorTimerTest.java | 2 +- .../actors/runtime/DaprGrpcClientTest.java | 2 +- .../actors/runtime/DaprHttpClientTest.java | 2 +- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java index 82ee62009a..d8d1411307 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.dapr.client.ObjectSerializer; import io.dapr.utils.DurationUtils; +import io.dapr.utils.RepeatedDuration; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -77,11 +78,19 @@ private byte[] serialize(ActorTimerParams timer) throws IOException { JsonGenerator generator = JSON_FACTORY.createGenerator(writer); generator.writeStartObject(); generator.writeStringField("dueTime", DurationUtils.convertDurationToDaprFormat(timer.getDueTime())); - generator.writeStringField("period", DurationUtils.convertDurationToDaprFormat(timer.getPeriod())); + generator.writeStringField("period", + DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(timer.getRepeatedPeriod())); generator.writeStringField("callback", timer.getCallback()); + + if (timer.getTtl().isPresent()) { + generator.writeStringField("ttl", + DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(timer.getTtl().get())); + } + if (timer.getData() != null) { generator.writeBinaryField("data", timer.getData()); } + generator.writeEndObject(); generator.close(); writer.flush(); @@ -187,10 +196,11 @@ private ActorTimerParams deserializeActorTimer(byte[] value) throws IOException JsonNode node = OBJECT_MAPPER.readTree(value); String callback = node.get("callback").asText(); Duration dueTime = extractDurationOrNull(node, "dueTime"); - Duration period = extractDurationOrNull(node, "period"); + RepeatedDuration period = extractRepeatedDurationOrNull(node, "period"); + RepeatedDuration ttl = extractRepeatedDurationOrNull(node, "ttl"); byte[] data = node.get("data") != null ? node.get("data").binaryValue() : null; - return new ActorTimerParams(callback, data, dueTime, period); + return new ActorTimerParams(callback, data, dueTime, period, ttl); } /** @@ -228,4 +238,20 @@ private static Duration extractDurationOrNull(JsonNode node, String name) { return DurationUtils.convertDurationFromDaprFormat(valueNode.asText()); } + + /** + * Extracts {@link RepeatedDuration} or null. + * + * @param node Node that contains the attribute. + * @param name Attribute name. + * @return Parsed {@link RepeatedDuration} or null. + */ + private static RepeatedDuration extractRepeatedDurationOrNull(JsonNode node, String name) { + JsonNode valueNode = node.get(name); + if (valueNode == null) { + return null; + } + + return DurationUtils.convertIso8601StringToRepeatedDuration(valueNode.asText()); + } } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java index 6a95fb3f00..1cb47bc2e2 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java @@ -19,7 +19,6 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Empty; import io.dapr.config.Properties; -import io.dapr.utils.DurationUtils; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; import io.grpc.ManagedChannel; @@ -30,6 +29,9 @@ import java.util.ArrayList; import java.util.List; +import static io.dapr.utils.DurationUtils.convertDurationToDaprFormat; +import static io.dapr.utils.DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat; + /** * A DaprClient over HTTP for Actor's runtime. */ @@ -154,8 +156,8 @@ public Mono registerReminder( .setActorId(actorId) .setName(reminderName) .setData(ByteString.copyFrom(reminderParams.getData())) - .setDueTime(DurationUtils.convertDurationToDaprFormat(reminderParams.getDueTime())) - .setPeriod(DurationUtils.convertDurationToDaprFormat(reminderParams.getPeriod())) + .setDueTime(convertDurationToDaprFormat(reminderParams.getDueTime())) + .setPeriod(convertDurationToDaprFormat(reminderParams.getPeriod())) .build(); ListenableFuture futureResponse = client.registerActorReminder(req); @@ -193,18 +195,19 @@ public Mono registerTimer( String timerName, ActorTimerParams timerParams) { return Mono.fromCallable(() -> { - DaprProtos.RegisterActorTimerRequest req = - DaprProtos.RegisterActorTimerRequest.newBuilder() - .setActorType(actorType) - .setActorId(actorId) - .setName(timerName) - .setCallback(timerParams.getCallback()) - .setData(ByteString.copyFrom(timerParams.getData())) - .setDueTime(DurationUtils.convertDurationToDaprFormat(timerParams.getDueTime())) - .setPeriod(DurationUtils.convertDurationToDaprFormat(timerParams.getPeriod())) - .build(); - - ListenableFuture futureResponse = client.registerActorTimer(req); + DaprProtos.RegisterActorTimerRequest.Builder reqBuilder = DaprProtos.RegisterActorTimerRequest.newBuilder() + .setActorType(actorType) + .setActorId(actorId) + .setName(timerName) + .setCallback(timerParams.getCallback()) + .setData(ByteString.copyFrom(timerParams.getData())) + .setDueTime(convertDurationToDaprFormat(timerParams.getDueTime())) + .setPeriod(convertRepeatedDurationToIso8601RepetitionFormat(timerParams.getRepeatedPeriod())); + + timerParams.getTtl() + .ifPresent(value -> reqBuilder.setTtl(convertRepeatedDurationToIso8601RepetitionFormat(value))); + + ListenableFuture futureResponse = client.registerActorTimer(reqBuilder.build()); futureResponse.get(); return null; }); diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java index 00320e0782..dbc145da8f 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java @@ -41,7 +41,7 @@ public void serialize() throws IOException { period); byte[] s = new ActorObjectSerializer().serialize(timer); - String expected = "{\"period\":\"1h0m3s0ms\",\"dueTime\":\"0h7m17s0ms\", \"callback\": \"myfunction\"}"; + String expected = "{\"period\":\"PT1H3S\",\"dueTime\":\"0h7m17s0ms\", \"callback\": \"myfunction\"}"; // Deep comparison via JsonNode.equals method. Assert.assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(s)); } diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java index 6b362b9841..6e4097bd86 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java @@ -210,7 +210,7 @@ public void registerActorTimer() { assertEquals(timerName, argument.getName()); assertEquals(callback, argument.getCallback()); assertEquals(DurationUtils.convertDurationToDaprFormat(params.getDueTime()), argument.getDueTime()); - assertEquals(DurationUtils.convertDurationToDaprFormat(params.getPeriod()), argument.getPeriod()); + assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getRepeatedPeriod()), argument.getPeriod()); return true; }))).thenReturn(settableFuture); Mono result = client.registerTimer(ACTOR_TYPE, ACTOR_ID, timerName, params); diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprHttpClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprHttpClientTest.java index 8ef2f976cf..d62e847622 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprHttpClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprHttpClientTest.java @@ -115,7 +115,7 @@ public void registerActorTimer() { @Override public Response.Builder respond(Request request) { String expectedBody = "{\"dueTime\":\"0h0m5s0ms\"," + - "\"period\":\"0h0m10s0ms\"," + + "\"period\":\"PT10S\"," + "\"callback\":\"mycallback\"," + "\"data\":\""+ Base64.getEncoder().encodeToString(data.getBytes()) +"\"}"; String body = ""; From e19f58a594b39cf43e2089249a91925c69c35fdf Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 14:30:36 +0100 Subject: [PATCH 11/19] Fix negative duration parsing Signed-off-by: Giovanni van der Schelde --- sdk/src/main/java/io/dapr/utils/DurationUtils.java | 6 ++++++ .../java/io/dapr/utils/DurationUtilsIso8601Test.java | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/sdk/src/main/java/io/dapr/utils/DurationUtils.java b/sdk/src/main/java/io/dapr/utils/DurationUtils.java index ec0c2f5366..bcadf3d9a6 100644 --- a/sdk/src/main/java/io/dapr/utils/DurationUtils.java +++ b/sdk/src/main/java/io/dapr/utils/DurationUtils.java @@ -100,10 +100,16 @@ public static String convertDurationToDaprFormat(Duration value) { * * @param repeatedDuration {@link RepeatedDuration} to parse to ISO-8601 format. * @return String containing the parsed {@link RepeatedDuration} to the ISO-8601 format, possibly with repetitions. + * Negative duration results in an empty string, meaning fire only once. */ public static String convertRepeatedDurationToIso8601RepetitionFormat(RepeatedDuration repeatedDuration) { StringBuilder sb = new StringBuilder(); + if (repeatedDuration.getDuration().isNegative()) { + // Negative duration results in fire only once. + return sb.toString(); + } + repeatedDuration.getRepetitions() .ifPresent(value -> sb.append(String.format("R%d/", value))); diff --git a/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java b/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java index de5ec5f7ad..1329aa464e 100644 --- a/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java +++ b/sdk/src/test/java/io/dapr/utils/DurationUtilsIso8601Test.java @@ -90,4 +90,16 @@ public void nullInputResultsInIllegalArgumentException() { public void emptyInputResultsInIllegalArgumentException() { DurationUtils.convertIso8601StringToRepeatedDuration(null); } + + @Test + public void negativeDurationShouldReturnEmptyString() { + // Arrange + RepeatedDuration input = new RepeatedDuration(Duration.ZERO.minusMinutes(1)); + + // Act + String result = DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(input); + + // Assert + Assert.assertEquals("", result); + } } From 0427864c030049711e49bd3fc4c3d07c5d6906fb Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 15:21:57 +0100 Subject: [PATCH 12/19] Change minimum amount of repetitions Signed-off-by: Giovanni van der Schelde --- sdk/src/main/java/io/dapr/utils/RepeatedDuration.java | 2 +- sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index d2759a6135..09bb43acc9 100644 --- a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -13,7 +13,7 @@ public final class RepeatedDuration { /** * The minimum amount of repetitions TTL and Period can have. */ - private static final Integer MIN_AMOUNT_REPETITIONS = 0; + private static final Integer MIN_AMOUNT_REPETITIONS = 1; /** * The duration. diff --git a/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java index dcee3aae8f..6b48a55f04 100644 --- a/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java +++ b/sdk/src/test/java/io/dapr/utils/RepeatedDurationTest.java @@ -12,6 +12,11 @@ public void invalidAmountOfRepetitions() { new RepeatedDuration(Duration.ZERO, -1); } + @Test(expected = IllegalArgumentException.class) + public void repetitionsMustBeGreaterThanZero() { + new RepeatedDuration(Duration.ZERO, 0); + } + @Test(expected = IllegalArgumentException.class) public void durationCanNotBeNull() { new RepeatedDuration(null); From 71043cf4cced6e74221382c360293d15291e2edd Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 16:03:52 +0100 Subject: [PATCH 13/19] Remove public access modifier on getters Since the class is package-private there is no need to make the getters public. Signed-off-by: Giovanni van der Schelde --- .../io/dapr/actors/runtime/ActorTimerParams.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index 354005eddc..3ddeeaf2ed 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -106,7 +106,7 @@ final class ActorTimerParams { * * @return The name of the method for this timer. */ - public String getCallback() { + String getCallback() { return this.callback; } @@ -115,7 +115,7 @@ public String getCallback() { * * @return Time as Duration when timer is first due. */ - public Duration getDueTime() { + Duration getDueTime() { return this.dueTime; } @@ -126,7 +126,7 @@ public Duration getDueTime() { * @deprecated As of release 1.4, replaced by {@link #getRepeatedPeriod()} */ @Deprecated - public Duration getPeriod() { + Duration getPeriod() { return this.period.getDuration(); } @@ -136,7 +136,7 @@ public Duration getPeriod() { * * @return Periodic time as {@link RepeatedDuration} when timer will be invoked. */ - public RepeatedDuration getRepeatedPeriod() { + RepeatedDuration getRepeatedPeriod() { return this.period; } @@ -145,7 +145,7 @@ public RepeatedDuration getRepeatedPeriod() { * * @return State containing information to be used by the callback method, or null. */ - public byte[] getData() { + byte[] getData() { return this.data; } @@ -155,7 +155,7 @@ public byte[] getData() { * * @return Time at which or time interval after which the timer will be expired and deleted, or null. */ - public Optional getTtl() { + Optional getTtl() { return Optional.ofNullable(this.ttl); } } From 9d3e2b98515f6304922f0ac36624ae4487d7a666 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 16:10:53 +0100 Subject: [PATCH 14/19] Change getter of period Signed-off-by: Giovanni van der Schelde --- .../actors/runtime/ActorObjectSerializer.java | 2 +- .../io/dapr/actors/runtime/ActorTimerParams.java | 15 ++------------- .../io/dapr/actors/runtime/DaprGrpcClient.java | 2 +- .../dapr/actors/runtime/DaprGrpcClientTest.java | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java index d8d1411307..fa12110db0 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java @@ -79,7 +79,7 @@ private byte[] serialize(ActorTimerParams timer) throws IOException { generator.writeStartObject(); generator.writeStringField("dueTime", DurationUtils.convertDurationToDaprFormat(timer.getDueTime())); generator.writeStringField("period", - DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(timer.getRepeatedPeriod())); + DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(timer.getPeriod())); generator.writeStringField("callback", timer.getCallback()); if (timer.getTtl().isPresent()) { diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index 3ddeeaf2ed..81480b1d98 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -121,22 +121,11 @@ Duration getDueTime() { /** * Gets the periodic time when timer will be invoked. - * - * @return Periodic time as Duration when timer will be invoked. - * @deprecated As of release 1.4, replaced by {@link #getRepeatedPeriod()} - */ - @Deprecated - Duration getPeriod() { - return this.period.getDuration(); - } - - /** - * Gets the periodic time when timer will be invoked. - * Possibly contains repetitions to limit the the total number of callback invocations. + * Possibly contains repetitions to limit the the total number of callback invocations. * * @return Periodic time as {@link RepeatedDuration} when timer will be invoked. */ - RepeatedDuration getRepeatedPeriod() { + RepeatedDuration getPeriod() { return this.period; } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java index 1cb47bc2e2..2605b8bd87 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java @@ -202,7 +202,7 @@ public Mono registerTimer( .setCallback(timerParams.getCallback()) .setData(ByteString.copyFrom(timerParams.getData())) .setDueTime(convertDurationToDaprFormat(timerParams.getDueTime())) - .setPeriod(convertRepeatedDurationToIso8601RepetitionFormat(timerParams.getRepeatedPeriod())); + .setPeriod(convertRepeatedDurationToIso8601RepetitionFormat(timerParams.getPeriod())); timerParams.getTtl() .ifPresent(value -> reqBuilder.setTtl(convertRepeatedDurationToIso8601RepetitionFormat(value))); diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java index 6e4097bd86..109b274fc0 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java @@ -210,7 +210,7 @@ public void registerActorTimer() { assertEquals(timerName, argument.getName()); assertEquals(callback, argument.getCallback()); assertEquals(DurationUtils.convertDurationToDaprFormat(params.getDueTime()), argument.getDueTime()); - assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getRepeatedPeriod()), argument.getPeriod()); + assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getPeriod()), argument.getPeriod()); return true; }))).thenReturn(settableFuture); Mono result = client.registerTimer(ACTOR_TYPE, ACTOR_ID, timerName, params); From 41a2722c15c0941093f174b894d4ad64b2fd8e90 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 16:29:02 +0100 Subject: [PATCH 15/19] Clean up javadocs Signed-off-by: Giovanni van der Schelde --- .../dapr/actors/runtime/ActorTimerParams.java | 2 ++ .../java/io/dapr/utils/RepeatedDuration.java | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java index 81480b1d98..5767d492d1 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimerParams.java @@ -57,7 +57,9 @@ final class ActorTimerParams { * @param data The state 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. + * @deprecated As of release 1.4, replace with {@link #ActorTimerParams(String, byte[], Duration, RepeatedDuration)} */ + @Deprecated ActorTimerParams(String callback, byte[] data, Duration dueTime, diff --git a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index 09bb43acc9..447a552769 100644 --- a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -8,10 +8,13 @@ import java.time.Duration; import java.util.Optional; +/** + * Represents a duration with an optional amount of repetitions. + */ public final class RepeatedDuration { /** - * The minimum amount of repetitions TTL and Period can have. + * The minimum amount of repetitions. */ private static final Integer MIN_AMOUNT_REPETITIONS = 1; @@ -27,7 +30,7 @@ public final class RepeatedDuration { private final Integer repetitions; /** - * Instantiates a new instance for a repeated duration. + * Instantiates a new instance of a repeated duration. * * @param duration The interval until an action. */ @@ -39,7 +42,7 @@ public RepeatedDuration(Duration duration) { * Instantiates a new instance for a repeated duration. * * @param duration The interval until an action. - * @param repetitions The amount of times to invoke the action. May be null. + * @param repetitions The amount of times to invoke the action. */ public RepeatedDuration(Duration duration, Integer repetitions) { validateDuration(duration); @@ -73,10 +76,20 @@ private static void validateRepetitions(Integer repetitions) { } } + /** + * Gets the {@link Duration}. + * + * @return The {@link Duration}. + */ public Duration getDuration() { return duration; } + /** + * Gets the amount of repetitions. + * + * @return The amount of repetitions as {@link Optional}. + */ public Optional getRepetitions() { return Optional.ofNullable(repetitions); } From 35f7b3cec58dae3695428120e64a8af7fc1de9f8 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 17:15:58 +0100 Subject: [PATCH 16/19] Add TTL to ActorReminderParams Signed-off-by: Giovanni van der Schelde --- .../actors/runtime/ActorReminderParams.java | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java index 389457b7ad..ae91dc8c4b 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java @@ -13,7 +13,10 @@ package io.dapr.actors.runtime; +import io.dapr.utils.RepeatedDuration; + import java.time.Duration; +import java.util.Optional; /** * Parameters for Actor Reminder. @@ -38,7 +41,13 @@ final class ActorReminderParams { /** * Interval between triggers. */ - private final Duration period; + private final RepeatedDuration period; + + /** + * Time at which or time interval after which the reminder will be expired and deleted. + * If ttl is omitted, no restrictions are applied. + */ + private final RepeatedDuration ttl; /** * Instantiates a new instance for the params of a reminder. @@ -46,13 +55,39 @@ final class ActorReminderParams { * @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. + * @deprecated As of release 1.4, replace with {@link #ActorReminderParams(byte[], Duration, RepeatedDuration)} */ + @Deprecated ActorReminderParams(byte[] data, Duration dueTime, Duration period) { + this(data, dueTime, new RepeatedDuration(period, null)); + } + + /** + * 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(byte[] data, Duration dueTime, RepeatedDuration period) { + this(data, dueTime, period, null); + } + + /** + * 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. + * @param ttl Time at which or time interval after which the reminder will be expired and deleted. + */ + ActorReminderParams(byte[] data, Duration dueTime, RepeatedDuration period, RepeatedDuration ttl) { validateDueTime("DueTime", dueTime); - validatePeriod("Period", period); + validatePeriod("Period", period.getDuration()); this.data = data; this.dueTime = dueTime; this.period = period; + this.ttl = ttl; } /** @@ -70,7 +105,7 @@ Duration getDueTime() { * @return Interval between triggers. */ Duration getPeriod() { - return period; + return period.getDuration(); } /** @@ -82,6 +117,15 @@ byte[] getData() { return data; } + /** + * Gets the time at which or time interval after which the reminder will be expired and deleted. + * + * @return Time at which or time interval after which the reminder will be expired and deleted. + */ + Optional getTtl() { + return Optional.ofNullable(ttl); + } + /** * Validates due time is valid, throws {@link IllegalArgumentException}. * From 4877780773861864d4b3ec6ed4f4929ab653bf1c Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 17:16:24 +0100 Subject: [PATCH 17/19] Apply project styling to ActorReminderParams Signed-off-by: Giovanni van der Schelde --- .../actors/runtime/ActorReminderParams.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java index ae91dc8c4b..876c059d72 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java @@ -90,6 +90,34 @@ final class ActorReminderParams { this.ttl = ttl; } + /** + * 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( + "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); + throw new IllegalArgumentException(message); + } + } + + /** + * 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, MIN_TIME_PERIOD); + throw new IllegalArgumentException(message); + } + } + /** * Gets the time the reminder is due for the 1st time. * @@ -125,32 +153,4 @@ byte[] getData() { Optional getTtl() { return Optional.ofNullable(ttl); } - - /** - * 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( - "argName: %s - Duration toMillis() - specified value must be greater than %s", argName, Duration.ZERO); - throw new IllegalArgumentException(message); - } - } - - /** - * 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, MIN_TIME_PERIOD); - throw new IllegalArgumentException(message); - } - } } From 46e783527682edcd14bf222016fcb3475b76371b Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Sun, 28 Nov 2021 17:30:29 +0100 Subject: [PATCH 18/19] Add RepeatedDuration (de)serialization to reminders Signed-off-by: Giovanni van der Schelde --- .../io/dapr/actors/runtime/AbstractActor.java | 40 ++++++++++++++++++- .../actors/runtime/ActorObjectSerializer.java | 15 +++++-- .../actors/runtime/ActorReminderParams.java | 10 +++++ .../dapr/actors/runtime/DaprGrpcClient.java | 21 +++++----- .../dapr/actors/runtime/ActorTimerTest.java | 10 ++++- .../actors/runtime/DaprGrpcClientTest.java | 11 +++-- .../java/io/dapr/utils/RepeatedDuration.java | 2 +- 7 files changed, 89 insertions(+), 20 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java index 91fd4212e8..fdf58cd8a4 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -121,9 +121,47 @@ protected Mono registerReminder( T state, Duration dueTime, Duration period) { + return this.registerReminder(reminderName, state, dueTime, new RepeatedDuration(period)); + } + + /** + * Registers a reminder for this Actor. + * + * @param reminderName Name of the reminder. + * @param state State to be send along with reminder triggers. + * @param dueTime Due time for the first trigger. + * @param period Frequency for the triggers. + * @param Type of the state object. + * @return Asynchronous void response. + */ + protected Mono registerReminder( + String reminderName, + T state, + Duration dueTime, + RepeatedDuration period) { + return this.registerReminder(reminderName, state, dueTime, period, null); + } + + /** + * Registers a reminder for this Actor. + * + * @param reminderName Name of the reminder. + * @param state State to be send along with reminder triggers. + * @param dueTime Due time for the first trigger. + * @param period Frequency for the triggers. + * @param ttl The time at which or time interval after which the reminder will be expired and deleted. + * @param Type of the state object. + * @return Asynchronous void response. + */ + protected Mono registerReminder( + String reminderName, + T state, + Duration dueTime, + RepeatedDuration period, + RepeatedDuration ttl) { try { byte[] data = this.actorRuntimeContext.getObjectSerializer().serialize(state); - ActorReminderParams params = new ActorReminderParams(data, dueTime, period); + ActorReminderParams params = new ActorReminderParams(data, dueTime, period, ttl); return this.actorRuntimeContext.getDaprClient().registerReminder( this.actorRuntimeContext.getActorTypeInformation().getName(), this.id.toString(), diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java index fa12110db0..0143efd1ea 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorObjectSerializer.java @@ -110,10 +110,18 @@ private byte[] serialize(ActorReminderParams reminder) throws IOException { JsonGenerator generator = JSON_FACTORY.createGenerator(writer); generator.writeStartObject(); generator.writeStringField("dueTime", DurationUtils.convertDurationToDaprFormat(reminder.getDueTime())); - generator.writeStringField("period", DurationUtils.convertDurationToDaprFormat(reminder.getPeriod())); + generator.writeStringField("period", + DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(reminder.getRepeatedPeriod())); + + if (reminder.getTtl().isPresent()) { + generator.writeStringField("ttl", + DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(reminder.getTtl().get())); + } + if (reminder.getData() != null) { generator.writeBinaryField("data", reminder.getData()); } + generator.writeEndObject(); generator.close(); writer.flush(); @@ -217,10 +225,11 @@ private ActorReminderParams deserializeActorReminder(byte[] value) throws IOExce JsonNode node = OBJECT_MAPPER.readTree(value); Duration dueTime = extractDurationOrNull(node, "dueTime"); - Duration period = extractDurationOrNull(node, "period"); + RepeatedDuration period = extractRepeatedDurationOrNull(node, "period"); + RepeatedDuration ttl = extractRepeatedDurationOrNull(node, "ttl"); byte[] data = node.get("data") != null ? node.get("data").binaryValue() : null; - return new ActorReminderParams(data, dueTime, period); + return new ActorReminderParams(data, dueTime, period, ttl); } /** diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java index 876c059d72..74709cd86a 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java @@ -153,4 +153,14 @@ byte[] getData() { Optional getTtl() { return Optional.ofNullable(ttl); } + + /** + * Gets the periodic time when the reminder will be invoked. + * Possibly contains repetitions to limit the the total number of callback invocations. + * + * @return Periodic time as {@link RepeatedDuration} when reminder will be invoked. + */ + public RepeatedDuration getRepeatedPeriod() { + return period; + } } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java index 2605b8bd87..1574752ea9 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprGrpcClient.java @@ -150,17 +150,18 @@ public Mono registerReminder( String reminderName, ActorReminderParams reminderParams) { return Mono.fromCallable(() -> { - DaprProtos.RegisterActorReminderRequest req = - DaprProtos.RegisterActorReminderRequest.newBuilder() - .setActorType(actorType) - .setActorId(actorId) - .setName(reminderName) - .setData(ByteString.copyFrom(reminderParams.getData())) - .setDueTime(convertDurationToDaprFormat(reminderParams.getDueTime())) - .setPeriod(convertDurationToDaprFormat(reminderParams.getPeriod())) - .build(); + DaprProtos.RegisterActorReminderRequest.Builder reqBuilder = DaprProtos.RegisterActorReminderRequest.newBuilder() + .setActorType(actorType) + .setActorId(actorId) + .setName(reminderName) + .setData(ByteString.copyFrom(reminderParams.getData())) + .setDueTime(convertDurationToDaprFormat(reminderParams.getDueTime())) + .setPeriod(convertRepeatedDurationToIso8601RepetitionFormat(reminderParams.getRepeatedPeriod())); + + reminderParams.getTtl() + .ifPresent(value -> reqBuilder.setTtl(convertRepeatedDurationToIso8601RepetitionFormat(value))); - ListenableFuture futureResponse = client.registerActorReminder(req); + ListenableFuture futureResponse = client.registerActorReminder(reqBuilder.build()); futureResponse.get(); return null; }); diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java index dbc145da8f..2c4a630f03 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java @@ -14,6 +14,7 @@ package io.dapr.actors.runtime; import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.utils.RepeatedDuration; import org.junit.Assert; import org.junit.Test; @@ -34,14 +35,19 @@ public void serialize() throws IOException { .plusHours(1) .plusSeconds(3); + Duration ttl = Duration.ZERO + .plusHours(4) + .plusSeconds(2); + ActorTimerParams timer = new ActorTimerParams( "myfunction", null, dueTime, - period); + new RepeatedDuration(period), + new RepeatedDuration(ttl, 4)); byte[] s = new ActorObjectSerializer().serialize(timer); - String expected = "{\"period\":\"PT1H3S\",\"dueTime\":\"0h7m17s0ms\", \"callback\": \"myfunction\"}"; + String expected = "{\"period\":\"PT1H3S\",\"dueTime\":\"0h7m17s0ms\", \"callback\": \"myfunction\", \"ttl\": \"R4/PT4H2S\"}"; // Deep comparison via JsonNode.equals method. Assert.assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(s)); } diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java index 109b274fc0..f73ea833af 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprGrpcClientTest.java @@ -19,6 +19,7 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Empty; import io.dapr.utils.DurationUtils; +import io.dapr.utils.RepeatedDuration; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; import org.junit.Before; @@ -158,7 +159,8 @@ public void registerActorReminder() { ActorReminderParams params = new ActorReminderParams( "hello world".getBytes(), Duration.ofSeconds(1), - Duration.ofSeconds(2) + new RepeatedDuration(Duration.ofSeconds(2)), + new RepeatedDuration(Duration.ofSeconds(2), 1) ); when(grpcStub.registerActorReminder(argThat(argument -> { @@ -166,7 +168,8 @@ public void registerActorReminder() { assertEquals(ACTOR_ID, argument.getActorId()); assertEquals(reminderName, argument.getName()); assertEquals(DurationUtils.convertDurationToDaprFormat(params.getDueTime()), argument.getDueTime()); - assertEquals(DurationUtils.convertDurationToDaprFormat(params.getPeriod()), argument.getPeriod()); + assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getRepeatedPeriod()), argument.getPeriod()); + assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getTtl().get()), argument.getTtl()); return true; }))).thenReturn(settableFuture); Mono result = client.registerReminder(ACTOR_TYPE, ACTOR_ID, reminderName, params); @@ -201,7 +204,8 @@ public void registerActorTimer() { callback, "hello world".getBytes(), Duration.ofSeconds(1), - Duration.ofSeconds(2) + new RepeatedDuration(Duration.ofSeconds(2)), + new RepeatedDuration(Duration.ofSeconds(2), 4) ); when(grpcStub.registerActorTimer(argThat(argument -> { @@ -211,6 +215,7 @@ public void registerActorTimer() { assertEquals(callback, argument.getCallback()); assertEquals(DurationUtils.convertDurationToDaprFormat(params.getDueTime()), argument.getDueTime()); assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getPeriod()), argument.getPeriod()); + assertEquals(DurationUtils.convertRepeatedDurationToIso8601RepetitionFormat(params.getTtl().get()), argument.getTtl()); return true; }))).thenReturn(settableFuture); Mono result = client.registerTimer(ACTOR_TYPE, ACTOR_ID, timerName, params); diff --git a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index 447a552769..82b3c01a82 100644 --- a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -88,7 +88,7 @@ public Duration getDuration() { /** * Gets the amount of repetitions. * - * @return The amount of repetitions as {@link Optional}. + * @return The amount of repetitions as {@link Optional}. */ public Optional getRepetitions() { return Optional.ofNullable(repetitions); From 3f6b554f219206d75e3af1bcb01582fa401f1be8 Mon Sep 17 00:00:00 2001 From: Giovanni van der Schelde Date: Wed, 26 Jan 2022 21:52:00 +0100 Subject: [PATCH 19/19] Add Copyright to RepeatedDuration class Signed-off-by: Giovanni van der Schelde --- .../dapr/actors/runtime/ActorReminderParams.java | 2 +- .../main/java/io/dapr/utils/RepeatedDuration.java | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java index 74709cd86a..2f62d0791c 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java @@ -160,7 +160,7 @@ Optional getTtl() { * * @return Periodic time as {@link RepeatedDuration} when reminder will be invoked. */ - public RepeatedDuration getRepeatedPeriod() { + RepeatedDuration getRepeatedPeriod() { return period; } } diff --git a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java index 82b3c01a82..5ddd31b022 100644 --- a/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java +++ b/sdk/src/main/java/io/dapr/utils/RepeatedDuration.java @@ -1,7 +1,15 @@ /* - * Copyright (c) Microsoft Corporation and Dapr Contributors. - * Licensed under the MIT License. - */ + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ package io.dapr.utils;