diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 4e4b4ecd3..48fc7cad7 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -26,6 +26,7 @@ import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -938,7 +939,13 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return result; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[]) + */ + @Override public Long ttl(byte[] key) { + Long result = delegate.ttl(key); if (isFutureConversion()) { addResultConverter(identityConverter); @@ -946,6 +953,21 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return result; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + Long result = delegate.ttl(key, timeUnit); + if (isFutureConversion()) { + addResultConverter(identityConverter); + } + + return result; + } + public DataType type(byte[] key) { DataType result = delegate.type(key); if (isFutureConversion()) { @@ -1329,11 +1351,33 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return result; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[]) + */ + @Override public Long pTtl(byte[] key) { + Long result = delegate.pTtl(key); if (isFutureConversion()) { addResultConverter(identityConverter); } + + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(byte[] key, TimeUnit timeUnit) { + + Long result = delegate.pTtl(key, timeUnit); + if (isFutureConversion()) { + addResultConverter(identityConverter); + } + return result; } @@ -2059,12 +2103,22 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return result; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#ttl(java.lang.String) + */ + @Override public Long ttl(String key) { - Long result = delegate.ttl(serialize(key)); - if (isFutureConversion()) { - addResultConverter(identityConverter); - } - return result; + return ttl(serialize(key)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#ttl(java.lang.String, java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(String key, TimeUnit timeUnit) { + return ttl(serialize(key), timeUnit); } public DataType type(String key) { @@ -2689,10 +2743,24 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return pExpireAt(serialize(key), unixTimeInMillis); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#pTtl(java.lang.String) + */ + @Override public Long pTtl(String key) { return pTtl(serialize(key)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#pTtl(java.lang.String, java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(String key, TimeUnit timeUnit) { + return pTtl(serialize(key), timeUnit); + } + public String scriptLoad(String script) { String result = delegate.scriptLoad(serialize(script)); if (isFutureConversion()) { diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java index 1e10027d9..a3c70a708 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.data.redis.connection; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; @@ -26,6 +27,7 @@ import org.springframework.data.redis.core.ScanOptions; * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public interface RedisKeyCommands { @@ -186,7 +188,19 @@ public interface RedisKeyCommands { Long ttl(byte[] key); /** - * Get the time to live for {@code key} in milliseconds. + * Get the time to live for {@code key} in and convert it to the given {@link TimeUnit}. + *

+ * See http://redis.io/commands/ttl + * + * @param key + * @param timeUnit + * @return + * @since 1.8 + */ + Long ttl(byte[] key, TimeUnit timeUnit); + + /** + * Get the precise time to live for {@code key} in milliseconds. *

* See http://redis.io/commands/pttl * @@ -195,6 +209,18 @@ public interface RedisKeyCommands { */ Long pTtl(byte[] key); + /** + * Get the precise time to live for {@code key} in and convert it to the given {@link TimeUnit}. + *

+ * See http://redis.io/commands/pttl + * + * @param key + * @param timeUnit + * @return + * @since 1.8 + */ + Long pTtl(byte[] key, TimeUnit timeUnit); + /** * Sort the elements for {@code key}. *

diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index a6682d515..7b2aab8e6 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; @@ -60,34 +61,176 @@ public interface StringRedisConnection extends RedisConnection { Object execute(String command); + /** + * Determine if given {@code key} exists. + *

+ * See http://redis.io/commands/exists + * + * @param key + * @return + */ Boolean exists(String key); + /** + * Delete given {@code keys}. + *

+ * See http://redis.io/commands/del + * + * @param keys + * @return The number of keys that were removed. + */ Long del(String... keys); + /** + * Determine the type stored at {@code key}. + *

+ * See http://redis.io/commands/type + * + * @param key + * @return + */ DataType type(String key); + /** + * Find all keys matching the given {@code pattern}. + *

+ * See http://redis.io/commands/keys + * + * @param pattern + * @return + */ Collection keys(String pattern); + /** + * Rename key {@code oleName} to {@code newName}. + *

+ * See http://redis.io/commands/rename + * + * @param oldName + * @param newName + */ void rename(String oldName, String newName); + /** + * Rename key {@code oleName} to {@code newName} only if {@code newName} does not exist. + *

+ * See http://redis.io/commands/renamenx + * + * @param oldName + * @param newName + * @return + */ Boolean renameNX(String oldName, String newName); + /** + * Set time to live for given {@code key} in seconds. + *

+ * See http://redis.io/commands/expire + * + * @param key + * @param seconds + * @return + */ Boolean expire(String key, long seconds); + /** + * Set time to live for given {@code key} in milliseconds. + *

+ * See http://redis.io/commands/pexpire + * + * @param key + * @param millis + * @return + */ Boolean pExpire(String key, long millis); + /** + * Set the expiration for given {@code key} as a {@literal UNIX} timestamp. + *

+ * See http://redis.io/commands/expireat + * + * @param key + * @param unixTime + * @return + */ Boolean expireAt(String key, long unixTime); + /** + * Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds. + *

+ * See http://redis.io/commands/pexpireat + * + * @param key + * @param unixTimeInMillis + * @return + */ Boolean pExpireAt(String key, long unixTimeInMillis); + /** + * Remove the expiration from given {@code key}. + *

+ * See http://redis.io/commands/persist + * + * @param key + * @return + */ Boolean persist(String key); + /** + * Move given {@code key} to database with {@code index}. + *

+ * See http://redis.io/commands/move + * + * @param key + * @param dbIndex + * @return + */ Boolean move(String key, int dbIndex); + /** + * Get the time to live for {@code key} in seconds. + *

+ * See http://redis.io/commands/ttl + * + * @param key + * @return + */ Long ttl(String key); + /** + * Get the time to live for {@code key} in and convert it to the given {@link TimeUnit}. + *

+ * See http://redis.io/commands/ttl + * + * @param key + * @param timeUnit + * @return + * @since 1.8 + */ + Long ttl(String key, TimeUnit timeUnit); + + /** + * Get the precise time to live for {@code key} in milliseconds. + *

+ * See http://redis.io/commands/pttl + * + * @param key + * @return + */ Long pTtl(String key); + /** + * Get the precise time to live for {@code key} in and convert it to the given {@link TimeUnit}. + *

+ * See http://redis.io/commands/pttl + * + * @param key + * @param timeUnit + * @return + * @since 1.8 + */ + Long pTtl(String key, TimeUnit timeUnit); + String echo(String message); // sort commands diff --git a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java index b462bc091..510d5752d 100644 --- a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java +++ b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.core.convert.converter.Converter; import org.springframework.data.geo.Distance; @@ -43,6 +44,7 @@ import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation; import org.springframework.data.redis.connection.RedisNode.NodeType; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.NumberUtils; import org.springframework.util.ObjectUtils; @@ -275,6 +277,80 @@ abstract public class Converters { + NumberUtils.parseNumber(microseconds, Long.class) / 1000L; } + /** + * Converts {@code seconds} to the given {@link TimeUnit}. + * + * @param seconds + * @param targetUnit must not be {@literal null}. + * @return + * @since 1.8 + */ + public static long secondsToTimeUnit(long seconds, TimeUnit targetUnit) { + + Assert.notNull(targetUnit, "TimeUnit must not be null!"); + + if (seconds > 0) { + return targetUnit.convert(seconds, TimeUnit.SECONDS); + } + + return seconds; + } + + /** + * Creates a new {@link Converter} to convert from seconds to the given {@link TimeUnit}. + * + * @param timeUnit muist not be {@literal null}. + * @return + * @since 1.8 + */ + public static Converter secondsToTimeUnit(final TimeUnit timeUnit) { + + return new Converter() { + + @Override + public Long convert(Long seconds) { + return secondsToTimeUnit(seconds, timeUnit); + } + }; + } + + /** + * Converts {@code milliseconds} to the given {@link TimeUnit}. + * + * @param milliseconds + * @param targetUnit must not be {@literal null}. + * @return + * @since 1.8 + */ + public static long millisecondsToTimeUnit(long milliseconds, TimeUnit targetUnit) { + + Assert.notNull(targetUnit, "TimeUnit must not be null!"); + + if (milliseconds > 0) { + return targetUnit.convert(milliseconds, TimeUnit.MILLISECONDS); + } + + return milliseconds; + } + + /** + * Creates a new {@link Converter} to convert from milliseconds to the given {@link TimeUnit}. + * + * @param timeUnit muist not be {@literal null}. + * @return + * @since 1.8 + */ + public static Converter millisecondsToTimeUnit(final TimeUnit timeUnit) { + + return new Converter() { + + @Override + public Long convert(Long seconds) { + return millisecondsToTimeUnit(seconds, timeUnit); + } + }; + } + /** * {@link Converter} capable of deserializing {@link GeoResults}. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java index e3b272bb5..03dc6948c 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java @@ -450,6 +450,19 @@ public class JedisClusterConnection implements RedisClusterConnection { throw convertJedisAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + try { + return Converters.secondsToTimeUnit(cluster.ttl(key), timeUnit); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } /* * (non-Javadoc) @@ -467,6 +480,22 @@ public class JedisClusterConnection implements RedisClusterConnection { }, topologyProvider.getTopology().getKeyServingMasterNode(key)).getValue(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(final byte[] key, final TimeUnit timeUnit) { + + return clusterCommandExecutor.executeCommandOnSingleNode(new JedisClusterCommandCallback() { + + @Override + public Long doInCluster(Jedis client) { + return Converters.millisecondsToTimeUnit(client.pttl(key), timeUnit); + } + }, topologyProvider.getTopology().getKeyServingMasterNode(key)).getValue(); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#sort(byte[], org.springframework.data.redis.connection.SortParameters) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java index 10331e35b..6a7f75183 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java @@ -53,6 +53,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.TransactionResultConverter; import org.springframework.data.redis.core.Cursor; @@ -968,7 +969,15 @@ public class JedisConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[]) + */ + @Override public Long ttl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { if (isPipelined()) { pipeline(new JedisResult(pipeline.ttl(key))); @@ -978,12 +987,38 @@ public class JedisConnection extends AbstractRedisConnection { transaction(new JedisResult(transaction.ttl(key))); return null; } + return jedis.ttl(key); } catch (Exception ex) { throw convertJedisAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(new JedisResult(pipeline.ttl(key), Converters.secondsToTimeUnit(timeUnit))); + return null; + } + if (isQueueing()) { + transaction(new JedisResult(transaction.ttl(key), Converters.secondsToTimeUnit(timeUnit))); + return null; + } + + return Converters.secondsToTimeUnit(jedis.ttl(key), timeUnit); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + public Boolean pExpire(byte[] key, long millis) { try { @@ -1017,7 +1052,15 @@ public class JedisConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[]) + */ + @Override public Long pTtl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { if (isPipelined()) { pipeline(new JedisResult(pipeline.pttl(key))); @@ -1027,12 +1070,38 @@ public class JedisConnection extends AbstractRedisConnection { transaction(new JedisResult(transaction.pttl(key))); return null; } + return jedis.pttl(key); } catch (Exception ex) { throw convertJedisAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(new JedisResult(pipeline.pttl(key), Converters.millisecondsToTimeUnit(timeUnit))); + return null; + } + if (isQueueing()) { + transaction(new JedisResult(transaction.pttl(key), Converters.millisecondsToTimeUnit(timeUnit))); + return null; + } + + return Converters.millisecondsToTimeUnit(jedis.pttl(key), timeUnit); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + public byte[] dump(byte[] key) { try { if (isPipelined()) { diff --git a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java index d67f703f9..155dcab68 100644 --- a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.jredis.ClientRuntimeException; import org.jredis.JRedis; @@ -50,6 +51,7 @@ import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.types.Expiration; @@ -67,6 +69,7 @@ import org.springframework.util.ReflectionUtils; * @author Thomas Darimont * @author David Liu * @author Ninad Divadkar + * @author Mark Paluch * @deprecated since 1.7. Will be removed in subsequent version. */ @Deprecated @@ -369,10 +372,24 @@ public class JredisConnection extends AbstractRedisConnection { throw new UnsupportedOperationException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[]) + */ + @Override public Long pTtl(byte[] key) { throw new UnsupportedOperationException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(byte[] key, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + public byte[] dump(byte[] key) { throw new UnsupportedOperationException(); } @@ -433,7 +450,15 @@ public class JredisConnection extends AbstractRedisConnection { throw new UnsupportedOperationException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[]) + */ + @Override public Long ttl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { return jredis.ttl(key); } catch (Exception ex) { @@ -441,6 +466,22 @@ public class JredisConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + return Converters.secondsToTimeUnit(jredis.ttl(key), timeUnit); + } catch (Exception ex) { + throw convertJredisAccessException(ex); + } + } + public DataType type(byte[] key) { try { return JredisUtils.convertDataType(jredis.type(key)); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index e3fc5cab5..643c30480 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -60,6 +60,7 @@ import org.springframework.data.redis.connection.RedisSubscribedConnectionExcept import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.TransactionResultConverter; import org.springframework.data.redis.core.Cursor; @@ -949,7 +950,15 @@ public class LettuceConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[]) + */ + @Override public Long pTtl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { if (isPipelined()) { pipeline(new LettuceResult(getAsyncConnection().pttl(key))); @@ -959,12 +968,38 @@ public class LettuceConnection extends AbstractRedisConnection { transaction(new LettuceTxResult(getConnection().pttl(key))); return null; } + return getConnection().pttl(key); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(new LettuceResult(getAsyncConnection().pttl(key), Converters.millisecondsToTimeUnit(timeUnit))); + return null; + } + if (isQueueing()) { + transaction(new LettuceTxResult(getConnection().pttl(key), Converters.millisecondsToTimeUnit(timeUnit))); + return null; + } + + return Converters.millisecondsToTimeUnit(getConnection().pttl(key), timeUnit); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + public byte[] dump(byte[] key) { try { if (isPipelined()) { @@ -1131,7 +1166,15 @@ public class LettuceConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[]) + */ + @Override public Long ttl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { if (isPipelined()) { pipeline(new LettuceResult(getAsyncConnection().ttl(key))); @@ -1141,12 +1184,38 @@ public class LettuceConnection extends AbstractRedisConnection { transaction(new LettuceTxResult(getConnection().ttl(key))); return null; } + return getConnection().ttl(key); } catch (Exception ex) { throw convertLettuceAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(new LettuceResult(getAsyncConnection().ttl(key), Converters.secondsToTimeUnit(timeUnit))); + return null; + } + if (isQueueing()) { + transaction(new LettuceTxResult(getConnection().ttl(key), Converters.secondsToTimeUnit(timeUnit))); + return null; + } + + return Converters.secondsToTimeUnit(getConnection().ttl(key), timeUnit); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + public DataType type(byte[] key) { try { if (isPipelined()) { diff --git a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java index d02b92660..960b1d3fd 100644 --- a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; @@ -49,6 +50,7 @@ import org.springframework.data.redis.connection.RedisSubscribedConnectionExcept import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.types.Expiration; @@ -77,6 +79,7 @@ import redis.reply.Reply; * @author Thomas Darimont * @author David Liu * @author Ninad Divadkar + * @author Mark Paluch * @deprecated since 1.7. Will be removed in subsequent version. */ @Deprecated @@ -765,18 +768,52 @@ public class SrpConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[]) + */ + @Override public Long ttl(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + try { if (isPipelined()) { pipeline(new SrpResult(pipeline.ttl(key))); return null; } + return client.ttl(key).data(); } catch (Exception ex) { throw convertSrpAccessException(ex); } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#ttl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long ttl(byte[] key, TimeUnit timeUnit) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(new SrpResult(pipeline.ttl(key), Converters.secondsToTimeUnit(timeUnit))); + return null; + } + return Converters.secondsToTimeUnit(client.ttl(key).data(), timeUnit); + } catch (Exception ex) { + throw convertSrpAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[]) + */ + @Override public Long pTtl(byte[] key) { try { if (isPipelined()) { @@ -789,6 +826,23 @@ public class SrpConnection extends AbstractRedisConnection { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#pTtl(byte[], java.util.concurrent.TimeUnit) + */ + @Override + public Long pTtl(byte[] key, TimeUnit timeUnit) { + try { + if (isPipelined()) { + pipeline(new SrpResult(pipeline.pttl(key), Converters.millisecondsToTimeUnit(timeUnit))); + return null; + } + return Converters.millisecondsToTimeUnit(client.pttl(key).data(), timeUnit); + } catch (Exception ex) { + throw convertSrpAccessException(ex); + } + } + public byte[] dump(byte[] key) { try { if (isPipelined()) { diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index e042b70e4..6ea8b595e 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -715,14 +715,11 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return execute(new RedisCallback() { public Long doInRedis(RedisConnection connection) { - Long expire; try { - expire = connection.pTtl(rawKey); - return expire < 0 ? expire : timeUnit.convert(expire, TimeUnit.MILLISECONDS); + return connection.pTtl(rawKey, timeUnit); } catch (Exception e) { // Driver may not support pTtl or we may be running on Redis 2.4 - expire = connection.ttl(rawKey); - return expire < 0 ? expire : timeUnit.convert(expire, TimeUnit.SECONDS); + return connection.ttl(rawKey, timeUnit); } } }, true); diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 394277735..0de0d960e 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -967,6 +967,22 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(new Object[] { -1L })); } + /** + * @see DATAREDIS-526 + */ + @Test + public void testTtlWithTimeUnit() { + + connection.set("whatup", "yo"); + actual.add(connection.expire("whatup", 10)); + actual.add(connection.ttl("whatup", TimeUnit.MILLISECONDS)); + + List results = getResults(); + + assertTrue((Long) results.get(1) > TimeUnit.SECONDS.toMillis(5)); + assertTrue((Long) results.get(1) <= TimeUnit.SECONDS.toMillis(10)); + } + @Test @IfProfileValue(name = "redisVersion", value = "2.6+") public void testPTtlNoExpire() { @@ -978,25 +994,48 @@ public abstract class AbstractConnectionIntegrationTests { @Test @IfProfileValue(name = "redisVersion", value = "2.6+") public void testPTtl() { + connection.set("whatup", "yo"); - actual.add(connection.pExpire("whatup", 9000l)); + actual.add(connection.pExpire("whatup", TimeUnit.SECONDS.toMillis(10))); actual.add(connection.pTtl("whatup")); + List results = getResults(); + assertTrue((Long) results.get(1) > -1); } + /** + * @see DATAREDIS-526 + */ + @Test + @IfProfileValue(name = "redisVersion", value = "2.6+") + public void testPTtlWithTimeUnit() { + + connection.set("whatup", "yo"); + actual.add(connection.pExpire("whatup", TimeUnit.MINUTES.toMillis(10))); + actual.add(connection.pTtl("whatup", TimeUnit.SECONDS)); + + List results = getResults(); + + assertTrue((Long) results.get(1) > TimeUnit.MINUTES.toSeconds(9)); + assertTrue((Long) results.get(1) <= TimeUnit.MINUTES.toSeconds(10)); + } + @Test @IfProfileValue(name = "redisVersion", value = "2.6+") public void testDumpAndRestore() { + connection.set("testing", "12"); actual.add(connection.dump("testing".getBytes())); List results = getResults(); initConnection(); + actual.add(connection.del("testing")); actual.add((connection.get("testing"))); connection.restore("testing".getBytes(), 0, (byte[]) results.get(results.size() - 1)); actual.add(connection.get("testing")); - verifyResults(Arrays.asList(new Object[] { 1l, null, "12" })); + + verifyResults(Arrays.asList(new Object[] { 1L, null, "12" })); } @Test diff --git a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java index feb5ae59c..6eac68666 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -19,6 +19,7 @@ import org.springframework.data.geo.Point; /** * @author Christoph Strobl + * @author Mark Paluch */ public interface ClusterConnectionTests { @@ -119,7 +120,7 @@ public interface ClusterConnectionTests { /** * @see DATAREDIS-315 */ - void persistShoudRemoveTTL(); + void persistShouldRemoveTTL(); /** * @see DATAREDIS-315 @@ -141,6 +142,11 @@ public interface ClusterConnectionTests { */ void ttlShouldReturnMinusTwoWhenKeyDoesNotExist(); + /** + * @see DATAREDIS-526 + */ + void ttlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist(); + /** * @see DATAREDIS-315 */ @@ -156,6 +162,11 @@ public interface ClusterConnectionTests { */ void pTtlShouldReturnMinusTwoWhenKeyDoesNotExist(); + /** + * @see DATAREDIS-526 + */ + void pTtlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist(); + /** * @see DATAREDIS-315 */ diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java index fa46b76c0..92254be85 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -33,6 +33,7 @@ import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit; * @author Jennifer Hickey * @author Christoph Strobl * @author Ninad Divadkar + * @author Mark Paluch */ public class DefaultStringRedisConnectionPipelineTests extends DefaultStringRedisConnectionTests { @@ -973,6 +974,15 @@ public class DefaultStringRedisConnectionPipelineTests extends DefaultStringRedi super.testTtl(); } + /** + * @see DATAREDIS-526 + */ + @Override + public void testTtlWithTimeUnit() { + doReturn(Arrays.asList(new Object[] { 5L })).when(nativeConnection).closePipeline(); + super.testTtlWithTimeUnit(); + } + @Test public void testTypeBytes() { doReturn(Arrays.asList(new Object[] { DataType.HASH })).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index 0707c42bd..ef61a56d5 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -32,6 +32,7 @@ import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit; * @author Jennifer Hickey * @author Christoph Strobl * @author Ninad Divadkar + * @author Mark Paluch */ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRedisConnectionTxTests { @@ -1047,6 +1048,16 @@ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRe super.testTtl(); } + /** + * @see DATAREDIS-526 + */ + @Override + public void testTtlWithTimeUnit() { + + doReturn(Arrays.asList(new Object[] { Arrays.asList(new Object[] { 5L }) })).when(nativeConnection).closePipeline(); + super.testTtlWithTimeUnit(); + } + @Test public void testTypeBytes() { doReturn(Arrays.asList(new Object[] { Arrays.asList(new Object[] { DataType.HASH }) })).when(nativeConnection) diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index 9d9dc8cf8..908f14f75 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -1193,6 +1194,18 @@ public class DefaultStringRedisConnectionTests { verifyResults(Arrays.asList(new Object[] { 5l })); } + /** + * @see DATAREDIS-526 + */ + @Test + public void testTtlWithTimeUnit() { + + doReturn(5L).when(nativeConnection).ttl(fooBytes, TimeUnit.SECONDS); + + actual.add(connection.ttl(foo, TimeUnit.SECONDS)); + verifyResults(Arrays.asList(new Object[] { 5L })); + } + @Test public void testTypeBytes() { doReturn(DataType.HASH).when(nativeConnection).type(fooBytes); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java index 6a991b44d..7eda5424a 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java @@ -959,6 +959,16 @@ public class DefaultStringRedisConnectionTxTests extends DefaultStringRedisConne super.testTtl(); } + /** + * @see DATAREDIS-526 + */ + @Override + public void testTtlWithTimeUnit() { + + doReturn(Arrays.asList(new Object[] { 5L })).when(nativeConnection).exec(); + super.testTtl(); + } + @Test public void testTypeBytes() { doReturn(Arrays.asList(new Object[] { DataType.HASH })).when(nativeConnection).exec(); diff --git a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java index a4805a15e..66ee14ea3 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -44,6 +45,7 @@ import org.springframework.util.ObjectUtils; * @author Thomas Darimont * @author David Liu * @author Ninad Divadkar + * @author Mark Paluch */ public class RedisConnectionUnitTests { @@ -688,6 +690,10 @@ public class RedisConnectionUnitTests { return delegate.ttl(key); } + public Long ttl(byte[] key, TimeUnit timeUnit) { + return delegate.pTtl(key, timeUnit); + } + public List bLPop(int timeout, byte[]... keys) { return delegate.bLPop(timeout, keys); } @@ -708,6 +714,10 @@ public class RedisConnectionUnitTests { return delegate.pTtl(key); } + public Long pTtl(byte[] key, TimeUnit timeUnit) { + return delegate.pTtl(key, timeUnit); + } + public Cursor sScan(byte[] key, ScanOptions options) { return delegate.sScan(key, options); } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 5d0a4455a..b57eb0a00 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -39,6 +39,7 @@ import java.util.ListIterator; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; @@ -378,7 +379,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { * @see DATAREDIS-315 */ @Test - public void persistShoudRemoveTTL() { + public void persistShouldRemoveTTL() { nativeConnection.setex(KEY_1_BYTES, 10, VALUE_1_BYTES); @@ -428,6 +429,14 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.ttl(KEY_1_BYTES), is(-2L)); } + /** + * @see DATAREDIS-526 + */ + @Test + public void ttlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist() { + assertThat(clusterConnection.ttl(KEY_1_BYTES, TimeUnit.HOURS), is(-2L)); + } + /** * @see DATAREDIS-315 */ @@ -459,6 +468,14 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.pTtl(KEY_1_BYTES), is(-2L)); } + /** + * @see DATAREDIS-526 + */ + @Test + public void pTtlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist() { + assertThat(clusterConnection.pTtl(KEY_1_BYTES, TimeUnit.HOURS), is(-2L)); + } + /** * @see DATAREDIS-315 */ diff --git a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java index 7dd209d02..23433beeb 100644 --- a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java @@ -349,6 +349,14 @@ public class JRedisConnectionIntegrationTests extends AbstractConnectionIntegrat super.testPTtlNoExpire(); } + /** + * @see DATAREDIS-526 + */ + @Test(expected = UnsupportedOperationException.class) + public void testPTtlWithTimeUnit() { + super.testPTtlWithTimeUnit(); + } + @Test(expected = UnsupportedOperationException.class) public void testDumpAndRestore() { super.testDumpAndRestore(); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index decfce555..fa0b5ffec 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -377,7 +377,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { * @see DATAREDIS-315 */ @Test - public void persistShoudRemoveTTL() { + public void persistShouldRemoveTTL() { nativeConnection.setex(KEY_1, 10, VALUE_1); @@ -427,6 +427,14 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.ttl(KEY_1_BYTES), is(-2L)); } + /** + * @see DATAREDIS-526 + */ + @Test + public void ttlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist() { + assertThat(clusterConnection.ttl(KEY_1_BYTES, TimeUnit.HOURS), is(-2L)); + } + /** * @see DATAREDIS-315 */ @@ -438,6 +446,17 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.ttl(KEY_1_BYTES), is(-1L)); } + /** + * @see DATAREDIS-526 + */ + @Test + public void ttlWithTimeUnitShouldReturnMinusOneWhenKeyDoesNotHaveExpirationSet() { + + nativeConnection.set(KEY_1, VALUE_1); + + assertThat(clusterConnection.ttl(KEY_1_BYTES, TimeUnit.SECONDS), is(-1L)); + } + /** * @see DATAREDIS-315 */ @@ -458,6 +477,14 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.pTtl(KEY_1_BYTES), is(-2L)); } + /** + * @see DATAREDIS-526 + */ + @Test + public void pTtlWithTimeUnitShouldReturnMinusTwoWhenKeyDoesNotExist() { + assertThat(clusterConnection.pTtl(KEY_1_BYTES, TimeUnit.SECONDS), is(-2L)); + } + /** * @see DATAREDIS-315 */ diff --git a/src/test/java/org/springframework/data/redis/core/RedisClusterTemplateTests.java b/src/test/java/org/springframework/data/redis/core/RedisClusterTemplateTests.java index a0b6e554b..ee491d755 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisClusterTemplateTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisClusterTemplateTests.java @@ -128,6 +128,18 @@ public class RedisClusterTemplateTests extends RedisTemplateTests { super.testSortBulkMapper(); } + @Test + @Ignore("This one fails when using GET options on numbers") + public void testGetExpireMillisUsingTransactions() { + super.testGetExpireMillisUsingTransactions(); + } + + @Test + @Ignore("This one fails when using GET options on numbers") + public void testGetExpireMillisUsingPipelining() { + super.testGetExpireMillisUsingPipelining(); + } + @Parameters public static Collection testParams() { diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java index d325ff429..a4ea5d763 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java @@ -599,6 +599,65 @@ public class RedisTemplateTests { assertThat(ttl, lessThan(25L)); } + /** + * @see DATAREDIS-526 + */ + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void testGetExpireMillisUsingTransactions() { + + assumeTrue(redisTemplate.getConnectionFactory() instanceof JedisConnectionFactory + || redisTemplate.getConnectionFactory() instanceof LettuceConnectionFactory); + + final K key = keyFactory.instance(); + List result = redisTemplate.execute(new SessionCallback>() { + + @Override + public List execute(RedisOperations operations) throws DataAccessException { + + operations.multi(); + operations.boundValueOps(key).set(valueFactory.instance()); + operations.expire(key, 1, TimeUnit.DAYS); + operations.getExpire(key, TimeUnit.HOURS); + + return operations.exec(); + } + }); + + assertThat(result, hasSize(2)); + assertThat(((Long) result.get(1)), greaterThanOrEqualTo(23L)); + assertThat(((Long) result.get(1)), lessThan(25L)); + } + + /** + * @see DATAREDIS-526 + */ + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void testGetExpireMillisUsingPipelining() { + + assumeTrue(redisTemplate.getConnectionFactory() instanceof JedisConnectionFactory + || redisTemplate.getConnectionFactory() instanceof LettuceConnectionFactory); + + final K key = keyFactory.instance(); + List result = redisTemplate.executePipelined(new SessionCallback() { + + @Override + public Object execute(RedisOperations operations) throws DataAccessException { + + operations.boundValueOps(key).set(valueFactory.instance()); + operations.expire(key, 1, TimeUnit.DAYS); + operations.getExpire(key, TimeUnit.HOURS); + + return null; + } + }); + + assertThat(result, hasSize(2)); + assertThat(((Long) result.get(1)), greaterThanOrEqualTo(23L)); + assertThat(((Long) result.get(1)), lessThan(25L)); + } + @Test public void testGetExpireMillisNotSupported() {