org.springframework.data.redis.connection.lettuce.LettuceInvoker Maven / Gradle / Ivy
/*
* Copyright 2021-2022 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
*
* https://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.connection.lettuce;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Utility for functional invocation of {@link RedisClusterAsyncCommands Lettuce methods}. Typically used to express the
* method call as method reference and passing method arguments through one of the {@code just} or {@code from} methods.
*
* {@code just} methods record the method call and evaluate the method result immediately. {@code from} methods allows
* composing a functional pipeline to transform the result using a {@link Converter}.
*
* Usage example:
*
*
* LettuceInvoker invoker = …;
*
* Long result = invoker.just(RedisGeoAsyncCommands::geoadd, key, point.getX(), point.getY(), member);
*
* List<byte[]> result = invoker.fromMany(RedisGeoAsyncCommands::geohash, key, members)
* .toList(it -> it.getValueOrElse(null));
*
*
* The actual translation from {@link RedisFuture} is delegated to {@link Synchronizer} which can either await
* completion or record the future along {@link Converter} for further processing.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.5
*/
class LettuceInvoker {
private final RedisClusterAsyncCommands connection;
private final Synchronizer synchronizer;
LettuceInvoker(RedisClusterAsyncCommands connection, Synchronizer synchronizer) {
this.connection = connection;
this.synchronizer = synchronizer;
}
/**
* Invoke the {@link ConnectionFunction0} and return its result.
*
* @param function must not be {@literal null}.
*/
@Nullable
R just(ConnectionFunction0 function) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection), Converters.identityConverter(), () -> null);
}
/**
* Invoke the {@link ConnectionFunction1} and return its result.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
*/
@Nullable
R just(ConnectionFunction1 function, T1 t1) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection, t1));
}
/**
* Invoke the {@link ConnectionFunction2} and return its result.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
*/
@Nullable
R just(ConnectionFunction2 function, T1 t1, T2 t2) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection, t1, t2));
}
/**
* Invoke the {@link ConnectionFunction3} and return its result.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
*/
@Nullable
R just(ConnectionFunction3 function, T1 t1, T2 t2, T3 t3) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection, t1, t2, t3));
}
/**
* Invoke the {@link ConnectionFunction4} and return its result.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
*/
@Nullable
R just(ConnectionFunction4 function, T1 t1, T2 t2, T3 t3, T4 t4) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection, t1, t2, t3, t4));
}
/**
* Invoke the {@link ConnectionFunction5} and return its result.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
* @param t5 fifth argument.
*/
@Nullable
R just(ConnectionFunction5 function, T1 t1, T2 t2, T3 t3, T4 t4,
T5 t5) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return synchronizer.invoke(() -> function.apply(connection, t1, t2, t3, t4, t5));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction0} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
*/
SingleInvocationSpec from(ConnectionFunction0 function) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return new DefaultSingleInvocationSpec<>(() -> function.apply(connection), synchronizer);
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction1} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
*/
SingleInvocationSpec from(ConnectionFunction1 function, T1 t1) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return from(it -> function.apply(it, t1));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction2} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
*/
SingleInvocationSpec from(ConnectionFunction2 function, T1 t1, T2 t2) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return from(it -> function.apply(it, t1, t2));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction3} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
*/
SingleInvocationSpec from(ConnectionFunction3 function, T1 t1, T2 t2, T3 t3) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return from(it -> function.apply(it, t1, t2, t3));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction4} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
*/
SingleInvocationSpec from(ConnectionFunction4 function, T1 t1, T2 t2, T3 t3,
T4 t4) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return from(it -> function.apply(it, t1, t2, t3, t4));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction5} and return a {@link SingleInvocationSpec} for
* further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
* @param t5 fifth argument.
*/
SingleInvocationSpec from(ConnectionFunction5 function, T1 t1,
T2 t2, T3 t3, T4 t4, T5 t5) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return from(it -> function.apply(it, t1, t2, t3, t4, t5));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction0} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
*/
, E> ManyInvocationSpec fromMany(ConnectionFunction0 function) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return new DefaultManyInvocationSpec<>(() -> function.apply(connection), synchronizer);
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction1} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
*/
, E, T1> ManyInvocationSpec fromMany(ConnectionFunction1 function, T1 t1) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return fromMany(it -> function.apply(it, t1));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction2} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
*/
, E, T1, T2> ManyInvocationSpec fromMany(ConnectionFunction2 function, T1 t1,
T2 t2) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return fromMany(it -> function.apply(it, t1, t2));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction3} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
*/
, E, T1, T2, T3> ManyInvocationSpec fromMany(ConnectionFunction3 function,
T1 t1, T2 t2, T3 t3) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return fromMany(it -> function.apply(it, t1, t2, t3));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction4} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
*/
, E, T1, T2, T3, T4> ManyInvocationSpec fromMany(
ConnectionFunction4 function, T1 t1, T2 t2, T3 t3, T4 t4) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return fromMany(it -> function.apply(it, t1, t2, t3, t4));
}
/**
* Compose a invocation pipeline from the {@link ConnectionFunction5} that returns a {@link Collection}-like result
* and return a {@link ManyInvocationSpec} for further composition.
*
* @param function must not be {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
* @param t5 fifth argument.
*/
, E, T1, T2, T3, T4, T5> ManyInvocationSpec fromMany(
ConnectionFunction5 function, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) {
Assert.notNull(function, "ConnectionFunction must not be null!");
return fromMany(it -> function.apply(it, t1, t2, t3, t4, t5));
}
/**
* Represents an element in the invocation pipleline allowing consuming the result by applying a {@link Converter}.
*
* @param
*/
interface SingleInvocationSpec {
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result after applying
* {@link Converter}.
*
* @param converter must not be {@literal null}.
* @param target type.
* @return the converted result, can be {@literal null}.
*/
@Nullable
T get(Converter converter);
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result after applying
* {@link Converter} or return the {@literal nullDefault} value if not present.
*
* @param converter must not be {@literal null}.
* @param nullDefault can be {@literal null}.
* @param target type.
* @return the converted result, can be {@literal null}.
*/
@Nullable
default T orElse(Converter converter, @Nullable T nullDefault) {
return getOrElse(converter, () -> nullDefault);
}
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result after applying
* {@link Converter} or return the {@literal nullDefault} value if not present.
*
* @param converter must not be {@literal null}.
* @param nullDefault must not be {@literal null}.
* @param target type.
* @return the converted result, can be {@literal null}.
*/
@Nullable
T getOrElse(Converter converter, Supplier nullDefault);
}
/**
* Represents an element in the invocation pipleline for methods returning {@link Collection}-like results allowing
* consuming the result by applying a {@link Converter}.
*
* @param
*/
interface ManyInvocationSpec {
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result.
*
* @return the result as {@link List}.
*/
default List toList() {
return toList(Converters.identityConverter());
}
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result after applying
* {@link Converter}.
*
* @param converter must not be {@literal null}.
* @param target type.
* @return the converted {@link List}.
*/
List toList(Converter converter);
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result.
*
* @return the result as {@link Set}.
*/
default Set toSet() {
return toSet(Converters.identityConverter());
}
/**
* Materialize the pipeline by invoking the {@code ConnectionFunction} and returning the result after applying
* {@link Converter}.
*
* @param converter must not be {@literal null}.
* @param target type.
* @return the converted {@link Set}.
*/
Set toSet(Converter converter);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 0 arguments.
*
* @param
*/
@FunctionalInterface
interface ConnectionFunction0 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
*/
RedisFuture apply(RedisClusterAsyncCommands connection);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 1 argument.
*
* @param
* @param
*/
@FunctionalInterface
interface ConnectionFunction1 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
* @param t1 first argument.
*/
RedisFuture apply(RedisClusterAsyncCommands connection, T1 t1);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 2 arguments.
*
* @param
* @param
* @param
*/
@FunctionalInterface
interface ConnectionFunction2 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
*/
RedisFuture apply(RedisClusterAsyncCommands connection, T1 t1, T2 t2);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 3 arguments.
*
* @param
* @param
* @param
* @param
*/
@FunctionalInterface
interface ConnectionFunction3 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
*/
RedisFuture apply(RedisClusterAsyncCommands connection, T1 t1, T2 t2, T3 t3);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 4 arguments.
*
* @param
* @param
* @param
* @param
* @param
*/
@FunctionalInterface
interface ConnectionFunction4 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
*/
RedisFuture apply(RedisClusterAsyncCommands connection, T1 t1, T2 t2, T3 t3, T4 t4);
}
/**
* A function accepting {@link RedisClusterAsyncCommands} with 5 arguments.
*
* @param
* @param
* @param
* @param
* @param
* @param
*/
@FunctionalInterface
interface ConnectionFunction5 {
/**
* Apply this function to the arguments and return a {@link RedisFuture}.
*
* @param connection the connection in use. Never {@literal null}.
* @param t1 first argument.
* @param t2 second argument.
* @param t3 third argument.
* @param t4 fourth argument.
* @param t5 fifth argument.
*/
RedisFuture apply(RedisClusterAsyncCommands connection, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
}
static class DefaultSingleInvocationSpec implements SingleInvocationSpec {
private final Supplier> parent;
private final Synchronizer synchronizer;
public DefaultSingleInvocationSpec(Supplier> parent, Synchronizer synchronizer) {
this.parent = parent;
this.synchronizer = synchronizer;
}
@Override
public T get(Converter converter) {
Assert.notNull(converter, "Converter must not be null");
return synchronizer.invoke(parent, converter, () -> null);
}
@Nullable
@Override
public T getOrElse(Converter converter, Supplier nullDefault) {
Assert.notNull(converter, "Converter must not be null!");
return synchronizer.invoke(parent, converter, nullDefault);
}
}
static class DefaultManyInvocationSpec implements ManyInvocationSpec {
private final Supplier>> parent;
private final Synchronizer synchronizer;
public DefaultManyInvocationSpec(Supplier>> parent, Synchronizer synchronizer) {
this.parent = (Supplier) parent;
this.synchronizer = synchronizer;
}
@Override
public List toList(Converter converter) {
Assert.notNull(converter, "Converter must not be null!");
return synchronizer.invoke(parent, source -> {
if (source.isEmpty()) {
return Collections.emptyList();
}
List result = new ArrayList<>(source.size());
for (S s : source) {
result.add(converter.convert(s));
}
return result;
}, Collections::emptyList);
}
@Override
public Set toSet(Converter converter) {
Assert.notNull(converter, "Converter must not be null!");
return synchronizer.invoke(parent, source -> {
if (source.isEmpty()) {
return Collections.emptySet();
}
Set result = new LinkedHashSet<>(source.size());
for (S s : source) {
result.add(converter.convert(s));
}
return result;
}, Collections::emptySet);
}
}
/**
* Interface to define a synchronization function to evaluate {@link RedisFuture}.
*/
@FunctionalInterface
interface Synchronizer {
@Nullable
@SuppressWarnings({ "unchecked", "rawtypes" })
default T invoke(Supplier> futureSupplier) {
return (T) doInvoke((Supplier) futureSupplier, Converters.identityConverter(), () -> null);
}
@Nullable
@SuppressWarnings({ "unchecked", "rawtypes" })
default T invoke(Supplier> futureSupplier, Converter converter,
Supplier nullDefault) {
return (T) doInvoke((Supplier) futureSupplier, (Converter