diff --git a/src/main/asciidoc/reference/redis-scripting.adoc b/src/main/asciidoc/reference/redis-scripting.adoc index d0caca55d..917442085 100644 --- a/src/main/asciidoc/reference/redis-scripting.adoc +++ b/src/main/asciidoc/reference/redis-scripting.adoc @@ -3,7 +3,7 @@ Redis versions 2.6 and higher provide support for execution of Lua scripts through the http://redis.io/commands/eval[eval] and http://redis.io/commands/evalsha[evalsha] commands. Spring Data Redis provides a high-level abstraction for script execution that handles serialization and automatically makes use of the Redis script cache. -Scripts can be run through the `execute` methods of `RedisTemplate`. RedisTemplate uses a configurable `ScriptExecutor` to execute the provided script. By default, the `ScriptExecutor` takes care of serializing the provided keys and arguments and deserializing the script result. This is done with the `RedisTemplate` key and value serializers. There is an additional `execute` method that allows you to pass custom serializers for the script arguments and result. +Scripts can be run through the `execute` methods of `RedisTemplate` and `ReactiveRedisTemplate`. Both use a configurable `ScriptExecutor` / `ReactiveScriptExecutor` to run the provided script. By default, the `ScriptExecutor` takes care of serializing the provided keys and arguments and deserializing the script result. This is done via the key and value serializers of the template. There is an additional overload that allows you to pass custom serializers for the script arguments and result. The default `ScriptExecutor` optimizes performance by retrieving the SHA1 of the script and attempting first to run `evalsha`, falling back to `eval` if the script is not yet present in the Redis script cache. @@ -13,19 +13,21 @@ Here's an example that executes a common "check-and-set" scenario using a Lua sc ---- @Bean public RedisScript script() { - DefaultRedisScript redisScript = new DefaultRedisScript(); - redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua"))); - redisScript.setResultType(Boolean.class); + + ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua"); + return RedisScript.of(scriptSource, Boolean.class); } ---- [source,java] ---- -public class Example { +public class Example { + @Autowired - RedisScript script; + RedisScript script; + public boolean checkAndSet(String expectedValue, String newValue) { - return redisTemplate.execute(script, Collections.singletonList("key"), expectedValue, newValue); + return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue)); } } ---- @@ -41,7 +43,7 @@ public class Example { return false ---- -The XML above configures a `DefaultRedisScript` pointing to a file called `checkandset.lua`, which is expected to return a boolean value. The script `resultType` should be one of `Long`, `Boolean`, `List`, or deserialized value type. It can also be null if the script returns a throw-away status (i.e "OK"). It is ideal to configure a single instance of `DefaultRedisScript` in your application context to avoid re-calculation of the script's SHA1 on every script execution. +The code above configures a `RedisScript` pointing to a file called `checkandset.lua`, which is expected to return a boolean value. The script `resultType` should be one of `Long`, `Boolean`, `List`, or deserialized value type. It can also be `null` if the script returns a throw-away status (i.e "OK"). It is ideal to configure a single instance of `DefaultRedisScript` in your application context to avoid re-calculation of the script's SHA1 on every script execution. The checkAndSet method above then executes th Scripts can be executed within a `SessionCallback` as part of a transaction or pipeline. See <> and <> for more information. diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java index aef45c341..cff245a7c 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveScriptingCommands.java @@ -19,11 +19,16 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; /** - * Redis Scripting commands executed using reactive infrastructure. + * Redis Scripting commands executed using reactive infrastructure. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.0 */ public interface ReactiveScriptingCommands { @@ -47,19 +52,31 @@ public interface ReactiveScriptingCommands { * Execute the script by calling {@link #evalSha(String, ReturnType, int, ByteBuffer...)}. * * @param script must not be {@literal null}. - * @return + * @return never {@literal null}. * @see Redis Documentation: SCRIPT LOAD */ Mono scriptLoad(ByteBuffer script); + /** + * Check if given {@code scriptSha} exist in script cache. + * + * @param scriptSha The sha1 of the script is present in script cache. Must not be {@literal null}. + * @return a {@link Mono} indicating if script is present. + */ + default Mono scriptExists(String scriptSha) { + + Assert.notNull(scriptSha, "ScriptSha must not be null!"); + return scriptExists(Collections.singletonList(scriptSha)).singleOrEmpty(); + } + /** * Check if given {@code scriptShas} exist in script cache. * - * @param scriptShas - * @return one entry per given scriptSha in returned list. + * @param scriptShas must not be {@literal null}. + * @return {@link Flux} emitting one entry per scriptSha in given {@link List}. * @see Redis Documentation: SCRIPT EXISTS */ - Flux scriptExists(String... scriptShas); + Flux scriptExists(List scriptShas); /** * Evaluate given {@code script}. @@ -68,7 +85,7 @@ public interface ReactiveScriptingCommands { * @param returnType must not be {@literal null}. * @param numKeys * @param keysAndArgs must not be {@literal null}. - * @return + * @return never {@literal null}. * @see Redis Documentation: EVAL */ Flux eval(ByteBuffer script, ReturnType returnType, int numKeys, ByteBuffer... keysAndArgs); @@ -80,7 +97,7 @@ public interface ReactiveScriptingCommands { * @param returnType must not be {@literal null}. * @param numKeys * @param keysAndArgs must not be {@literal null}. - * @return + * @return never {@literal null}. * @see Redis Documentation: EVALSHA */ Flux evalSha(String scriptSha, ReturnType returnType, int numKeys, ByteBuffer... keysAndArgs); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommands.java index 2e62c0bf5..75421f358 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommands.java @@ -22,13 +22,17 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.List; import org.springframework.data.redis.connection.ReactiveScriptingCommands; import org.springframework.data.redis.connection.ReturnType; import org.springframework.util.Assert; /** + * {@link ReactiveScriptingCommands} implementation for the Lettuce Redis driver. + * * @author Mark Paluch + * @author Christoph Strobl * @since 2.0 */ class LettuceReactiveScriptingCommands implements ReactiveScriptingCommands { @@ -81,14 +85,14 @@ class LettuceReactiveScriptingCommands implements ReactiveScriptingCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.ReactiveScriptingCommands#scriptExists(java.lang.String[]) + * @see org.springframework.data.redis.connection.ReactiveScriptingCommands#scriptExists(java.util.List) */ @Override - public Flux scriptExists(String... scriptShas) { + public Flux scriptExists(List scriptShas) { - Assert.notNull(scriptShas, "Script SHAs must not be null!"); + Assert.notEmpty(scriptShas, "Script SHAs must not be empty!"); - return connection.execute(cmd -> cmd.scriptExists(scriptShas)); + return connection.execute(cmd -> cmd.scriptExists(scriptShas.toArray(new String[scriptShas.size()]))); } /* diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java index 724b949d5..4ff0da234 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java @@ -20,13 +20,15 @@ import reactor.core.publisher.Mono; import java.time.Duration; import java.time.Instant; +import java.util.Collections; import java.util.List; import org.reactivestreams.Publisher; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.data.redis.serializer.RedisElementReader; +import org.springframework.data.redis.serializer.RedisElementWriter; import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.data.redis.serializer.RedisSerializer; /** @@ -181,31 +183,54 @@ public interface ReactiveRedisOperations { // Methods dealing with Redis Lua scripts // ------------------------------------------------------------------------- + /** + * Executes the given {@link RedisScript}. + * + * @param script must not be {@literal null}. + * @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null}, + * likely indicating a throw-away status reply (i.e. "OK"). + */ + default Flux execute(RedisScript script) { + return execute(script, Collections.emptyList()); + } + + /** + * Executes the given {@link RedisScript}. + * + * @param script must not be {@literal null}. + * @param keys must not be {@literal null}. + * @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null}, + * likely indicating a throw-away status reply (i.e. "OK"). + */ + default Flux execute(RedisScript script, List keys) { + return execute(script, keys, Collections.emptyList()); + } + /** * Executes the given {@link RedisScript} * - * @param script The script to execute - * @param keys keys that need to be passed to the script. - * @param args args that need to be passed to the script. - * @return return value of the script or raw {@link java.nio.ByteBuffer} if {@link RedisScript#getResultType()} is - * {@literal null}, likely indicating a throw-away status reply (i.e. "OK"). + * @param script The script to execute. Must not be {@literal null}. + * @param keys keys that need to be passed to the script. Must not be {@literal null}. + * @param args args that need to be passed to the script. Must not be {@literal null}. + * @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null}, + * likely indicating a throw-away status reply (i.e. "OK"). */ - Flux execute(RedisScript script, List keys, Object... args); + Flux execute(RedisScript script, List keys, List args); /** * Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script * arguments and result. * * @param script The script to execute - * @param argsSerializerPair The {@link SerializationPair} to use for serializing args - * @param resultSerializerPair The {@link SerializationPair} to use for serializing the script return value + * @param argsWriter The {@link RedisElementWriter} to use for serializing args + * @param resultReader The {@link RedisElementReader} to use for serializing the script return value * @param keys keys that need to be passed to the script. * @param args args that need to be passed to the script. - * @return return value of the script or raw {@link java.nio.ByteBuffer} if {@link RedisScript#getResultType()} is - * {@literal null}, likely indicating a throw-away status reply (i.e. "OK"). + * @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null}, + * likely indicating a throw-away status reply (i.e. "OK"). */ - Flux execute(RedisScript script, SerializationPair argsSerializerPair, - SerializationPair resultSerializerPair, List keys, Object... args); + Flux execute(RedisScript script, List keys, List args, RedisElementWriter argsWriter, + RedisElementReader resultReader); // ------------------------------------------------------------------------- // Methods to obtain specific operations interface objects. diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java index 8fcc88338..eee921392 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java @@ -30,11 +30,12 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection; import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.core.script.DefaultScriptOperators; +import org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor; +import org.springframework.data.redis.core.script.ReactiveScriptExecutor; import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.data.redis.core.script.ScriptOperators; +import org.springframework.data.redis.serializer.RedisElementReader; +import org.springframework.data.redis.serializer.RedisElementWriter; import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -58,7 +59,7 @@ public class ReactiveRedisTemplate implements ReactiveRedisOperations serializationContext; private final boolean exposeConnection; - private final ScriptOperators scriptOperators; + private final ReactiveScriptExecutor reactiveScriptExecutor; /** * Creates new {@link ReactiveRedisTemplate} using given {@link ReactiveRedisConnectionFactory} and @@ -89,7 +90,7 @@ public class ReactiveRedisTemplate implements ReactiveRedisOperations(connectionFactory, serializationContext); + this.reactiveScriptExecutor = new DefaultReactiveScriptExecutor<>(connectionFactory, serializationContext); } /** @@ -491,21 +492,23 @@ public class ReactiveRedisTemplate implements ReactiveRedisOperations Flux execute(RedisScript script, List keys, Object... args) { - return scriptOperators.execute(script, keys, args); + public Flux execute(RedisScript script, List keys, List args) { + return reactiveScriptExecutor.execute(script, keys, args); } - /* (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, java.util.List, java.lang.Object[]) + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List, org.springframework.data.redis.serializer.RedisElementWriter, org.springframework.data.redis.serializer.RedisElementReader) */ @Override - public Flux execute(RedisScript script, SerializationPair argsSerializerPair, - SerializationPair resultSerializerPair, List keys, Object... args) { - return scriptOperators.execute(script, argsSerializerPair, resultSerializerPair, keys, args); + public Flux execute(RedisScript script, List keys, List args, RedisElementWriter argsWriter, + RedisElementReader resultReader) { + return reactiveScriptExecutor.execute(script, keys, args, argsWriter, resultReader); } // ------------------------------------------------------------------------- diff --git a/src/main/java/org/springframework/data/redis/core/script/DefaultScriptOperators.java b/src/main/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutor.java similarity index 62% rename from src/main/java/org/springframework/data/redis/core/script/DefaultScriptOperators.java rename to src/main/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutor.java index a89637bb8..f1aab5965 100644 --- a/src/main/java/org/springframework/data/redis/core/script/DefaultScriptOperators.java +++ b/src/main/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutor.java @@ -19,37 +19,41 @@ import reactor.core.publisher.Flux; import java.nio.ByteBuffer; import java.util.List; +import java.util.stream.Stream; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.ReactiveRedisConnection; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.core.ReactiveRedisCallback; +import org.springframework.data.redis.serializer.RedisElementReader; +import org.springframework.data.redis.serializer.RedisElementWriter; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.util.Assert; /** - * Default implementation of {@link ScriptOperators}. Optimizes performance by attempting to execute script first using - * evalsha, then falling back to eval if Redis has not yet cached the script. + * Default implementation of {@link ReactiveScriptExecutor}. Optimizes performance by attempting to execute script first + * using {@code EVALSHA}, then falling back to {@code EVAL} if Redis has not yet cached the script. * * @author Mark Paluch + * @author Christoph Strobl * @param The type of keys that may be passed during script execution * @since 2.0 */ -public class DefaultScriptOperators implements ScriptOperators { +public class DefaultReactiveScriptExecutor implements ReactiveScriptExecutor { private final ReactiveRedisConnectionFactory connectionFactory; private final RedisSerializationContext serializationContext; /** - * Creates a new {@link DefaultScriptOperators} given {@link ReactiveRedisConnectionFactory} and + * Creates a new {@link DefaultReactiveScriptExecutor} given {@link ReactiveRedisConnectionFactory} and * {@link RedisSerializationContext}. * * @param connectionFactory must not be {@literal null}. * @param serializationContext must not be {@literal null}. */ - public DefaultScriptOperators(ReactiveRedisConnectionFactory connectionFactory, + public DefaultReactiveScriptExecutor(ReactiveRedisConnectionFactory connectionFactory, RedisSerializationContext serializationContext) { Assert.notNull(connectionFactory, "ReactiveRedisConnectionFactory must not be null!"); @@ -59,49 +63,51 @@ public class DefaultScriptOperators implements ScriptOperators { this.serializationContext = serializationContext; } - /* (non-Javadoc) - * @see org.springframework.data.redis.core.script.ScriptOperators#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object[]) + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.script.ReactiveScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List) */ @Override @SuppressWarnings("unchecked") - public Flux execute(RedisScript script, List keys, Object... args) { + public Flux execute(RedisScript script, List keys, List args) { Assert.notNull(script, "RedisScript must not be null!"); Assert.notNull(keys, "Keys must not be null!"); Assert.notNull(args, "Args must not be null!"); // use the Template's value serializer for args and result - return execute(script, serializationContext.getKeySerializationPair(), - (SerializationPair) serializationContext.getValueSerializationPair(), keys, args); + return execute(script, keys, args, serializationContext.getKeySerializationPair().getWriter(), + (RedisElementReader) serializationContext.getValueSerializationPair().getReader()); } - /* (non-Javadoc) - * @see org.springframework.data.redis.core.script.ScriptOperators#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, java.util.List, java.lang.Object[]) + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.script.ReactiveScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List, org.springframework.data.redis.serializer.RedisElementWriter, org.springframework.data.redis.serializer.RedisElementReader) */ @Override @SuppressWarnings("unchecked") - public Flux execute(RedisScript script, SerializationPair argsSerializerPair, - SerializationPair resultSerializationPair, List keys, Object... args) { + public Flux execute(RedisScript script, List keys, List args, RedisElementWriter argsWriter, + RedisElementReader resultReader) { Assert.notNull(script, "RedisScript must not be null!"); - Assert.notNull(argsSerializerPair, "Argument SerializationPair must not be null!"); - Assert.notNull(resultSerializationPair, "Result SerializationPair must not be null!"); + Assert.notNull(argsWriter, "Argument Writer must not be null!"); + Assert.notNull(resultReader, "Result Reader must not be null!"); Assert.notNull(keys, "Keys must not be null!"); Assert.notNull(args, "Args must not be null!"); return execute(connection -> { ReturnType returnType = ReturnType.fromJavaType(script.getResultType()); - ByteBuffer[] keysAndArgs = keysAndArgs((SerializationPair) argsSerializerPair, keys, args); + ByteBuffer[] keysAndArgs = keysAndArgs(argsWriter, keys, args); int keySize = keys.size(); - return eval(connection, script, returnType, keySize, keysAndArgs, resultSerializationPair); + return eval(connection, script, returnType, keySize, keysAndArgs, resultReader); }); } protected Flux eval(ReactiveRedisConnection connection, RedisScript script, ReturnType returnType, - int numKeys, ByteBuffer[] keysAndArgs, SerializationPair resultSerializer) { + int numKeys, ByteBuffer[] keysAndArgs, RedisElementReader resultReader) { Flux result = connection.scriptingCommands().evalSha(script.getSha1(), returnType, numKeys, keysAndArgs); @@ -115,41 +121,25 @@ public class DefaultScriptOperators implements ScriptOperators { .error(e instanceof RuntimeException ? (RuntimeException) e : new RedisSystemException(e.getMessage(), e)); }); - return script.getResultType() == null ? result : deserializeResult(resultSerializer, result); + return script.returnsRawValue() ? result : deserializeResult(resultReader, result); } - protected ByteBuffer[] keysAndArgs(SerializationPair argsSerializer, List keys, Object[] args) { + protected ByteBuffer[] keysAndArgs(RedisElementWriter argsWriter, List keys, List args) { - int keySize = keys != null ? keys.size() : 0; - ByteBuffer[] keysAndArgs = new ByteBuffer[args.length + keySize]; - int i = 0; - - if (keys != null) { - for (K key : keys) { - if (key instanceof ByteBuffer) { - keysAndArgs[i++] = (ByteBuffer) key; - } else { - keysAndArgs[i++] = keySerializer().getWriter().write(key); - } - } - } - - for (Object arg : args) { - if (arg instanceof ByteBuffer) { - keysAndArgs[i++] = (ByteBuffer) arg; - } else { - keysAndArgs[i++] = argsSerializer.getWriter().write(arg); - } - } - return keysAndArgs; + return Stream.concat(keys.stream().map(t -> keySerializer().getWriter().write(t)), + args.stream().map(t -> argsWriter.write(t))).toArray(size -> new ByteBuffer[size]); } + /** + * @param script + * @return + */ protected ByteBuffer scriptBytes(RedisScript script) { return serializationContext.getStringSerializationPair().getWriter().write(script.getScriptAsString()); } - protected Flux deserializeResult(SerializationPair pair, Flux result) { - return result.map(it -> ScriptUtils.deserializeResult(pair.getReader(), it)); + protected Flux deserializeResult(RedisElementReader reader, Flux result) { + return result.map(it -> ScriptUtils.deserializeResult(reader, it)); } protected SerializationPair keySerializer() { @@ -174,6 +164,7 @@ public class DefaultScriptOperators implements ScriptOperators { try { return Flux.defer(() -> action.doInRedis(conn)).doFinally(signal -> conn.close()); } catch (RuntimeException e) { + conn.close(); throw e; } diff --git a/src/main/java/org/springframework/data/redis/core/script/DefaultRedisScript.java b/src/main/java/org/springframework/data/redis/core/script/DefaultRedisScript.java index 63513c5be..7723ef0ed 100644 --- a/src/main/java/org/springframework/data/redis/core/script/DefaultRedisScript.java +++ b/src/main/java/org/springframework/data/redis/core/script/DefaultRedisScript.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 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. @@ -37,23 +37,32 @@ import org.springframework.util.Assert; public class DefaultRedisScript implements RedisScript, InitializingBean { private ScriptSource scriptSource; - private String sha1; - private Class resultType; - private final Object shaModifiedMonitor = new Object(); /** * Creates a new {@link DefaultRedisScript} */ - public DefaultRedisScript() {} + public DefaultRedisScript() { + + } + + /** + * Creates a new {@link DefaultRedisScript} + * + * @param script must not be {@literal null}. + * @since 2.0 + */ + public DefaultRedisScript(String script) { + this(script, null); + } /** * Creates a new {@link DefaultRedisScript} * - * @param script - * @param resultType + * @param script must not be {@literal null}. + * @param resultType can be {@literal null}. */ public DefaultRedisScript(String script, Class resultType) { @@ -61,11 +70,20 @@ public class DefaultRedisScript implements RedisScript, InitializingBean { this.resultType = resultType; } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ public void afterPropertiesSet() throws Exception { - Assert.notNull(this.scriptSource, "Either script, script location," + " or script source is required"); + Assert.state(this.scriptSource != null, "Either script, script location," + " or script source is required"); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.script.RedisScript#getSha1() + */ public String getSha1() { + synchronized (shaModifiedMonitor) { if (sha1 == null || scriptSource.isModified()) { this.sha1 = DigestUtils.sha1DigestAsHex(getScriptAsString()); @@ -74,11 +92,20 @@ public class DefaultRedisScript implements RedisScript, InitializingBean { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.script.RedisScript#getResultType() + */ public Class getResultType() { return this.resultType; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.script.RedisScript#getScriptAsString() + */ public String getScriptAsString() { + try { return scriptSource.getScriptAsString(); } catch (IOException e) { @@ -95,7 +122,7 @@ public class DefaultRedisScript implements RedisScript, InitializingBean { } /** - * @param script The script text + * @param scriptText The script text */ public void setScriptText(String scriptText) { this.scriptSource = new StaticScriptSource(scriptText); 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 b2d52793e..7ee7a76a7 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 @@ -17,7 +17,6 @@ package org.springframework.data.redis.core.script; import java.util.List; -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; diff --git a/src/main/java/org/springframework/data/redis/core/script/ReactiveScriptExecutor.java b/src/main/java/org/springframework/data/redis/core/script/ReactiveScriptExecutor.java new file mode 100644 index 000000000..030f73ea2 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/core/script/ReactiveScriptExecutor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 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; + +import reactor.core.publisher.Flux; + +import java.util.Collections; +import java.util.List; + +import org.springframework.data.redis.serializer.RedisElementReader; +import org.springframework.data.redis.serializer.RedisElementWriter; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * Executes {@link RedisScript}s using reactive infrastructure. + * + * @author Mark Paluch + * @author Christoph Strobl + * @param The type of keys that may be passed during script execution + * @since 2.0 + */ +public interface ReactiveScriptExecutor { + + /** + * Execute the given {@link RedisScript} + * + * @param script must not be {@literal null}. + * @return the return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is + * {@literal null}, likely indicating a throw-away status reply (i.e. "OK") + */ + default Flux execute(RedisScript script) { + return execute(script, Collections.emptyList()); + } + + /** + * Execute the given {@link RedisScript} + * + * @param script must not be {@literal null}. + * @param keys must not be {@literal null}. + * @return the return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is + * {@literal null}, likely indicating a throw-away status reply (i.e. "OK") + */ + default Flux execute(RedisScript script, List keys) { + return execute(script, keys, Collections.emptyList()); + } + + /** + * Executes the given {@link RedisScript} + * + * @param script The script to execute. Must not be {@literal null}. + * @param keys Any keys that need to be passed to the script. Must not be {@literal null}. + * @param args Any args that need to be passed to the script. Can be {@literal empty}. + * @return The return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is + * {@literal null}, likely indicating a throw-away status reply (i.e. "OK") + */ + Flux execute(RedisScript script, List keys, List args); + + /** + * Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script + * arguments and result. + * + * @param script The script to execute. must not be {@literal null}. + * @param keys Any keys that need to be passed to the script + * @param args Any args that need to be passed to the script + * @param argsWriter The {@link RedisElementWriter} to use for serializing args. Must not be {@literal null}. + * @param resultReader The {@link RedisElementReader} to use for serializing the script return value. Must not be + * {@literal null}. + * @return The return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is + * {@literal null}, likely indicating a throw-away status reply (i.e. "OK") + */ + Flux execute(RedisScript script, List keys, List args, RedisElementWriter argsWriter, + RedisElementReader resultReader); +} diff --git a/src/main/java/org/springframework/data/redis/core/script/RedisScript.java b/src/main/java/org/springframework/data/redis/core/script/RedisScript.java index 5ea6b5639..68e42a1c0 100644 --- a/src/main/java/org/springframework/data/redis/core/script/RedisScript.java +++ b/src/main/java/org/springframework/data/redis/core/script/RedisScript.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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,30 +15,67 @@ */ package org.springframework.data.redis.core.script; +import org.springframework.util.Assert; + /** * A script to be executed using the Redis scripting support available as of * version 2.6 * * @author Jennifer Hickey - * @param The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be null if - * the script returns a throw-away status (i.e "OK") + * @author Christoph Strobl + * @param The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be + * {@litearl null} if the script returns a throw-away status (i.e "OK") */ public interface RedisScript { /** - * @return The SHA1 of the script, used for executing Redis evalsha command + * @return The SHA1 of the script, used for executing Redis evalsha command. */ String getSha1(); /** - * @return The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be null if - * the script returns a throw-away status (i.e "OK") + * @return The script result type. Should be one of Long, Boolean, List, or deserialized value type. {@literal null} + * if the script returns a throw-away status (i.e "OK"). */ Class getResultType(); /** - * @return The script contents + * @return The script contents. */ String getScriptAsString(); + /** + * @return {@literal true} if result type is {@literal null} and does not need any further deserialization. + * @since 2.0 + */ + default boolean returnsRawValue() { + return getResultType() == null; + } + + /** + * Creates new {@link RedisScript} from {@link String}. + * + * @param script must not be {@literal null}. + * @return new instance of {@link RedisScript}. + * @since 2.0 + */ + static RedisScript of(String script) { + return new DefaultRedisScript<>(script); + } + + /** + * Creates new {@link RedisScript} from {@link String}. + * + * @param script must not be {@literal null}. + * @param resultType must not be {@literal null}. + * @return new instance of {@link RedisScript}. + * @since 2.0 + */ + static RedisScript of(String script, Class resultType) { + + Assert.notNull(script, "Script must not be null!"); + Assert.notNull(resultType, "ResultType must not be null!"); + + return new DefaultRedisScript(script, resultType); + } } diff --git a/src/main/java/org/springframework/data/redis/core/script/ScriptOperators.java b/src/main/java/org/springframework/data/redis/core/script/ScriptOperators.java deleted file mode 100644 index b389c342d..000000000 --- a/src/main/java/org/springframework/data/redis/core/script/ScriptOperators.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017 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; - -import reactor.core.publisher.Flux; - -import java.util.List; - -import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; -import org.springframework.data.redis.serializer.RedisSerializer; - -/** - * Executes {@link RedisScript}s using reactive infrastructure. - * - * @author Mark Paluch - * @param The type of keys that may be passed during script execution - * @since 2.0 - */ -public interface ScriptOperators { - - /** - * Executes the given {@link RedisScript} - * - * @param script The script to execute - * @param keys Any keys that need to be passed to the script - * @param args Any args that need to be passed to the script - * @return The return value of the script or null if {@link RedisScript#getResultType()} is null, likely indicating a - * throw-away status reply (i.e. "OK") - */ - Flux execute(RedisScript script, List keys, Object... args); - - /** - * Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script - * arguments and result. - * - * @param script The script to execute - * @param argsSerializer The {@link SerializationPair} to use for serializing args - * @param resultSerializer The {@link SerializationPair} to use for serializing the script return value - * @param keys Any keys that need to be passed to the script - * @param args Any args that need to be passed to the script - * @return The return value of the script or null if {@link RedisScript#getResultType()} is null, likely indicating a - * throw-away status reply (i.e. "OK") - */ - Flux execute(RedisScript script, SerializationPair argsSerializer, SerializationPair resultSerializer, - List keys, Object... args); -} diff --git a/src/main/java/org/springframework/data/redis/core/script/ScriptUtils.java b/src/main/java/org/springframework/data/redis/core/script/ScriptUtils.java index 80fca3ea2..b98673beb 100644 --- a/src/main/java/org/springframework/data/redis/core/script/ScriptUtils.java +++ b/src/main/java/org/springframework/data/redis/core/script/ScriptUtils.java @@ -27,6 +27,7 @@ import org.springframework.data.redis.serializer.RedisSerializer; * Utilities for Lua script execution and result deserialization. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.0 */ class ScriptUtils { @@ -45,7 +46,7 @@ class ScriptUtils { static T deserializeResult(RedisSerializer resultSerializer, Object result) { if (result instanceof byte[]) { - return resultSerializer == null ? (T) result : resultSerializer.deserialize((byte[]) result); + return resultSerializer.deserialize((byte[]) result); } if (result instanceof List) { @@ -66,7 +67,7 @@ class ScriptUtils { * Deserialize {@code result} using {@link RedisElementReader} to the reader type. Collection types and intermediate * collection elements are deserialized recursivly. * - * @param resultSerializer must not be {@literal null}. + * @param reader must not be {@literal null}. * @param result must not be {@literal null}. * @return the deserialized result. */ @@ -75,7 +76,7 @@ class ScriptUtils { if (result instanceof ByteBuffer) { - return reader == null ? (T) result : reader.read((ByteBuffer) result); + return reader.read((ByteBuffer) result); } if (result instanceof List) { diff --git a/src/main/java/org/springframework/data/redis/serializer/RedisElementReader.java b/src/main/java/org/springframework/data/redis/serializer/RedisElementReader.java index 6bb5fc197..dc39b0e81 100644 --- a/src/main/java/org/springframework/data/redis/serializer/RedisElementReader.java +++ b/src/main/java/org/springframework/data/redis/serializer/RedisElementReader.java @@ -17,11 +17,14 @@ package org.springframework.data.redis.serializer; import java.nio.ByteBuffer; +import org.springframework.util.Assert; + /** * Strategy interface that specifies a deserializer that can deserialize a binary element representation stored in Redis * into an object. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.0 */ @FunctionalInterface @@ -34,4 +37,16 @@ public interface RedisElementReader { * @return the deserialized value. */ T read(ByteBuffer buffer); + + /** + * Create new {@link RedisElementReader} using given {@link RedisSerializer}. + * + * @param serializer must not be {@literal null}. + * @return new instance of {@link RedisElementReader}. + */ + static RedisElementReader from(RedisSerializer serializer) { + + Assert.notNull(serializer, "Serializer must not be null!"); + return new DefaultRedisElementReader<>(serializer); + } } diff --git a/src/main/java/org/springframework/data/redis/serializer/RedisElementWriter.java b/src/main/java/org/springframework/data/redis/serializer/RedisElementWriter.java index 82a7f85c8..911738498 100644 --- a/src/main/java/org/springframework/data/redis/serializer/RedisElementWriter.java +++ b/src/main/java/org/springframework/data/redis/serializer/RedisElementWriter.java @@ -17,11 +17,15 @@ package org.springframework.data.redis.serializer; import java.nio.ByteBuffer; +import org.springframework.util.Assert; + /** * Strategy interface that specifies a serializer that can serialize an element to its binary representation to be used * as Redis protocol payload. * * @author Mark Paluch + * @author Christoph Strobl + * @since 2.0 */ @FunctionalInterface public interface RedisElementWriter { @@ -29,8 +33,20 @@ public interface RedisElementWriter { /** * Serialize a {@code element} to its {@link ByteBuffer} representation. * - * @param element + * @param element can be {@literal null}. * @return the {@link ByteBuffer} representing {@code element} in its binary form. */ ByteBuffer write(T element); + + /** + * Create new {@link RedisElementWriter} using given {@link RedisSerializer}. + * + * @param serializer must not be {@literal null}. + * @return new instance of {@link RedisElementWriter}. + */ + static RedisElementWriter from(RedisSerializer serializer) { + + Assert.notNull(serializer, "Serializer must not be null!"); + return new DefaultRedisElementWriter<>(serializer); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommandsTests.java index 9fc606718..03fe67741 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommandsTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveScriptingCommandsTests.java @@ -23,6 +23,7 @@ import io.lettuce.core.ScriptOutputType; import reactor.test.StepVerifier; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,6 +35,7 @@ import org.springframework.data.redis.connection.ReturnType; /** * @author Mark Paluch + * @author Christoph Strobl */ public class LettuceReactiveScriptingCommandsTests extends LettuceReactiveCommandsTestsBase { @@ -44,7 +46,7 @@ public class LettuceReactiveScriptingCommandsTests extends LettuceReactiveComman String sha1 = nativeCommands.scriptLoad("return KEYS[1]"); - StepVerifier.create(connection.scriptingCommands().scriptExists("foo", sha1)) // + StepVerifier.create(connection.scriptingCommands().scriptExists(Arrays.asList("foo", sha1))) // .expectNext(false) // .expectNext(true) // .verifyComplete(); diff --git a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java index f60d2b64b..1e64e5d71 100644 --- a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java @@ -44,6 +44,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.RedisElementReader; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.data.redis.serializer.RedisSerializer; @@ -167,7 +168,7 @@ public class ReactiveRedisTemplateIntegrationTests { @Test // DATAREDIS-683 @SuppressWarnings("unchecked") - public void execute() { + public void executeScript() { K key = keyFactory.instance(); V value = valueFactory.instance(); @@ -184,25 +185,26 @@ public class ReactiveRedisTemplateIntegrationTests { } @Test // DATAREDIS-683 - public void executeWithSerializationPairs() { + public void executeScriptWithElementReaderAndWriter() { K key = keyFactory.instance(); V value = valueFactory.instance(); - SerializationPair json = SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Person.class)); - SerializationPair string = SerializationPair.fromSerializer(new StringRedisSerializer()); + SerializationPair json = SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Person.class)); + RedisElementReader resultReader = RedisElementReader.from(new StringRedisSerializer()); assumeFalse(value instanceof Long); Person person = new Person("Walter", "White", 51); - StepVerifier.create( - redisTemplate.execute(new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1])", String.class), - json, string, Collections.singletonList(key), person)) + StepVerifier + .create( + redisTemplate.execute(new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1])", String.class), + Collections.singletonList(key), Collections.singletonList(person), json.getWriter(), resultReader)) .expectNext("OK").verifyComplete(); Flux execute = redisTemplate.execute( - new DefaultRedisScript<>("return redis.call('get', KEYS[1])", Person.class), json, json, - Collections.singletonList(key)); + new DefaultRedisScript<>("return redis.call('get', KEYS[1])", Person.class), Collections.singletonList(key), + Collections.emptyList(), json.getWriter(), json.getReader()); StepVerifier.create(execute).expectNext(person).verifyComplete(); } diff --git a/src/test/java/org/springframework/data/redis/core/script/DefaultScriptOperatorsUnitTests.java b/src/test/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutorUnitTests.java similarity index 80% rename from src/test/java/org/springframework/data/redis/core/script/DefaultScriptOperatorsUnitTests.java rename to src/test/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutorUnitTests.java index ca9604fc7..649b867e5 100644 --- a/src/test/java/org/springframework/data/redis/core/script/DefaultScriptOperatorsUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/script/DefaultReactiveScriptExecutorUnitTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.redis.Person; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.ReactiveRedisConnection; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; @@ -37,9 +38,10 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; /** * @author Mark Paluch + * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) -public class DefaultScriptOperatorsUnitTests { +public class DefaultReactiveScriptExecutorUnitTests { private final DefaultRedisScript SCRIPT = new DefaultRedisScript<>("return KEYS[0]", String.class); @@ -47,7 +49,7 @@ public class DefaultScriptOperatorsUnitTests { @Mock ReactiveRedisConnection connectionMock; @Mock ReactiveScriptingCommands scriptingCommandsMock; - DefaultScriptOperators executor; + DefaultReactiveScriptExecutor executor; @Before public void setUp() { @@ -55,7 +57,7 @@ public class DefaultScriptOperatorsUnitTests { when(connectionFactoryMock.getReactiveConnection()).thenReturn(connectionMock); when(connectionMock.scriptingCommands()).thenReturn(scriptingCommandsMock); - executor = new DefaultScriptOperators<>(connectionFactoryMock, RedisSerializationContext.string()); + executor = new DefaultReactiveScriptExecutor<>(connectionFactoryMock, RedisSerializationContext.string()); } @Test // DATAREDIS-683 @@ -64,7 +66,7 @@ public class DefaultScriptOperatorsUnitTests { when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt())) .thenReturn(Flux.just(ByteBuffer.wrap("FOO".getBytes()))); - StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectNext("FOO").verifyComplete(); + StepVerifier.create(executor.execute(SCRIPT)).expectNext("FOO").verifyComplete(); verify(scriptingCommandsMock).evalSha(anyString(), any(ReturnType.class), anyInt()); verify(scriptingCommandsMock, never()).eval(any(), any(ReturnType.class), anyInt()); @@ -79,7 +81,7 @@ public class DefaultScriptOperatorsUnitTests { when(scriptingCommandsMock.eval(any(), any(ReturnType.class), anyInt())) .thenReturn(Flux.just(ByteBuffer.wrap("FOO".getBytes()))); - StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectNext("FOO").verifyComplete(); + StepVerifier.create(executor.execute(SCRIPT)).expectNext("FOO").verifyComplete(); verify(scriptingCommandsMock).evalSha(anyString(), any(ReturnType.class), anyInt()); verify(scriptingCommandsMock).eval(any(), any(ReturnType.class), anyInt()); @@ -91,8 +93,7 @@ public class DefaultScriptOperatorsUnitTests { when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt())).thenReturn(Flux .error(new UnsupportedOperationException("NOSCRIPT No matching script. Please use EVAL.", new Exception()))); - StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())) - .expectError(UnsupportedOperationException.class).verify(); + StepVerifier.create(executor.execute(SCRIPT)).expectError(UnsupportedOperationException.class).verify(); } @Test // DATAREDIS-683 @@ -116,8 +117,19 @@ public class DefaultScriptOperatorsUnitTests { when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt())) .thenReturn(Flux.error(new RuntimeException())); - StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectError().verify(); + StepVerifier.create(executor.execute(SCRIPT)).expectError().verify(); verify(connectionMock).close(); } + + @Test // DATAREDIS-683 + public void doesNotConvertRawResult() { + + Person returnValue = new Person(); + + when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt())) + .thenReturn(Flux.just(returnValue)); + + StepVerifier.create(executor.execute(RedisScript.of("return KEYS[0]"))).expectNext(returnValue).verifyComplete(); + } } diff --git a/src/test/java/org/springframework/data/redis/core/script/DefaultRedisScriptTests.java b/src/test/java/org/springframework/data/redis/core/script/DefaultRedisScriptTests.java index 1293fc504..0bbe3480e 100644 --- a/src/test/java/org/springframework/data/redis/core/script/DefaultRedisScriptTests.java +++ b/src/test/java/org/springframework/data/redis/core/script/DefaultRedisScriptTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -26,11 +26,13 @@ import org.springframework.scripting.support.StaticScriptSource; * Test of {@link DefaultRedisScript} * * @author Jennifer Hickey + * @author Christoph Strobl */ public class DefaultRedisScriptTests { @Test public void testGetSha1() { + StaticScriptSource script = new StaticScriptSource("return KEYS[1]"); DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(script); @@ -45,6 +47,7 @@ public class DefaultRedisScriptTests { @Test public void testGetScriptAsString() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText("return ARGS[1]"); redisScript.setResultType(String.class); @@ -53,14 +56,16 @@ public class DefaultRedisScriptTests { @Test(expected = ScriptingException.class) public void testGetScriptAsStringError() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("nonexistent"))); redisScript.setResultType(Long.class); redisScript.getScriptAsString(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalStateException.class) public void initializeWithNoScript() throws Exception { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.afterPropertiesSet(); }