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 c3fa4e82c..7152798e0 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 @@ -17,6 +17,9 @@ package org.springframework.data.redis.connection.lettuce; import static com.lambdaworks.redis.protocol.CommandType.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; @@ -3105,19 +3108,51 @@ public class LettuceConnection implements RedisConnection { throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode."); } - String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId - + options.toOptionString(); - String script = "return redis.call('HSCAN'" + params + ")"; - - List result = eval(script.getBytes(), ReturnType.MULTI, 0); + byte[] script = createBinaryLuaScriptForScan("HSCAN", key, cursorId, options); + List result = eval(script, ReturnType.MULTI, 0); String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0)); Map values = failsafeReadScanValues(result, LettuceConverters.bytesListToMapConverter()); return new ScanIteration>(Long.valueOf(nextCursorId), values.entrySet()); } + }.open(); } + private byte[] createBinaryLuaScriptForScan(String command, byte[] key, long cursorId, ScanOptions options) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + try { + + outputStream.write(("return redis.call('" + command + "'").getBytes("UTF-8")); + outputStream.write(", '".getBytes("UTF-8")); + writeFilteredKey(key, outputStream); + outputStream.write("', ".getBytes("UTF-8")); + outputStream.write(Long.toString(cursorId).getBytes("UTF-8")); + outputStream.write(options.toOptionString().getBytes("UTF-8")); + outputStream.write(")".getBytes("UTF-8")); + } catch (IOException e) { + e.printStackTrace(); + } + + return outputStream.toByteArray(); + } + + private void writeFilteredKey(byte[] key, OutputStream stream) { + byte toBeFiltered = (byte) '\r'; + for (byte b : key) { + try { + if (toBeFiltered == b) { + stream.write("\\r".getBytes()); + } else { + stream.write(b); + } + } catch (IOException e) { + throw new IllegalArgumentException("Cannot convert key to suitable redis key for lua", e); + } + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions) @@ -3145,11 +3180,8 @@ public class LettuceConnection implements RedisConnection { throw new UnsupportedOperationException("'SSCAN' cannot be called in pipeline / transaction mode."); } - String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId - + options.toOptionString(); - String script = "return redis.call('SSCAN'" + params + ")"; - - List result = eval(script.getBytes(), ReturnType.MULTI, 0); + byte[] script = createBinaryLuaScriptForScan("SSCAN", key, cursorId, options); + List result = eval(script, ReturnType.MULTI, 0); String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0)); List values = failsafeReadScanValues(result, null); @@ -3185,11 +3217,9 @@ public class LettuceConnection implements RedisConnection { throw new UnsupportedOperationException("'ZSCAN' cannot be called in pipeline / transaction mode."); } - String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId - + options.toOptionString(); - String script = "return redis.call('ZSCAN'" + params + ")"; + byte[] script = createBinaryLuaScriptForScan("ZSCAN", key, cursorId, options); + List result = eval(script, ReturnType.MULTI, 0); - List result = eval(script.getBytes(), ReturnType.MULTI, 0); String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0)); List values = failsafeReadScanValues(result, LettuceConverters.bytesListToTupleListConverter()); diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java index 195f886fd..88439273b 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java @@ -26,7 +26,9 @@ import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.BoundHashOperations; +import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.SessionCallback; /** @@ -293,4 +295,22 @@ public class DefaultRedisMap implements RedisMap { throw new IllegalStateException("Cannot read collection with Redis connection in pipeline/multi-exec mode"); } } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisMap#scan() + */ + @Override + public Cursor> scan() { + return scan(ScanOptions.NONE); + } + + /** + * @since 1.4 + * @param options + * @return + */ + private Cursor> scan(ScanOptions options) { + return hashOps.scan(options); + } } diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java index 473fd9b92..82d08e394 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -23,13 +23,16 @@ import java.util.UUID; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.ScanOptions; /** * Default implementation for {@link RedisSet}. Note that the collection support works only with normal, * non-pipeline/multi-exec connections as it requires a reply to be sent right away. * * @author Costin Leau + * @author Christoph Strobl */ public class DefaultRedisSet extends AbstractRedisCollection implements RedisSet { @@ -162,4 +165,22 @@ public class DefaultRedisSet extends AbstractRedisCollection implements Re public DataType getType() { return DataType.SET; } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisSet#scan() + */ + @Override + public Cursor scan() { + return scan(ScanOptions.NONE); + } + + /** + * @since 1.4 + * @param options + * @return + */ + public Cursor scan(ScanOptions options) { + return boundSetOps.scan(options); + } } diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java index 780113cf5..deb8d2473 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -20,9 +20,13 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.BoundZSetOperations; +import org.springframework.data.redis.core.ConvertingCursor; +import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** @@ -30,6 +34,7 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; * non-pipeline/multi-exec connections as it requires a reply to be sent right away. * * @author Costin Leau + * @author Christoph Strobl */ public class DefaultRedisZSet extends AbstractRedisCollection implements RedisZSet { @@ -229,4 +234,29 @@ public class DefaultRedisZSet extends AbstractRedisCollection implements R public DataType getType() { return DataType.ZSET; } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#scan() + */ + @Override + public Cursor scan() { + return new ConvertingCursor, E>(scan(ScanOptions.NONE), new Converter, E>() { + + @Override + public E convert(TypedTuple source) { + return source.getValue(); + } + }); + } + + /** + * @since 1.4 + * @param options + * @return + */ + public Cursor> scan(ScanOptions options) { + return boundZSetOps.scan(options); + } + } diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java b/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java index 10a1166f5..d0687f0a6 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -15,16 +15,25 @@ */ package org.springframework.data.redis.support.collections; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ConcurrentMap; /** * Map view of a Redis hash. * * @author Costin Leau + * @author Christoph Strobl */ public interface RedisMap extends RedisStore, ConcurrentMap { Long increment(K key, long delta); Double increment(K key, double delta); + + /** + * @since 1.4 + * @return + */ + Iterator> scan(); } diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java b/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java index 6b6706601..8d332e718 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java @@ -21,8 +21,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -247,4 +249,9 @@ public class RedisProperties extends Properties implements RedisMap> scan() { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java b/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java index d7ac0a119..8bce68dc3 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -16,12 +16,14 @@ package org.springframework.data.redis.support.collections; import java.util.Collection; +import java.util.Iterator; import java.util.Set; /** * Redis extension for the {@link Set} contract. Supports {@link Set} specific operations backed by Redis operations. * * @author Costin Leau + * @author Christoph Strobl */ public interface RedisSet extends RedisCollection, Set { @@ -48,4 +50,10 @@ public interface RedisSet extends RedisCollection, Set { RedisSet diffAndStore(RedisSet set, String destKey); RedisSet diffAndStore(Collection> sets, String destKey); + + /** + * @since 1.4 + * @return + */ + Iterator scan(); } diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java index 6cfe72a15..19e6bc63d 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.support.collections; import java.util.Collection; import java.util.Comparator; +import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; @@ -124,4 +125,10 @@ public interface RedisZSet extends RedisCollection, Set { * @throws NoSuchElementException sorted set is empty. */ E last(); + + /** + * @since 1.4 + * @return + */ + Iterator scan(); } diff --git a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java index 0f92b36ce..b721eb76d 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,6 +36,7 @@ import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.StringObjectFactory; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; import org.springframework.test.annotation.IfProfileValue; /** @@ -48,6 +50,9 @@ import org.springframework.test.annotation.IfProfileValue; */ @RunWith(Parameterized.class) public class DefaultHashOperationsTests { + + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + private RedisTemplate redisTemplate; private ObjectFactory keyFactory; diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java index df8903e0e..cc138d7e9 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java @@ -29,6 +29,7 @@ import java.util.Set; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,6 +37,7 @@ import org.junit.runners.Parameterized.Parameters; import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; import org.springframework.test.annotation.IfProfileValue; /** @@ -49,6 +51,8 @@ import org.springframework.test.annotation.IfProfileValue; @RunWith(Parameterized.class) public class DefaultZSetOperationsTests { + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + private RedisTemplate redisTemplate; private ObjectFactory keyFactory; diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapTests.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapTests.java index fbe468493..d6e2f5297 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -15,23 +15,16 @@ */ package org.springframework.data.redis.support.collections; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; -import static org.springframework.data.redis.matcher.RedisTestMatchers.isEqual; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -41,6 +34,7 @@ import java.util.Set; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -53,19 +47,35 @@ import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.RedisVersionUtils; import org.springframework.data.redis.connection.ConnectionUtils; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.data.redis.test.util.RedisClientRule; +import org.springframework.data.redis.test.util.RedisDriver; +import org.springframework.data.redis.test.util.WithRedisDriver; +import org.springframework.test.annotation.IfProfileValue; /** * Integration test for Redis Map. * * @author Costin Leau * @author Jennifer Hickey + * @author Christoph Strobl + * @auhtor Thomas Darimont */ @RunWith(Parameterized.class) public abstract class AbstractRedisMapTests { + public @Rule RedisClientRule clientRule = new RedisClientRule() { + public RedisConnectionFactory getConnectionFactory() { + return template.getConnectionFactory(); + } + }; + + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + protected RedisMap map; protected ObjectFactory keyFactory; protected ObjectFactory valueFactory; @@ -478,4 +488,29 @@ public abstract class AbstractRedisMapTests { public void testReplaceNullValue() { map.replace(getKey(), null); } + + /** + * @see DATAREDIS-314 + */ + @Test + @IfProfileValue(name = "redisVersion", value = "2.8+") + @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) + public void testScanWorksCorrectly() { + + K k1 = getKey(); + K k2 = getKey(); + + V v1 = getValue(); + V v2 = getValue(); + + map.put(k1, v1); + map.put(k2, v2); + + Iterator> it = map.scan(); + while (it.hasNext()) { + Entry entry = it.next(); + assertThat(entry.getKey(), anyOf(equalTo(k1), equalTo(k2))); + assertThat(entry.getValue(), anyOf(equalTo(v1), equalTo(v2))); + } + } } diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetTests.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetTests.java index 2d274d8c4..078fb1748 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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,7 +17,7 @@ package org.springframework.data.redis.support.collections; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.springframework.data.redis.matcher.RedisTestMatchers.isEqual; +import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.util.ArrayList; import java.util.Arrays; @@ -26,20 +26,35 @@ import java.util.List; import java.util.Set; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.springframework.data.redis.ObjectFactory; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.support.collections.DefaultRedisSet; -import org.springframework.data.redis.support.collections.RedisSet; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.data.redis.test.util.RedisClientRule; +import org.springframework.data.redis.test.util.RedisDriver; +import org.springframework.data.redis.test.util.WithRedisDriver; +import org.springframework.test.annotation.IfProfileValue; /** * Integration test for Redis set. * * @author Costin Leau + * @author Christoph Strobl + * @author Thomas Darimont */ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTests { + public @Rule RedisClientRule clientRule = new RedisClientRule() { + public RedisConnectionFactory getConnectionFactory() { + return template.getConnectionFactory(); + } + }; + + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + protected RedisSet set; /** @@ -65,7 +80,6 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe return new DefaultRedisSet((BoundSetOperations) set.getOperations().boundSetOps(key)); } - @SuppressWarnings("unchecked") @Test public void testDiff() { RedisSet diffSet1 = createSetFor("test:set:diff1"); @@ -87,7 +101,6 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe assertThat(diff, hasItem(t1)); } - @SuppressWarnings("unchecked") @Test public void testDiffAndStore() { RedisSet diffSet1 = createSetFor("test:set:diff1"); @@ -114,7 +127,6 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe assertEquals(resultName, diff.getKey()); } - @SuppressWarnings("unchecked") @Test public void testIntersect() { RedisSet intSet1 = createSetFor("test:set:int1"); @@ -139,7 +151,6 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe assertThat(inter, hasItem(t2)); } - @SuppressWarnings("unchecked") public void testIntersectAndStore() { RedisSet intSet1 = createSetFor("test:set:int1"); RedisSet intSet2 = createSetFor("test:set:int2"); @@ -213,7 +224,6 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe assertEquals(resultName, union.getKey()); } - @SuppressWarnings("unchecked") @Test public void testIterator() { T t1 = getT(); @@ -290,4 +300,22 @@ public abstract class AbstractRedisSetTests extends AbstractRedisCollectionTe assertEquals(0, result.size()); } + + /** + * @see DATAREDIS-314 + */ + @SuppressWarnings("unchecked") + @IfProfileValue(name = "redisVersion", value = "2.8+") + @Test + @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) + public void testScanWorksCorrectly() { + + Object[] expectedArray = new Object[] { getT(), getT(), getT() }; + collection.addAll((List) Arrays.asList(expectedArray)); + + Iterator it = set.scan(); + while (it.hasNext()) { + assertThat(it.next(), anyOf(equalTo(expectedArray[0]), equalTo(expectedArray[1]), equalTo(expectedArray[2]))); + } + } } diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java index 6d725b34a..f737a71f6 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java @@ -15,15 +15,10 @@ */ package org.springframework.data.redis.support.collections; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeTrue; -import static org.springframework.data.redis.matcher.RedisTestMatchers.isEqual; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.util.Arrays; import java.util.Iterator; @@ -31,21 +26,37 @@ import java.util.NoSuchElementException; import java.util.Set; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.connection.ConnectionUtils; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.BoundZSetOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.data.redis.test.util.RedisClientRule; +import org.springframework.data.redis.test.util.RedisDriver; +import org.springframework.data.redis.test.util.WithRedisDriver; +import org.springframework.test.annotation.IfProfileValue; /** * Integration test for Redis ZSet. * * @author Costin Leau * @author Jennifer Hickey + * @author Thomas Darimont */ public abstract class AbstractRedisZSetTest extends AbstractRedisCollectionTests { + public @Rule RedisClientRule clientRule = new RedisClientRule() { + public RedisConnectionFactory getConnectionFactory() { + return template.getConnectionFactory(); + } + }; + + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + protected RedisZSet zSet; /** @@ -519,4 +530,28 @@ public abstract class AbstractRedisZSetTest extends AbstractRedisCollectionTe Object[] array = collection.toArray(new Object[zSet.size()]); assertArrayEquals(new Object[] { t1, t2, t3, t4 }, array); } + + /** + * @see DATAREDIS-314 + */ + @IfProfileValue(name = "redisVersion", value = "2.8+") + @Test + @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) + public void testScanWorksCorrectly() { + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + zSet.add(t4, 4); + + Iterator it = zSet.scan(); + while (it.hasNext()) { + assertThat(it.next(), anyOf(equalTo(t1), equalTo(t2), equalTo(t3), equalTo(t4))); + } + } } diff --git a/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesTests.java b/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesTests.java index a6702d1fb..14bef3b30 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/RedisPropertiesTests.java @@ -52,6 +52,7 @@ import org.springframework.oxm.xstream.XStreamMarshaller; /** * @author Costin Leau * @author Thomas Darimont + * @author Christoph Strobl */ public class RedisPropertiesTests extends RedisMapTests { @@ -220,6 +221,12 @@ public class RedisPropertiesTests extends RedisMapTests { assertTrue(keys.contains(key3)); } + @Override + @Test(expected = UnsupportedOperationException.class) + public void testScanWorksCorrectly() { + super.testScanWorksCorrectly(); + } + /** * @see DATAREDIS-241 */ @@ -371,4 +378,5 @@ public class RedisPropertiesTests extends RedisMapTests { { stringFactory, stringFactory, xGenericTemplateSrp }, { stringFactory, stringFactory, jsonPersonTemplateSrp }, { stringFactory, stringFactory, jackson2JsonPersonTemplateSrp } }); } + } diff --git a/src/test/java/org/springframework/data/redis/test/util/MinimumRedisVersionRule.java b/src/test/java/org/springframework/data/redis/test/util/MinimumRedisVersionRule.java index 74181e0bc..e376f41bf 100644 --- a/src/test/java/org/springframework/data/redis/test/util/MinimumRedisVersionRule.java +++ b/src/test/java/org/springframework/data/redis/test/util/MinimumRedisVersionRule.java @@ -23,7 +23,7 @@ import org.springframework.data.redis.RedisVersionUtils; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.Version; import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.test.annotation.IfProfileValue; import org.springframework.util.StringUtils; @@ -45,9 +45,10 @@ public class MinimumRedisVersionRule implements TestRule { private Version readServerVersion() { - JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(); connectionFactory.setHostName(SettingsUtils.getHost()); connectionFactory.setPort(SettingsUtils.getPort()); + connectionFactory.setTimeout(100); connectionFactory.afterPropertiesSet(); RedisConnection connection = connectionFactory.getConnection(); diff --git a/src/test/java/org/springframework/data/redis/test/util/RedisClientRule.java b/src/test/java/org/springframework/data/redis/test/util/RedisClientRule.java new file mode 100644 index 000000000..aecdf6ae1 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/test/util/RedisClientRule.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 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. + * 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 org.springframework.data.redis.test.util; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +/** + * A JUnit {@link TestRule} that checks whether a given {@link RedisConnectionFactory} is from the required + * {@link RedisDriver}. + * + * @author Christoph Strobl + * @author Thomas Darimont + */ +public abstract class RedisClientRule implements TestRule { + + @Override + public Statement apply(final Statement base, final Description description) { + + return new Statement() { + + @Override + public void evaluate() throws Throwable { + + WithRedisDriver withRedisDriver = description.getAnnotation(WithRedisDriver.class); + RedisConnectionFactory redisConnectionFactory = getConnectionFactory(); + + if (withRedisDriver != null && redisConnectionFactory != null) { + + boolean valid = true; + for (RedisDriver driver : withRedisDriver.value()) { + + valid &= driver.matches(redisConnectionFactory); + + if (!valid) { + throw new AssumptionViolatedException("not a vaild redis connection for driver: " + driver); + } + } + } + + base.evaluate(); + } + }; + } + + public abstract RedisConnectionFactory getConnectionFactory(); +} diff --git a/src/test/java/org/springframework/data/redis/test/util/RedisDriver.java b/src/test/java/org/springframework/data/redis/test/util/RedisDriver.java new file mode 100644 index 000000000..4617448a0 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/test/util/RedisDriver.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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. + * 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 org.springframework.data.redis.test.util; + +import org.springframework.data.redis.connection.ConnectionUtils; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +/** + * Enumerates the supported Redis driver types. + * + * @author Thomas Darimont + */ +public enum RedisDriver { + + JEDIS { + @Override + public boolean matches(RedisConnectionFactory connectionFactory) { + return ConnectionUtils.isJedis(connectionFactory); + } + }, + + LETTUCE { + @Override + public boolean matches(RedisConnectionFactory connectionFactory) { + return ConnectionUtils.isLettuce(connectionFactory); + } + }, + + SRP { + @Override + public boolean matches(RedisConnectionFactory connectionFactory) { + return ConnectionUtils.isSrp(connectionFactory); + } + }, + + JREDIS { + @Override + public boolean matches(RedisConnectionFactory connectionFactory) { + return ConnectionUtils.isJredis(connectionFactory); + } + }; + + /** + * @param connectionFactory + * @return true of the given {@link RedisConnectionFactory} is supported by the current redis driver. + */ + public abstract boolean matches(RedisConnectionFactory connectionFactory); +} diff --git a/src/test/java/org/springframework/data/redis/test/util/WithRedisDriver.java b/src/test/java/org/springframework/data/redis/test/util/WithRedisDriver.java new file mode 100644 index 000000000..5db4f3b75 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/test/util/WithRedisDriver.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 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. + * 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 org.springframework.data.redis.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to declare the supported {@link RedisDriver} types. + * + * @author Thomas Darimont + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface WithRedisDriver { + + RedisDriver[] value() default {}; +}