diff --git a/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java b/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java index 45cc070e7..91bc526bc 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java @@ -15,9 +15,11 @@ */ package org.springframework.data.redis.core; +import java.time.Duration; import java.util.concurrent.TimeUnit; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Value (or String in Redis terminology) operations bound to a certain key. @@ -47,6 +49,26 @@ public interface BoundValueOperations extends BoundKeyOperations { */ void set(V value, long timeout, TimeUnit unit); + /** + * Set the {@code value} and expiration {@code timeout} for the bound key. + * + * @param value must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @throws IllegalArgumentException if either {@code value} or {@code timeout} is not present. + * @see Redis Documentation: SETEX + * @since 2.1 + */ + default void set(V value, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null!"); + + if (TimeoutUtils.hasMillis(timeout)) { + set(value, timeout.toMillis(), TimeUnit.MILLISECONDS); + } else { + set(value, timeout.getSeconds(), TimeUnit.SECONDS); + } + } + /** * Set the bound key to hold the string {@code value} if the bound key is absent. * @@ -70,6 +92,28 @@ public interface BoundValueOperations extends BoundKeyOperations { @Nullable Boolean setIfAbsent(V value, long timeout, TimeUnit unit); + /** + * Set bound key to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent. + * + * @param value must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if either {@code value} or {@code timeout} is not present. + * @see Redis Documentation: SET + * @since 2.1 + */ + @Nullable + default Boolean setIfAbsent(V value, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null!"); + + if (TimeoutUtils.hasMillis(timeout)) { + return setIfAbsent(value, timeout.toMillis(), TimeUnit.MILLISECONDS); + } + + return setIfAbsent(value, timeout.getSeconds(), TimeUnit.SECONDS); + } + /** * Set the bound key to hold the string {@code value} if {@code key} is present. * @@ -96,6 +140,28 @@ public interface BoundValueOperations extends BoundKeyOperations { @Nullable Boolean setIfPresent(V value, long timeout, TimeUnit unit); + /** + * Set the bound key to hold the string {@code value} and expiration {@code timeout} if {@code key} is present. + * + * @param value must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if either {@code value} or {@code timeout} is not present. + * @see Redis Documentation: SET + * @since 2.1 + */ + @Nullable + default Boolean setIfPresent(V value, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null!"); + + if (TimeoutUtils.hasMillis(timeout)) { + return setIfPresent(value, timeout.toMillis(), TimeUnit.MILLISECONDS); + } + + return setIfPresent(value, timeout.getSeconds(), TimeUnit.SECONDS); + } + /** * Get the value of the bound key. * diff --git a/src/main/java/org/springframework/data/redis/core/TimeoutUtils.java b/src/main/java/org/springframework/data/redis/core/TimeoutUtils.java index bd72aac17..4dabfcf85 100644 --- a/src/main/java/org/springframework/data/redis/core/TimeoutUtils.java +++ b/src/main/java/org/springframework/data/redis/core/TimeoutUtils.java @@ -23,15 +23,18 @@ import java.util.concurrent.TimeUnit; * * @author Jennifer Hickey * @author Mark Paluch + * @author Christoph Strobl */ abstract public class TimeoutUtils { /** - * @param duration the actual {@link Duration} to inspect. + * Check if a given Duration can be represented in {@code sec} or requires {@code msec} representation. + * + * @param duration the actual {@link Duration} to inspect. Never {@literal null}. * @return {@literal true} if the {@link Duration} contains millisecond information. * @since 2.1 */ - static boolean hasMillis(Duration duration) { + public static boolean hasMillis(Duration duration) { return duration.toMillis() % 1000 != 0; } diff --git a/src/main/java/org/springframework/data/redis/core/ValueOperations.java b/src/main/java/org/springframework/data/redis/core/ValueOperations.java index 5f045cfff..7c3ffdef9 100644 --- a/src/main/java/org/springframework/data/redis/core/ValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ValueOperations.java @@ -58,10 +58,11 @@ public interface ValueOperations { * Set the {@code value} and expiration {@code timeout} for {@code key}. * * @param key must not be {@literal null}. - * @param value + * @param value must not be {@literal null}. * @param timeout must not be {@literal null}. - * @since 2.1 + * @throws IllegalArgumentException if either {@code key}, {@code value} or {@code timeout} is not present. * @see Redis Documentation: SETEX + * @since 2.1 */ default void set(K key, V value, Duration timeout) { @@ -103,11 +104,12 @@ public interface ValueOperations { * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent. * * @param key must not be {@literal null}. - * @param value + * @param value must not be {@literal null}. * @param timeout must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if either {@code key}, {@code value} or {@code timeout} is not present. + * @see Redis Documentation: SET * @since 2.1 - * @see Redis Documentation: SETEX */ @Nullable default Boolean setIfAbsent(K key, V value, Duration timeout) { @@ -153,11 +155,12 @@ public interface ValueOperations { * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is present. * * @param key must not be {@literal null}. - * @param value + * @param value must not be {@literal null}. * @param timeout must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if either {@code key}, {@code value} or {@code timeout} is not present. + * @see Redis Documentation: SET * @since 2.1 - * @see Redis Documentation: SETEX */ @Nullable default Boolean setIfPresent(K key, V value, Duration timeout) { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java index e0eb0fc7d..bfbd6cc3f 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.core; import static org.mockito.Mockito.*; +import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -64,4 +65,52 @@ public class DefaultBoundValueOperationsUnitTests { verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(10L), eq(TimeUnit.SECONDS)); } + + @Test // DATAREDIS-815 + public void setWithDurationOfSecondsShouldDelegateCorrectly() { + + boundValueOps.set(VALUE, Duration.ofSeconds(1)); + + verify(valueOps).set(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); + } + + @Test // DATAREDIS-815 + public void setWithDurationOfMillisShouldDelegateCorrectly() { + + boundValueOps.set(VALUE, Duration.ofMillis(250)); + + verify(valueOps).set(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); + } + + @Test // DATAREDIS-815 + public void setIfAbsentWithDurationOfSecondsShouldDelegateCorrectly() { + + boundValueOps.setIfAbsent(VALUE, Duration.ofSeconds(1)); + + verify(valueOps).setIfAbsent(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); + } + + @Test // DATAREDIS-815 + public void setIfAbsentWithDurationOfMillisShouldDelegateCorrectly() { + + boundValueOps.setIfAbsent(VALUE, Duration.ofMillis(250)); + + verify(valueOps).setIfAbsent(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); + } + + @Test // DATAREDIS-815 + public void setIfPresentWithDurationOfSecondsShouldDelegateCorrectly() { + + boundValueOps.setIfPresent(VALUE, Duration.ofSeconds(1)); + + verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); + } + + @Test // DATAREDIS-815 + public void setIfPresentWithDurationOfMillisShouldDelegateCorrectly() { + + boundValueOps.setIfPresent(VALUE, Duration.ofMillis(250)); + + verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); + } } diff --git a/src/test/java/org/springframework/data/redis/core/TimeoutUtilsTests.java b/src/test/java/org/springframework/data/redis/core/TimeoutUtilsTests.java index 0fd3b87b9..4c6d11175 100644 --- a/src/test/java/org/springframework/data/redis/core/TimeoutUtilsTests.java +++ b/src/test/java/org/springframework/data/redis/core/TimeoutUtilsTests.java @@ -15,78 +15,95 @@ */ package org.springframework.data.redis.core; +import static org.assertj.core.api.Assertions.*; + +import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.Test; -import static org.junit.Assert.assertEquals; - /** * Unit test of {@link TimeoutUtils} * * @author Jennifer Hickey + * @author Christoph Strobl */ public class TimeoutUtilsTests { @Test public void testConvertMoreThanOneSecond() { - assertEquals(2, TimeoutUtils.toSeconds(2010, TimeUnit.MILLISECONDS)); + assertThat(TimeoutUtils.toSeconds(2010, TimeUnit.MILLISECONDS)).isEqualTo(2); } @Test public void testConvertLessThanOneSecond() { - assertEquals(1, TimeoutUtils.toSeconds(999, TimeUnit.NANOSECONDS)); + assertThat(TimeoutUtils.toSeconds(999, TimeUnit.NANOSECONDS)).isOne(); } @Test public void testConvertZeroSeconds() { - assertEquals(0, TimeoutUtils.toSeconds(0, TimeUnit.MINUTES)); + assertThat(TimeoutUtils.toSeconds(0, TimeUnit.MINUTES)).isZero(); } @Test public void testConvertNegativeSecondsGreaterThanNegativeOne() { // Ensure we convert this to 0 as before, though ideally we wouldn't accept negative values - assertEquals(0, TimeoutUtils.toSeconds(-123, TimeUnit.MILLISECONDS)); + assertThat(TimeoutUtils.toSeconds(-123, TimeUnit.MILLISECONDS)).isZero(); } @Test public void testConvertNegativeSecondsEqualNegativeOne() { - assertEquals(-1, TimeoutUtils.toSeconds(-1111, TimeUnit.MILLISECONDS)); + assertThat(TimeoutUtils.toSeconds(-1111, TimeUnit.MILLISECONDS)).isEqualTo(-1); } @Test public void testConvertNegativeSecondsLessThanNegativeOne() { - assertEquals(-2, TimeoutUtils.toSeconds(-2344, TimeUnit.MILLISECONDS)); + assertThat(TimeoutUtils.toSeconds(-2344, TimeUnit.MILLISECONDS)).isEqualTo(-2); } @Test public void testConvertMoreThanOneMilli() { - assertEquals(2, TimeoutUtils.toMillis(2010, TimeUnit.MICROSECONDS)); + assertThat(TimeoutUtils.toMillis(2010, TimeUnit.MICROSECONDS)).isEqualTo(2); } @Test public void testConvertLessThanOneMilli() { - assertEquals(1, TimeoutUtils.toMillis(999, TimeUnit.NANOSECONDS)); + assertThat(TimeoutUtils.toMillis(999, TimeUnit.NANOSECONDS)).isOne(); } @Test public void testConvertZeroMillis() { - assertEquals(0, TimeoutUtils.toMillis(0, TimeUnit.SECONDS)); + assertThat(TimeoutUtils.toMillis(0, TimeUnit.SECONDS)).isZero(); } @Test public void testConvertNegativeMillisGreaterThanNegativeOne() { // Ensure we convert this to 0 as before, though ideally we wouldn't accept negative values - assertEquals(0, TimeoutUtils.toMillis(-123, TimeUnit.MICROSECONDS)); + assertThat(TimeoutUtils.toMillis(-123, TimeUnit.MICROSECONDS)).isZero(); } @Test public void testConvertNegativeMillisEqualNegativeOne() { - assertEquals(-1, TimeoutUtils.toMillis(-1111, TimeUnit.MICROSECONDS)); + assertThat(TimeoutUtils.toMillis(-1111, TimeUnit.MICROSECONDS)).isEqualTo(-1); } @Test public void testConvertNegativeMillisLessThanNegativeOne() { - assertEquals(-2, TimeoutUtils.toMillis(-2344, TimeUnit.MICROSECONDS)); + assertThat(TimeoutUtils.toMillis(-2344, TimeUnit.MICROSECONDS)).isEqualTo(-2); + } + + @Test // DATAREDIS-815 + public void hasMillisReturnsFalseForTimeoutOfExactSeconds() { + assertThat(TimeoutUtils.hasMillis(Duration.ofSeconds(1))).isFalse(); + } + + @Test // DATAREDIS-815 + public void hasMillisReturnsTrueForTimeoutWithMsec() { + assertThat(TimeoutUtils.hasMillis(Duration.ofMillis(1500))).isTrue(); + } + + @Test // DATAREDIS-815 + public void hasMillisReturnsTrueForTimeoutLessThanOneSecond() { + assertThat(TimeoutUtils.hasMillis(Duration.ofMillis(500))).isTrue(); } }