diff --git a/src/main/java/org/springframework/data/redis/core/script/DefaultScriptExecutor.java b/src/main/java/org/springframework/data/redis/core/script/DefaultScriptExecutor.java index 8ac3f7324..85c3ff2fc 100644 --- a/src/main/java/org/springframework/data/redis/core/script/DefaultScriptExecutor.java +++ b/src/main/java/org/springframework/data/redis/core/script/DefaultScriptExecutor.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; import org.springframework.dao.DataAccessException; -import org.springframework.dao.UncategorizedDataAccessException; +import org.springframework.dao.NonTransientDataAccessException; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.ReturnType; @@ -75,23 +75,23 @@ public class DefaultScriptExecutor implements ScriptExecutor { protected T eval(RedisConnection connection, RedisScript script, ReturnType returnType, int numKeys, byte[][] keysAndArgs, RedisSerializer resultSerializer) { - + Object result; try { result = connection.evalSha(script.getSha1(), returnType, numKeys, keysAndArgs); } catch (Exception e) { - if (!expectionContainsNoScriptError(e)) { + if (!exceptionContainsNoScriptError(e)) { throw e instanceof RuntimeException ? (RuntimeException) e : new RedisSystemException(e.getMessage(), e); } result = connection.eval(scriptBytes(script), returnType, numKeys, keysAndArgs); } - + if (script.getResultType() == null) { return null; } - + return deserializeResult(resultSerializer, result); } @@ -146,9 +146,9 @@ public class DefaultScriptExecutor implements ScriptExecutor { return template.getKeySerializer(); } - private boolean expectionContainsNoScriptError(Exception e) { + private boolean exceptionContainsNoScriptError(Exception e) { - if (!(e instanceof UncategorizedDataAccessException)) { + if (!(e instanceof NonTransientDataAccessException)) { return false; } diff --git a/src/test/java/org/springframework/data/redis/RedisTestProfileValueSource.java b/src/test/java/org/springframework/data/redis/RedisTestProfileValueSource.java index ef384a721..e49601241 100644 --- a/src/test/java/org/springframework/data/redis/RedisTestProfileValueSource.java +++ b/src/test/java/org/springframework/data/redis/RedisTestProfileValueSource.java @@ -16,7 +16,7 @@ package org.springframework.data.redis; import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.test.annotation.ProfileValueSource; /** @@ -27,6 +27,7 @@ import org.springframework.test.annotation.ProfileValueSource; * * @author Jennifer Hickey * @author Christoph Strobl + * @author Thomas Darimont */ public class RedisTestProfileValueSource implements ProfileValueSource { @@ -34,35 +35,56 @@ public class RedisTestProfileValueSource implements ProfileValueSource { private static final String REDIS_26 = "2.6"; private static final String REDIS_28 = "2.8"; private static final String REDIS_VERSION_KEY = "redisVersion"; - private static Version redisVersion; - private static final RedisTestProfileValueSource INSTANCE = new RedisTestProfileValueSource(); + + private static RedisTestProfileValueSource INSTANCE; + + private static final Version redisVersion; + + static { + redisVersion = tryDetectRedisVersionOrReturn(new Version(9, 9, 9)); + } + + private static Version tryDetectRedisVersionOrReturn(Version fallbackVersion) { + + try { + JedisConnectionFactory factory = new JedisConnectionFactory(); + factory.afterPropertiesSet(); + + RedisConnection connection = factory.getConnection(); + Version redisVersion = RedisVersionUtils.getRedisVersion(connection); + + connection.close(); + factory.destroy(); + + return redisVersion; + } catch (Exception ex) { + System.err.println("Couldn't detect redis version!"); + } + + return fallbackVersion; + } public RedisTestProfileValueSource() { - if (redisVersion == null) { - LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(SettingsUtils.getHost(), - SettingsUtils.getPort()); - connectionFactory.afterPropertiesSet(); - RedisConnection connection = connectionFactory.getConnection(); - redisVersion = RedisVersionUtils.getRedisVersion(connection); - connection.close(); - connectionFactory.destroy(); - } + INSTANCE = this; } public String get(String key) { - if (REDIS_VERSION_KEY.equals(key)) { - if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_28)) >= 0) { - return redisVersion.toString(); - } - if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_26)) >= 0) { - return REDIS_26; - } - if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_24)) >= 0) { - return REDIS_24; - } - throw new UnsupportedOperationException("Only Redis 2.4 and higher are supported"); + + if (!REDIS_VERSION_KEY.equals(key)) { + return System.getProperty(key); } - return System.getProperty(key); + + if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_28)) >= 0) { + return REDIS_28; + } + if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_26)) >= 0) { + return REDIS_26; + } + if (redisVersion.compareTo(RedisVersionUtils.parseVersion(REDIS_24)) >= 0) { + return REDIS_24; + } + + throw new UnsupportedOperationException("Only Redis 2.4 and higher are supported"); } public static boolean matches(String key, String value) { diff --git a/src/test/java/org/springframework/data/redis/core/script/DefaultScriptExecutorTests.java b/src/test/java/org/springframework/data/redis/core/script/AbstractDefaultScriptExecutorTests.java similarity index 84% rename from src/test/java/org/springframework/data/redis/core/script/DefaultScriptExecutorTests.java rename to src/test/java/org/springframework/data/redis/core/script/AbstractDefaultScriptExecutorTests.java index 2a84e5e6a..96002d716 100644 --- a/src/test/java/org/springframework/data/redis/core/script/DefaultScriptExecutorTests.java +++ b/src/test/java/org/springframework/data/redis/core/script/AbstractDefaultScriptExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -21,14 +21,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.junit.After; +import org.junit.ClassRule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.Person; -import org.springframework.data.redis.RedisTestProfileValueSource; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisCallback; @@ -39,30 +36,34 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.GenericToStringSerializer; import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.data.redis.test.util.RelaxedJUnit4ClassRunner; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; import org.springframework.scripting.support.StaticScriptSource; import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; -import org.springframework.test.context.ContextConfiguration; /** * Integration test of {@link DefaultScriptExecutor} * * @author Jennifer Hickey + * @author Thomas Darimont + * @author Christoph Strobl */ -@RunWith(RelaxedJUnit4ClassRunner.class) -@ContextConfiguration -@ProfileValueSourceConfiguration(RedisTestProfileValueSource.class) @IfProfileValue(name = "redisVersion", value = "2.6+") -public class DefaultScriptExecutorTests { +public abstract class AbstractDefaultScriptExecutorTests { - @Autowired private RedisConnectionFactory connFactory; + public static @ClassRule MinimumRedisVersionRule minRedisVersion = new MinimumRedisVersionRule(); - @SuppressWarnings("rawtypes") private RedisTemplate template; + @SuppressWarnings("rawtypes")// + private RedisTemplate template; + + protected abstract RedisConnectionFactory getConnectionFactory(); @SuppressWarnings("unchecked") - @After public void tearDown() { + + if (template == null) { + return; + } + template.execute(new RedisCallback() { public Object doInRedis(RedisConnection connection) { connection.flushDb(); @@ -76,7 +77,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteLongResult() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setLocation(new ClassPathResource("org/springframework/data/redis/core/script/increment.lua")); @@ -94,7 +95,7 @@ public class DefaultScriptExecutorTests { this.template = new RedisTemplate(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericToStringSerializer(Long.class)); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setLocation(new ClassPathResource("org/springframework/data/redis/core/script/cas.lua")); @@ -110,7 +111,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteListResultCustomArgsSerializer() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); template.boundListOps("mylist").leftPushAll("a", "b", "c", "d"); DefaultRedisScript script = new DefaultRedisScript(); @@ -126,7 +127,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteMixedListResult() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setLocation(new ClassPathResource("org/springframework/data/redis/core/script/popandlength.lua")); @@ -143,7 +144,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteValueResult() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setScriptText("return redis.call('GET',KEYS[1])"); @@ -159,7 +160,7 @@ public class DefaultScriptExecutorTests { this.template = new RedisTemplate(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericToStringSerializer(Long.class)); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setScriptText("return redis.call('SET',KEYS[1], ARGV[1])"); @@ -175,7 +176,7 @@ public class DefaultScriptExecutorTests { this.template = new RedisTemplate(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(personSerializer); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); DefaultRedisScript script = new DefaultRedisScript(); script.setScriptSource(new StaticScriptSource("redis.call('SET',KEYS[1], ARGV[1])\nreturn 'FOO'")); @@ -192,7 +193,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecutePipelined() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); final DefaultRedisScript script = new DefaultRedisScript(); script.setScriptText("return KEYS[1]"); @@ -212,7 +213,7 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteTx() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); final DefaultRedisScript script = new DefaultRedisScript(); script.setScriptText("return 'bar'..KEYS[1]"); @@ -234,9 +235,9 @@ public class DefaultScriptExecutorTests { @Test public void testExecuteCachedNullKeys() { this.template = new StringRedisTemplate(); - template.setConnectionFactory(connFactory); + template.setConnectionFactory(getConnectionFactory()); template.afterPropertiesSet(); - final DefaultRedisScript script = new DefaultRedisScript(); + DefaultRedisScript script = new DefaultRedisScript(); script.setScriptText("return 'HELLO'"); script.setResultType(String.class); ScriptExecutor scriptExecutor = new DefaultScriptExecutor(template); @@ -244,4 +245,22 @@ public class DefaultScriptExecutorTests { assertEquals("HELLO", scriptExecutor.execute(script, null)); assertEquals("HELLO", scriptExecutor.execute(script, null)); } + + /** + * @see DATAREDIS-356 + */ + @Test + public void shouldTransparentlyReEvaluateScriptIfNotPresent() throws Exception { + + this.template = new StringRedisTemplate(); + template.setConnectionFactory(getConnectionFactory()); + template.afterPropertiesSet(); + + DefaultRedisScript script = new DefaultRedisScript(); + script.setScriptText("return 'BUBU" + System.currentTimeMillis() + "'"); + script.setResultType(String.class); + + ScriptExecutor scriptExecutor = new DefaultScriptExecutor(template); + assertEquals("BUBU", scriptExecutor.execute(script, null).substring(0, 4)); + } } diff --git a/src/test/java/org/springframework/data/redis/core/script/jedis/JedisDefaultScriptExecutorTests.java b/src/test/java/org/springframework/data/redis/core/script/jedis/JedisDefaultScriptExecutorTests.java new file mode 100644 index 000000000..e306be6d3 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/script/jedis/JedisDefaultScriptExecutorTests.java @@ -0,0 +1,71 @@ +/* + * 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.core.script.jedis; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.script.AbstractDefaultScriptExecutorTests; +import org.springframework.data.redis.core.script.DefaultScriptExecutor; + +import redis.clients.jedis.Jedis; + +/** + * Integration test of {@link DefaultScriptExecutor} with {@link Jedis}. + * + * @author Thomas Darimont + */ +public class JedisDefaultScriptExecutorTests extends AbstractDefaultScriptExecutorTests { + + private static JedisConnectionFactory connectionFactory; + + @Before + public void setup() { + + connectionFactory = new JedisConnectionFactory(); + connectionFactory.afterPropertiesSet(); + + } + + @After + public void teardown() { + + super.tearDown(); + + if (connectionFactory != null) { + connectionFactory.destroy(); + } + } + + @Override + protected RedisConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + @Ignore("transactional execution is currently not supported with Jedis") + @Override + public void testExecuteTx() { + // super.testExecuteTx(); + } + + @Ignore("pipelined execution is currently not supported with Jedis") + @Override + public void testExecutePipelined() { + // super.testExecutePipelined(); + } +} diff --git a/src/test/java/org/springframework/data/redis/core/script/lettuce/LettuceDefaultScriptExecutorTests.java b/src/test/java/org/springframework/data/redis/core/script/lettuce/LettuceDefaultScriptExecutorTests.java new file mode 100644 index 000000000..37670a87b --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/script/lettuce/LettuceDefaultScriptExecutorTests.java @@ -0,0 +1,56 @@ +/* + * 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.core.script.lettuce; + +import org.junit.After; +import org.junit.Before; +import org.springframework.data.redis.SettingsUtils; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.script.AbstractDefaultScriptExecutorTests; +import org.springframework.data.redis.core.script.DefaultScriptExecutor; + +/** + * Integration test of {@link DefaultScriptExecutor} with Lettuce. + * + * @author Thomas Darimont + */ +public class LettuceDefaultScriptExecutorTests extends AbstractDefaultScriptExecutorTests { + + private static LettuceConnectionFactory connectionFactory; + + @Before + public void setup() { + + connectionFactory = new LettuceConnectionFactory(SettingsUtils.getHost(), SettingsUtils.getPort()); + connectionFactory.afterPropertiesSet(); + } + + @After + public void teardown() { + + super.tearDown(); + + if (connectionFactory != null) { + connectionFactory.destroy(); + } + } + + @Override + protected RedisConnectionFactory getConnectionFactory() { + return connectionFactory; + } +} diff --git a/src/test/java/org/springframework/data/redis/core/script/srp/SrpDefaultScriptExecutorTests.java b/src/test/java/org/springframework/data/redis/core/script/srp/SrpDefaultScriptExecutorTests.java new file mode 100644 index 000000000..2961452ef --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/script/srp/SrpDefaultScriptExecutorTests.java @@ -0,0 +1,69 @@ +/* + * 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.core.script.srp; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.springframework.data.redis.SettingsUtils; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.srp.SrpConnectionFactory; +import org.springframework.data.redis.core.script.AbstractDefaultScriptExecutorTests; +import org.springframework.data.redis.core.script.DefaultScriptExecutor; + +/** + * Integration test of {@link DefaultScriptExecutor} with SRP. + * + * @author Thomas Darimont + */ +public class SrpDefaultScriptExecutorTests extends AbstractDefaultScriptExecutorTests { + + private static SrpConnectionFactory connectionFactory; + + @Before + public void setup() { + + connectionFactory = new SrpConnectionFactory(SettingsUtils.getHost(), SettingsUtils.getPort()); + connectionFactory.afterPropertiesSet(); + } + + @After + public void teardown() { + + super.tearDown(); + + if (connectionFactory != null) { + connectionFactory.destroy(); + } + } + + @Override + protected RedisConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + @Ignore("transactional execution is currently not supported with SRP") + @Override + public void testExecuteTx() { + // super.testExecuteTx(); + } + + @Ignore("pipelined execution is currently not supported with SRP") + @Override + public void testExecutePipelined() { + // super.testExecutePipelined(); + } +} diff --git a/src/test/java/org/springframework/data/redis/test/util/RelaxedJUnit4ClassRunner.java b/src/test/java/org/springframework/data/redis/test/util/RelaxedJUnit4ClassRunner.java index 673f6d4ef..50c0a89da 100644 --- a/src/test/java/org/springframework/data/redis/test/util/RelaxedJUnit4ClassRunner.java +++ b/src/test/java/org/springframework/data/redis/test/util/RelaxedJUnit4ClassRunner.java @@ -15,7 +15,7 @@ */ package org.springframework.data.redis.test.util; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; import org.junit.Ignore; import org.junit.runners.model.FrameworkMethod; @@ -44,14 +44,16 @@ public class RelaxedJUnit4ClassRunner extends SpringJUnit4ClassRunner { @Override protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) { + return isAnnotatedElementIgnored(frameworkMethod.getMethod()); + } - Method method = frameworkMethod.getMethod(); + private boolean isAnnotatedElementIgnored(AnnotatedElement annotatedElement) { - if (method.isAnnotationPresent(Ignore.class)) { + if (annotatedElement.isAnnotationPresent(Ignore.class)) { return true; } - IfProfileValue ifProfileValue = method.getAnnotation(IfProfileValue.class); + IfProfileValue ifProfileValue = annotatedElement.getAnnotation(IfProfileValue.class); if (ifProfileValue == null) { return false; } diff --git a/src/test/resources/org/springframework/data/redis/core/script/DefaultScriptExecutorTests-context.xml b/src/test/resources/org/springframework/data/redis/core/script/DefaultScriptExecutorTests-context.xml deleted file mode 100644 index 75da6c57d..000000000 --- a/src/test/resources/org/springframework/data/redis/core/script/DefaultScriptExecutorTests-context.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file