DATAREDIS-683 - Polishing.

Rename *Operators -> *Executor and use plain RedisElementReader/Writer instead of SerilaizationPairs. Therefore we’ve introduced new static factory methods creating the required Reader/Writer from a given RedisSerializer.
Additionally some minor Javadoc updates, tests and removal of redundant null checks.

Original Pull Request: #268
This commit is contained in:
Christoph Strobl
2017-08-28 12:27:36 +02:00
parent a3b50ba765
commit 38d9f340c3
18 changed files with 376 additions and 191 deletions

View File

@@ -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 <a href="https://redis.io/commands/#scripting">Scripting</a> 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 <a href="http://redis.io/commands/script-load">Redis Documentation: SCRIPT LOAD</a>
*/
Mono<String> 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<Boolean> 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 <a href="http://redis.io/commands/script-exists">Redis Documentation: SCRIPT EXISTS</a>
*/
Flux<Boolean> scriptExists(String... scriptShas);
Flux<Boolean> scriptExists(List<String> 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 <a href="http://redis.io/commands/eval">Redis Documentation: EVAL</a>
*/
<T> Flux<T> 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 <a href="http://redis.io/commands/evalsha">Redis Documentation: EVALSHA</a>
*/
<T> Flux<T> evalSha(String scriptSha, ReturnType returnType, int numKeys, ByteBuffer... keysAndArgs);

View File

@@ -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 <a href="https://lettuce.io/">Lettuce</a> 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<Boolean> scriptExists(String... scriptShas) {
public Flux<Boolean> scriptExists(List<String> 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()])));
}
/*

View File

@@ -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<K, V> {
// 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 <T> Flux<T> execute(RedisScript<T> 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 <T> Flux<T> execute(RedisScript<T> script, List<K> 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").
*/
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args);
<T> Flux<T> execute(RedisScript<T> script, List<K> 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").
*/
<T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
SerializationPair<T> resultSerializerPair, List<K> keys, Object... args);
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
RedisElementReader<T> resultReader);
// -------------------------------------------------------------------------
// Methods to obtain specific operations interface objects.

View File

@@ -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<K, V> implements ReactiveRedisOperations<K, V
private final ReactiveRedisConnectionFactory connectionFactory;
private final RedisSerializationContext<K, V> serializationContext;
private final boolean exposeConnection;
private final ScriptOperators<K> scriptOperators;
private final ReactiveScriptExecutor<K> reactiveScriptExecutor;
/**
* Creates new {@link ReactiveRedisTemplate} using given {@link ReactiveRedisConnectionFactory} and
@@ -89,7 +90,7 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
this.connectionFactory = connectionFactory;
this.serializationContext = serializationContext;
this.exposeConnection = exposeConnection;
this.scriptOperators = new DefaultScriptOperators<>(connectionFactory, serializationContext);
this.reactiveScriptExecutor = new DefaultReactiveScriptExecutor<>(connectionFactory, serializationContext);
}
/**
@@ -491,21 +492,23 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
// Methods dealing with Redis Lua scripts
// -------------------------------------------------------------------------
/* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, 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)
*/
@Override
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args) {
return scriptOperators.execute(script, keys, args);
public <T> Flux<T> execute(RedisScript<T> script, List<K> 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 <T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
SerializationPair<T> resultSerializerPair, List<K> keys, Object... args) {
return scriptOperators.execute(script, argsSerializerPair, resultSerializerPair, keys, args);
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
RedisElementReader<T> resultReader) {
return reactiveScriptExecutor.execute(script, keys, args, argsWriter, resultReader);
}
// -------------------------------------------------------------------------

View File

@@ -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 <K> The type of keys that may be passed during script execution
* @since 2.0
*/
public class DefaultScriptOperators<K> implements ScriptOperators<K> {
public class DefaultReactiveScriptExecutor<K> implements ReactiveScriptExecutor<K> {
private final ReactiveRedisConnectionFactory connectionFactory;
private final RedisSerializationContext<K, ?> 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<K, ?> serializationContext) {
Assert.notNull(connectionFactory, "ReactiveRedisConnectionFactory must not be null!");
@@ -59,49 +63,51 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
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 <T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args) {
public <T> Flux<T> execute(RedisScript<T> script, List<K> 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<T>) serializationContext.getValueSerializationPair(), keys, args);
return execute(script, keys, args, serializationContext.getKeySerializationPair().getWriter(),
(RedisElementReader<T>) 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 <T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
SerializationPair<T> resultSerializationPair, List<K> keys, Object... args) {
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
RedisElementReader<T> 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<Object>) 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 <T> Flux<T> eval(ReactiveRedisConnection connection, RedisScript<T> script, ReturnType returnType,
int numKeys, ByteBuffer[] keysAndArgs, SerializationPair<T> resultSerializer) {
int numKeys, ByteBuffer[] keysAndArgs, RedisElementReader<T> resultReader) {
Flux<T> result = connection.scriptingCommands().evalSha(script.getSha1(), returnType, numKeys, keysAndArgs);
@@ -115,41 +121,25 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
.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<Object> argsSerializer, List<K> keys, Object[] args) {
protected ByteBuffer[] keysAndArgs(RedisElementWriter argsWriter, List<K> 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 <T> Flux<T> deserializeResult(SerializationPair<T> pair, Flux<T> result) {
return result.map(it -> ScriptUtils.deserializeResult(pair.getReader(), it));
protected <T> Flux<T> deserializeResult(RedisElementReader<T> reader, Flux<T> result) {
return result.map(it -> ScriptUtils.deserializeResult(reader, it));
}
protected SerializationPair<K> keySerializer() {
@@ -174,6 +164,7 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
try {
return Flux.defer(() -> action.doInRedis(conn)).doFinally(signal -> conn.close());
} catch (RuntimeException e) {
conn.close();
throw e;
}

View File

@@ -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<T> implements RedisScript<T>, InitializingBean {
private ScriptSource scriptSource;
private String sha1;
private Class<T> 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<T> resultType) {
@@ -61,11 +70,20 @@ public class DefaultRedisScript<T> implements RedisScript<T>, 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<T> implements RedisScript<T>, InitializingBean {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.script.RedisScript#getResultType()
*/
public Class<T> 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<T> implements RedisScript<T>, InitializingBean {
}
/**
* @param script The script text
* @param scriptText The script text
*/
public void setScriptText(String scriptText) {
this.scriptSource = new StaticScriptSource(scriptText);

View File

@@ -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;

View File

@@ -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 <K> The type of keys that may be passed during script execution
* @since 2.0
*/
public interface ReactiveScriptExecutor<K> {
/**
* 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 <T> Flux<T> execute(RedisScript<T> 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 <T> Flux<T> execute(RedisScript<T> script, List<K> 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")
*/
<T> Flux<T> execute(RedisScript<T> script, List<K> 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")
*/
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
RedisElementReader<T> resultReader);
}

View File

@@ -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 <a href="http://redis.io/commands/eval">Redis scripting support</a> available as of
* version 2.6
*
* @author Jennifer Hickey
* @param <T> 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 <T> 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<T> {
/**
* @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<T> 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 <T> RedisScript<T> 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 <T> RedisScript of(String script, Class<T> resultType) {
Assert.notNull(script, "Script must not be null!");
Assert.notNull(resultType, "ResultType must not be null!");
return new DefaultRedisScript(script, resultType);
}
}

View File

@@ -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 <K> The type of keys that may be passed during script execution
* @since 2.0
*/
public interface ScriptOperators<K> {
/**
* 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")
*/
<T> Flux<T> execute(RedisScript<T> script, List<K> 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")
*/
<T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializer, SerializationPair<T> resultSerializer,
List<K> keys, Object... args);
}

View File

@@ -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> T deserializeResult(RedisSerializer<T> 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) {

View File

@@ -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<T> {
* @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 <T> RedisElementReader<T> from(RedisSerializer<T> serializer) {
Assert.notNull(serializer, "Serializer must not be null!");
return new DefaultRedisElementReader<>(serializer);
}
}

View File

@@ -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<T> {
@@ -29,8 +33,20 @@ public interface RedisElementWriter<T> {
/**
* 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 <T> RedisElementWriter<T> from(RedisSerializer<T> serializer) {
Assert.notNull(serializer, "Serializer must not be null!");
return new DefaultRedisElementWriter<>(serializer);
}
}