Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2017-2018 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;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.DataType;
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.connection.ReactiveSubscription.Message;
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.listener.ReactiveRedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.serializer.RedisElementReader;
import org.springframework.data.redis.serializer.RedisElementWriter;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Central abstraction for reactive Redis data access implementing {@link ReactiveRedisOperations}.
*
* Performs automatic serialization/deserialization between the given objects and the underlying binary data in the
* Redis store.
*
* Note that while the template is generified, it is up to the serializers/deserializers to properly convert the given
* Objects to and from binary data.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
* @param the Redis key type against which the template works (usually a String)
* @param the Redis value type against which the template works
*/
public class ReactiveRedisTemplate implements ReactiveRedisOperations {
private final ReactiveRedisConnectionFactory connectionFactory;
private final RedisSerializationContext serializationContext;
private final boolean exposeConnection;
private final ReactiveScriptExecutor reactiveScriptExecutor;
/**
* Creates new {@link ReactiveRedisTemplate} using given {@link ReactiveRedisConnectionFactory} and
* {@link RedisSerializationContext}.
*
* @param connectionFactory must not be {@literal null}.
* @param serializationContext must not be {@literal null}.
*/
public ReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory,
RedisSerializationContext serializationContext) {
this(connectionFactory, serializationContext, false);
}
/**
* Creates new {@link ReactiveRedisTemplate} using given {@link ReactiveRedisConnectionFactory} and
* {@link RedisSerializationContext}.
*
* @param connectionFactory must not be {@literal null}.
* @param serializationContext must not be {@literal null}.
* @param exposeConnection flag indicating to expose the connection used.
*/
public ReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory,
RedisSerializationContext serializationContext, boolean exposeConnection) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
Assert.notNull(serializationContext, "SerializationContext must not be null!");
this.connectionFactory = connectionFactory;
this.serializationContext = serializationContext;
this.exposeConnection = exposeConnection;
this.reactiveScriptExecutor = new DefaultReactiveScriptExecutor<>(connectionFactory, serializationContext);
}
/**
* Returns the connectionFactory.
*
* @return Returns the connectionFactory
*/
public ReactiveRedisConnectionFactory getConnectionFactory() {
return connectionFactory;
}
// -------------------------------------------------------------------------
// Execution methods
// -------------------------------------------------------------------------
@Override
public Flux execute(ReactiveRedisCallback action) {
return execute(action, exposeConnection);
}
/**
* Executes the given action object within a connection that can be exposed or not. Additionally, the connection can
* be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios).
*
* @param return type
* @param action callback object to execute
* @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code
* @return object returned by the action
*/
public Flux execute(ReactiveRedisCallback action, boolean exposeConnection) {
Assert.notNull(action, "Callback object must not be null");
ReactiveRedisConnectionFactory factory = getConnectionFactory();
ReactiveRedisConnection conn = factory.getReactiveConnection();
try {
ReactiveRedisConnection connToUse = preProcessConnection(conn, false);
ReactiveRedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
Publisher result = action.doInRedis(connToExpose);
return Flux.from(postProcessResult(result, connToUse, false)).doFinally(signalType -> conn.close());
} catch (RuntimeException e) {
conn.close();
throw e;
}
}
/**
* Create a reusable Flux for a {@link ReactiveRedisCallback}. Callback is executed within a connection context. The
* connection is released outside the callback.
*
* @param callback must not be {@literal null}
* @return a {@link Flux} wrapping the {@link ReactiveRedisCallback}.
*/
public Flux createFlux(ReactiveRedisCallback callback) {
Assert.notNull(callback, "ReactiveRedisCallback must not be null!");
return Flux.defer(() -> doInConnection(callback, exposeConnection));
}
/**
* Create a reusable Mono for a {@link ReactiveRedisCallback}. Callback is executed within a connection context. The
* connection is released outside the callback.
*
* @param callback must not be {@literal null}
* @return a {@link Mono} wrapping the {@link ReactiveRedisCallback}.
*/
public Mono createMono(final ReactiveRedisCallback callback) {
Assert.notNull(callback, "ReactiveRedisCallback must not be null!");
return Mono.defer(() -> Mono.from(doInConnection(callback, exposeConnection)));
}
/**
* Executes the given action object within a connection that can be exposed or not. Additionally, the connection can
* be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios).
*
* @param return type
* @param action callback object to execute
* @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code
* @return object returned by the action
*/
private Publisher doInConnection(ReactiveRedisCallback action, boolean exposeConnection) {
Assert.notNull(action, "Callback object must not be null");
ReactiveRedisConnectionFactory factory = getConnectionFactory();
ReactiveRedisConnection conn = factory.getReactiveConnection();
ReactiveRedisConnection connToUse = preProcessConnection(conn, false);
ReactiveRedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
Publisher result = action.doInRedis(connToExpose);
return Flux.from(postProcessResult(result, connToUse, false)).doFinally(signal -> conn.close());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#convertAndSend(java.lang.String, java.lang.Object)
*/
@Override
public Mono convertAndSend(String destination, V message) {
Assert.hasText(destination, "Destination channel must not be empty!");
Assert.notNull(message, "Message must not be null!");
return createMono(connection -> connection.pubSubCommands().publish(
getSerializationContext().getStringSerializationPair().write(destination),
getSerializationContext().getValueSerializationPair().write(message)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#listenTo(org.springframework.data.redis.listener.Topic[])
*/
@Override
public Flux> listenTo(Topic... topics) {
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(getConnectionFactory());
return container
.receive(Arrays.asList(topics), getSerializationContext().getStringSerializationPair(),
getSerializationContext().getValueSerializationPair()) //
.doFinally((signalType) -> container.destroyLater().subscribeOn(Schedulers.elastic()));
}
// -------------------------------------------------------------------------
// Methods dealing with Redis keys
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#hasKey(java.lang.Object)
*/
@Override
public Mono hasKey(K key) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.keyCommands().exists(rawKey(key)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#type(java.lang.Object)
*/
@Override
public Mono type(K key) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.keyCommands().type(rawKey(key)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#keys(java.lang.Object)
*/
@Override
public Flux keys(K pattern) {
Assert.notNull(pattern, "Pattern must not be null!");
return createFlux(connection -> connection.keyCommands().keys(rawKey(pattern))) //
.flatMap(Flux::fromIterable) //
.map(this::readKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#scan(org.springframework.data.redis.core.ScanOptions)
*/
@Override
public Flux scan(ScanOptions options) {
Assert.notNull(options, "ScanOptions must not be null!");
return createFlux(connection -> connection.keyCommands().scan(options)) //
.map(this::readKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#randomKey()
*/
@Override
public Mono randomKey() {
return createMono(connection -> connection.keyCommands().randomKey()).map(this::readKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#rename(java.lang.Object, java.lang.Object)
*/
@Override
public Mono rename(K oldKey, K newKey) {
Assert.notNull(oldKey, "Old key must not be null!");
Assert.notNull(newKey, "New Key must not be null!");
return createMono(connection -> connection.keyCommands().rename(rawKey(oldKey), rawKey(newKey)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#renameIfAbsent(java.lang.Object, java.lang.Object)
*/
@Override
public Mono renameIfAbsent(K oldKey, K newKey) {
Assert.notNull(oldKey, "Old key must not be null!");
Assert.notNull(newKey, "New Key must not be null!");
return createMono(connection -> connection.keyCommands().renameNX(rawKey(oldKey), rawKey(newKey)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#delete(java.lang.Object[])
*/
@Override
@SafeVarargs
public final Mono delete(K... keys) {
Assert.notNull(keys, "Keys must not be null!");
Assert.notEmpty(keys, "Keys must not be empty!");
Assert.noNullElements(keys, "Keys must not contain null elements!");
if (keys.length == 1) {
return createMono(connection -> connection.keyCommands().del(rawKey(keys[0])));
}
Mono> listOfKeys = Flux.fromArray(keys).map(this::rawKey).collectList();
return createMono(connection -> listOfKeys.flatMap(rawKeys -> connection.keyCommands().mDel(rawKeys)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#delete(org.reactivestreams.Publisher)
*/
@Override
public Mono delete(Publisher keys) {
Assert.notNull(keys, "Keys must not be null!");
return createMono(connection -> connection.keyCommands() //
.del(Flux.from(keys).map(this::rawKey).map(KeyCommand::new)) //
.map(CommandResponse::getOutput));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#unlink(java.lang.Object[])
*/
@Override
@SafeVarargs
public final Mono unlink(K... keys) {
Assert.notNull(keys, "Keys must not be null!");
Assert.notEmpty(keys, "Keys must not be empty!");
Assert.noNullElements(keys, "Keys must not contain null elements!");
if (keys.length == 1) {
return createMono(connection -> connection.keyCommands().unlink(rawKey(keys[0])));
}
Mono> listOfKeys = Flux.fromArray(keys).map(this::rawKey).collectList();
return createMono(connection -> listOfKeys.flatMap(rawKeys -> connection.keyCommands().mUnlink(rawKeys)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#unlink(org.reactivestreams.Publisher)
*/
@Override
public Mono unlink(Publisher keys) {
Assert.notNull(keys, "Keys must not be null!");
return createMono(connection -> connection.keyCommands() //
.unlink(Flux.from(keys).map(this::rawKey).map(KeyCommand::new)) //
.map(CommandResponse::getOutput));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#expire(java.lang.Object, java.time.Duration)
*/
@Override
public Mono expire(K key, Duration timeout) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(timeout, "Timeout must not be null!");
if (timeout.getNano() == 0) {
return createMono(connection -> connection.keyCommands() //
.expire(rawKey(key), timeout));
}
return createMono(connection -> connection.keyCommands().pExpire(rawKey(key), timeout));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#expireAt(java.lang.Object, java.time.Instant)
*/
@Override
public Mono expireAt(K key, Instant expireAt) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(expireAt, "Expire at must not be null!");
if (expireAt.getNano() == 0) {
return createMono(connection -> connection.keyCommands() //
.expireAt(rawKey(key), expireAt));
}
return createMono(connection -> connection.keyCommands().pExpireAt(rawKey(key), expireAt));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#persist(java.lang.Object)
*/
@Override
public Mono persist(K key) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.keyCommands().persist(rawKey(key)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#getExpire(java.lang.Object)
*/
@Override
public Mono getExpire(K key) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.keyCommands().pTtl(rawKey(key)).flatMap(expiry -> {
if (expiry == -1) {
return Mono.just(Duration.ZERO);
}
if (expiry == -2) {
return Mono.empty();
}
return Mono.just(Duration.ofMillis(expiry));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#move(java.lang.Object, int)
*/
@Override
public Mono move(K key, int dbIndex) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.keyCommands().move(rawKey(key), dbIndex));
}
// -------------------------------------------------------------------------
// 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.util.List)
*/
@Override
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, java.util.List, java.util.List, org.springframework.data.redis.serializer.RedisElementWriter, org.springframework.data.redis.serializer.RedisElementReader)
*/
@Override
public Flux execute(RedisScript script, List keys, List args, RedisElementWriter argsWriter,
RedisElementReader resultReader) {
return reactiveScriptExecutor.execute(script, keys, args, argsWriter, resultReader);
}
// -------------------------------------------------------------------------
// Implementation hooks and helper methods
// -------------------------------------------------------------------------
/**
* Processes the connection (before any settings are executed on it). Default implementation returns the connection as
* is.
*
* @param connection must not be {@literal null}.
* @param existingConnection
*/
protected ReactiveRedisConnection preProcessConnection(ReactiveRedisConnection connection,
boolean existingConnection) {
return connection;
}
/**
* Processes the result before returning the {@link Publisher}. Default implementation returns the result as is.
*
* @param result must not be {@literal null}.
* @param connection must not be {@literal null}.
* @param existingConnection
* @return
*/
protected Publisher postProcessResult(Publisher result, ReactiveRedisConnection connection,
boolean existingConnection) {
return result;
}
protected ReactiveRedisConnection createRedisConnectionProxy(ReactiveRedisConnection reactiveRedisConnection) {
Class[] ifcs = ClassUtils.getAllInterfacesForClass(reactiveRedisConnection.getClass(),
getClass().getClassLoader());
return (ReactiveRedisConnection) Proxy.newProxyInstance(reactiveRedisConnection.getClass().getClassLoader(), ifcs,
new CloseSuppressingInvocationHandler(reactiveRedisConnection));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForGeo()
*/
@Override
public ReactiveGeoOperations opsForGeo() {
return opsForGeo(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForGeo(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveGeoOperations opsForGeo(RedisSerializationContext serializationContext) {
return new DefaultReactiveGeoOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForHash()
*/
@Override
public ReactiveHashOperations opsForHash() {
return opsForHash(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForHash(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveHashOperations opsForHash(
RedisSerializationContext serializationContext) {
return new DefaultReactiveHashOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForHyperLogLog()
*/
@Override
public ReactiveHyperLogLogOperations opsForHyperLogLog() {
return opsForHyperLogLog(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForHyperLogLog(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveHyperLogLogOperations opsForHyperLogLog(
RedisSerializationContext serializationContext) {
return new DefaultReactiveHyperLogLogOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForList()
*/
@Override
public ReactiveListOperations opsForList() {
return opsForList(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForList(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveListOperations opsForList(RedisSerializationContext serializationContext) {
return new DefaultReactiveListOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForSet()
*/
@Override
public ReactiveSetOperations opsForSet() {
return opsForSet(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForSet(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveSetOperations opsForSet(RedisSerializationContext serializationContext) {
return new DefaultReactiveSetOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForValue()
*/
@Override
public ReactiveValueOperations opsForValue() {
return opsForValue(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForValue(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveValueOperations opsForValue(RedisSerializationContext serializationContext) {
return new DefaultReactiveValueOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForZSet()
*/
@Override
public ReactiveZSetOperations opsForZSet() {
return opsForZSet(serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#opsForZSet(org.springframework.data.redis.serializer.RedisSerializationContext)
*/
@Override
public ReactiveZSetOperations opsForZSet(RedisSerializationContext serializationContext) {
return new DefaultReactiveZSetOperations<>(this, serializationContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveRedisOperations#serialization()
*/
@Override
public RedisSerializationContext getSerializationContext() {
return serializationContext;
}
private ByteBuffer rawKey(K key) {
return getSerializationContext().getKeySerializationPair().getWriter().write(key);
}
private K readKey(ByteBuffer buffer) {
return getSerializationContext().getKeySerializationPair().getReader().read(buffer);
}
}