diff --git a/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java new file mode 100644 index 000000000..95352acb9 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java @@ -0,0 +1,251 @@ +/* + * Copyright 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.core; + +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor; +import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.stream.ReadOffset; +import org.springframework.data.redis.connection.stream.StreamOffset; +import org.springframework.lang.Nullable; +import org.springframework.util.ReflectionUtils; + +/** + * Utility to create implementation objects for {@code Bound…Operations} so that bound key interfaces can be implemented + * automatically by translating interface calls to actual {@code …Operations} interfaces. + * + * @author Mark Paluch + * @since 3.0 + */ +class BoundOperationsProxyFactory { + + private final Map targetMethodCache = new ConcurrentHashMap<>(); + + /** + * Create a proxy object that implements {@link Class boundOperationsInterface} using the given {@code key} and + * {@link DataType}. Calls to {@code Bound…Operations} methods are bridged by forwarding these either to the + * {@code operationsTarget} or a default implementation. + * + * @param boundOperationsInterface the {@code Bound…Operations} interface. + * @param key the bound key. + * @param type the {@link DataType} for which to create a proxy object. + * @param operations the {@link RedisOperations} instance. + * @param operationsTargetFunction function to extract the actual delegate for method calls. + * @param + * @return the proxy object. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public T createProxy(Class boundOperationsInterface, Object key, DataType type, + RedisOperations operations, Function, Object> operationsTargetFunction) { + + DefaultBoundKeyOperations delegate = new DefaultBoundKeyOperations(type, key, (RedisOperations) operations); + Object operationsTarget = operationsTargetFunction.apply(operations); + + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addInterface(boundOperationsInterface); + proxyFactory.addAdvice(new DefaultMethodInvokingMethodInterceptor()); + proxyFactory.addAdvice( + new BoundOperationsMethodInterceptor(key, operations, boundOperationsInterface, operationsTarget, delegate)); + + return (T) proxyFactory.getProxy(); + } + + Method lookupRequiredMethod(Method method, Class targetClass, boolean considerKeyArgument) { + + Method target = lookupMethod(method, targetClass, considerKeyArgument); + + if (target == null) { + throw new IllegalArgumentException("Cannot lookup target method for %s in class %s. This appears to be a bug." + .formatted(method, targetClass.getName())); + } + + return target; + } + + @Nullable + Method lookupMethod(Method method, Class targetClass, boolean considerKeyArgument) { + + return targetMethodCache.computeIfAbsent(method, it -> { + + Class[] paramTypes; + + if (isStreamRead(method)) { + paramTypes = new Class[it.getParameterCount()]; + System.arraycopy(it.getParameterTypes(), 0, paramTypes, 0, paramTypes.length - 1); + paramTypes[paramTypes.length - 1] = StreamOffset[].class; + } else if (considerKeyArgument) { + + paramTypes = new Class[it.getParameterCount() + 1]; + paramTypes[0] = Object.class; + System.arraycopy(it.getParameterTypes(), 0, paramTypes, 1, paramTypes.length - 1); + } else { + paramTypes = it.getParameterTypes(); + } + + return ReflectionUtils.findMethod(targetClass, method.getName(), paramTypes); + }); + } + + private boolean isStreamRead(Method method) { + return method.getName().equals("read") + && method.getParameterTypes()[method.getParameterCount() - 1].equals(ReadOffset.class); + } + + /** + * {@link MethodInterceptor} to delegate proxy calls to either {@link RedisOperations}, {@code key}, + * {@link DefaultBoundKeyOperations} or the {@code operationsTarget} such as {@link ValueOperations}. + */ + class BoundOperationsMethodInterceptor implements MethodInterceptor { + + private final Class boundOperationsInterface; + private final Object operationsTarget; + private final DefaultBoundKeyOperations delegate; + + public BoundOperationsMethodInterceptor(Object key, RedisOperations operations, + Class boundOperationsInterface, Object operationsTarget, DefaultBoundKeyOperations delegate) { + + this.boundOperationsInterface = boundOperationsInterface; + this.operationsTarget = operationsTarget; + this.delegate = delegate; + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + + switch (method.getName()) { + case "getKey": + return delegate.getKey(); + + case "rename": + delegate.rename(invocation.getArguments()[0]); + return null; + + case "getOperations": + return delegate.getOps(); + } + + if (method.getDeclaringClass() == boundOperationsInterface) { + return doInvoke(invocation, method, operationsTarget, true); + } + + return doInvoke(invocation, method, delegate, false); + } + + @Nullable + private Object doInvoke(MethodInvocation invocation, Method method, Object target, boolean considerKeyArgument) { + + Method backingMethod = lookupRequiredMethod(method, target.getClass(), considerKeyArgument); + + Object[] args; + Object[] invocationArguments = invocation.getArguments(); + + if (isStreamRead(method)) { + // stream.read requires translation to StreamOffset using the bound key. + args = new Object[backingMethod.getParameterCount()]; + System.arraycopy(invocationArguments, 0, args, 0, args.length - 1); + args[args.length - 1] = new StreamOffset[] { + StreamOffset.create(delegate.getKey(), (ReadOffset) invocationArguments[invocationArguments.length - 1]) }; + } else if (backingMethod.getParameterCount() > 0 && backingMethod.getParameterTypes()[0].equals(Object.class)) { + + args = new Object[backingMethod.getParameterCount()]; + args[0] = delegate.getKey(); + System.arraycopy(invocationArguments, 0, args, 1, args.length - 1); + } else { + args = invocationArguments; + } + + try { + return backingMethod.invoke(target, args); + } catch (ReflectiveOperationException e) { + ReflectionUtils.handleReflectionException(e); + throw new UnsupportedOperationException("Should not happen", e); + } + } + } + + /** + * Default {@link BoundKeyOperations} implementation. Meant for internal usage. + * + * @author Costin Leau + * @author Christoph Strobl + */ + static class DefaultBoundKeyOperations implements BoundKeyOperations { + + private final DataType type; + private Object key; + private final RedisOperations ops; + + DefaultBoundKeyOperations(DataType type, Object key, RedisOperations operations) { + this.type = type; + + this.key = key; + this.ops = operations; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return ops.expire(key, timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return ops.expireAt(key, date); + } + + @Override + public Long getExpire() { + return ops.getExpire(key); + } + + @Override + public Boolean persist() { + return ops.persist(key); + } + + @Override + public void rename(Object newKey) { + if (ops.hasKey(key)) { + ops.rename(key, newKey); + } + key = newKey; + } + + public DataType getType() { + return type; + } + + public RedisOperations getOps() { + return ops; + } + } +} diff --git a/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java b/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java index 9bbcad038..45125bbc8 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java @@ -182,9 +182,23 @@ public interface BoundSetOperations extends BoundKeyOperations { * @param key must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SDIFF + * @deprecated since 3.0, use {@link #difference(Object)} instead to follow a consistent method naming scheme. */ @Nullable - Set diff(K key); + default Set diff(K key) { + return difference(key); + } + + /** + * Diff all sets for given the bound key and {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 3.0 + * @see Redis Documentation: SDIFF + */ + @Nullable + Set difference(K key); /** * Diff all sets for given the bound key and {@code keys}. @@ -192,9 +206,23 @@ public interface BoundSetOperations extends BoundKeyOperations { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SDIFF + * @deprecated since 3.0, use {@link #difference(Collection)} instead to follow a consistent method naming scheme. */ @Nullable - Set diff(Collection keys); + default Set diff(Collection keys) { + return difference(keys); + } + + /** + * Diff all sets for given the bound key and {@code keys}. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 3.0 + * @see Redis Documentation: SDIFF + */ + @Nullable + Set difference(Collection keys); /** * Diff all sets for given the bound key and {@code keys} and store result in {@code destKey}. @@ -202,8 +230,23 @@ public interface BoundSetOperations extends BoundKeyOperations { * @param keys must not be {@literal null}. * @param destKey must not be {@literal null}. * @see Redis Documentation: SDIFFSTORE + * @deprecated since 3.0, use {@link #differenceAndStore(Object, Object)} instead to follow a consistent method naming + * scheme.. */ - void diffAndStore(K keys, K destKey); + @Deprecated + default void diffAndStore(K keys, K destKey) { + differenceAndStore(keys, destKey); + } + + /** + * Diff all sets for given the bound key and {@code keys} and store result in {@code destKey}. + * + * @param keys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @since 3.0 + * @see Redis Documentation: SDIFFSTORE + */ + void differenceAndStore(K keys, K destKey); /** * Diff all sets for given the bound key and {@code keys} and store result in {@code destKey}. @@ -211,8 +254,23 @@ public interface BoundSetOperations extends BoundKeyOperations { * @param keys must not be {@literal null}. * @param destKey must not be {@literal null}. * @see Redis Documentation: SDIFFSTORE + * @deprecated since 3.0, use {@link #differenceAndStore(Collection, Object)} instead to follow a consistent method + * naming scheme. */ - void diffAndStore(Collection keys, K destKey); + @Deprecated + default void diffAndStore(Collection keys, K destKey) { + differenceAndStore(keys, destKey); + } + + /** + * Diff all sets for given the bound key and {@code keys} and store result in {@code destKey}. + * + * @param keys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @since 3.0 + * @see Redis Documentation: SDIFFSTORE + */ + void differenceAndStore(Collection keys, K destKey); /** * Get all elements of set at the bound key. diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundGeoOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundGeoOperations.java deleted file mode 100644 index d78ecd845..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundGeoOperations.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2016-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.core; - -import java.util.List; -import java.util.Map; - -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.GeoResults; -import org.springframework.data.geo.Metric; -import org.springframework.data.geo.Point; -import org.springframework.data.redis.connection.DataType; -import org.springframework.data.redis.connection.RedisGeoCommands; -import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation; -import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs; -import org.springframework.data.redis.domain.geo.GeoReference; -import org.springframework.data.redis.domain.geo.GeoShape; - -/** - * Default implementation of {@link BoundGeoOperations}. - * - * @author Ninad Divadkar - * @author Christoph Strobl - * @author Mark Paluch - * @since 1.8 - */ -class DefaultBoundGeoOperations extends DefaultBoundKeyOperations implements BoundGeoOperations { - - private final GeoOperations ops; - - /** - * Constructs a new {@code DefaultBoundGeoOperations}. - * - * @param key must not be {@literal null}. - * @param operations must not be {@literal null}. - */ - DefaultBoundGeoOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForGeo(); - } - - @Override - public Long add(Point point, M member) { - return ops.add(getKey(), point, member); - } - - @Override - public Long add(GeoLocation location) { - return ops.add(getKey(), location); - } - - @Override - public Long add(Map memberCoordinateMap) { - return ops.add(getKey(), memberCoordinateMap); - } - - @Override - public Long add(Iterable> locations) { - return ops.add(getKey(), locations); - } - - @Override - public Distance distance(M member1, M member2) { - return ops.distance(getKey(), member1, member2); - } - - @Override - public Distance distance(M member1, M member2, Metric unit) { - return ops.distance(getKey(), member1, member2, unit); - } - - @Override - public List hash(M... members) { - return ops.hash(getKey(), members); - } - - @Override - public List position(M... members) { - return ops.position(getKey(), members); - } - - @Override - public GeoResults> radius(Circle within) { - return ops.radius(getKey(), within); - } - - @Override - public GeoResults> radius(Circle within, GeoRadiusCommandArgs param) { - return ops.radius(getKey(), within, param); - } - - @Override - public GeoResults> radius(M member, double radius) { - return ops.radius(getKey(), member, radius); - } - - @Override - public GeoResults> radius(M member, Distance distance) { - return ops.radius(getKey(), member, distance); - } - - @Override - public GeoResults> radius(M member, Distance distance, GeoRadiusCommandArgs param) { - return ops.radius(getKey(), member, distance, param); - } - - @Override - public Long remove(M... members) { - return ops.remove(getKey(), members); - } - - @Override - public GeoResults> search(GeoReference reference, - GeoShape geoPredicate, RedisGeoCommands.GeoSearchCommandArgs args) { - return ops.search(getKey(), reference, geoPredicate, args); - } - - @Override - public Long searchAndStore(K destKey, GeoReference reference, - GeoShape geoPredicate, RedisGeoCommands.GeoSearchStoreCommandArgs args) { - return ops.searchAndStore(getKey(), destKey, reference, geoPredicate, args); - } - - @Override - public DataType getType() { - return DataType.ZSET; - } - -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java deleted file mode 100644 index 3c0a7d4b9..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.springframework.data.redis.connection.DataType; -import org.springframework.lang.Nullable; - -/** - * Default implementation for {@link HashOperations}. - * - * @author Costin Leau - * @author Christoph Strobl - * @author Ninad Divadkar - */ -class DefaultBoundHashOperations extends DefaultBoundKeyOperations - implements BoundHashOperations { - - private final HashOperations ops; - - /** - * Constructs a new DefaultBoundHashOperations instance. - * - * @param key - * @param operations - */ - DefaultBoundHashOperations(H key, RedisOperations operations) { - super(key, operations); - this.ops = operations.opsForHash(); - } - - @Override - public Long delete(Object... keys) { - return ops.delete(getKey(), keys); - } - - @Override - public HV get(Object key) { - return ops.get(getKey(), key); - } - - @Override - public List multiGet(Collection hashKeys) { - return ops.multiGet(getKey(), hashKeys); - } - - @Override - public RedisOperations getOperations() { - return ops.getOperations(); - } - - @Override - public Boolean hasKey(Object key) { - return ops.hasKey(getKey(), key); - } - - @Override - public Long increment(HK key, long delta) { - return ops.increment(getKey(), key, delta); - } - - @Override - public Double increment(HK key, double delta) { - return ops.increment(getKey(), key, delta); - } - - @Nullable - @Override - public HK randomKey() { - return ops.randomKey(getKey()); - } - - @Nullable - @Override - public Entry randomEntry() { - return ops.randomEntry(getKey()); - } - - @Nullable - @Override - public List randomKeys(long count) { - return ops.randomKeys(getKey(), count); - } - - @Nullable - @Override - public Map randomEntries(long count) { - return ops.randomEntries(getKey(), count); - } - - @Override - public Set keys() { - return ops.keys(getKey()); - } - - @Nullable - @Override - public Long lengthOfValue(HK hashKey) { - return ops.lengthOfValue(getKey(), hashKey); - } - - @Override - public Long size() { - return ops.size(getKey()); - } - - @Override - public void putAll(Map m) { - ops.putAll(getKey(), m); - } - - @Override - public void put(HK key, HV value) { - ops.put(getKey(), key, value); - } - - @Override - public Boolean putIfAbsent(HK key, HV value) { - return ops.putIfAbsent(getKey(), key, value); - } - - @Override - public List values() { - return ops.values(getKey()); - } - - @Override - public Map entries() { - return ops.entries(getKey()); - } - - @Override - public DataType getType() { - return DataType.HASH; - } - - @Override - public Cursor> scan(ScanOptions options) { - return ops.scan(getKey(), options); - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyOperations.java deleted file mode 100644 index 834cc5a84..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyOperations.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -/** - * Default {@link BoundKeyOperations} implementation. Meant for internal usage. - * - * @author Costin Leau - * @author Christoph Strobl - */ -abstract class DefaultBoundKeyOperations implements BoundKeyOperations { - - private K key; - private final RedisOperations ops; - - DefaultBoundKeyOperations(K key, RedisOperations operations) { - - this.key = key; - this.ops = operations; - } - - @Override - public K getKey() { - return key; - } - - @Override - public Boolean expire(long timeout, TimeUnit unit) { - return ops.expire(key, timeout, unit); - } - - @Override - public Boolean expireAt(Date date) { - return ops.expireAt(key, date); - } - - @Override - public Long getExpire() { - return ops.getExpire(key); - } - - @Override - public Boolean persist() { - return ops.persist(key); - } - - @Override - public void rename(K newKey) { - if (ops.hasKey(key)) { - ops.rename(key, newKey); - } - key = newKey; - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundListOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundListOperations.java deleted file mode 100644 index 9d3fe76a9..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundListOperations.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.springframework.data.redis.connection.DataType; -import org.springframework.data.redis.connection.RedisListCommands.Direction; - -/** - * Default implementation for {@link BoundListOperations}. - * - * @author Costin Leau - * @author Mark Paluch - */ -class DefaultBoundListOperations extends DefaultBoundKeyOperations implements BoundListOperations { - - private final ListOperations ops; - - /** - * Constructs a new DefaultBoundListOperations instance. - * - * @param key - * @param operations - */ - DefaultBoundListOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForList(); - } - - @Override - public RedisOperations getOperations() { - return ops.getOperations(); - } - - @Override - public V index(long index) { - return ops.index(getKey(), index); - } - - @Override - public Long indexOf(V value) { - return ops.indexOf(getKey(), value); - } - - @Override - public Long lastIndexOf(V value) { - return ops.lastIndexOf(getKey(), value); - } - - @Override - public V leftPop() { - return ops.leftPop(getKey()); - } - - @Override - public List leftPop(long count) { - return ops.leftPop(getKey(), count); - } - - @Override - public V leftPop(long timeout, TimeUnit unit) { - return ops.leftPop(getKey(), timeout, unit); - } - - @Override - public Long leftPush(V value) { - return ops.leftPush(getKey(), value); - } - - @Override - public Long leftPushAll(V... values) { - return ops.leftPushAll(getKey(), values); - } - - @Override - public Long leftPushIfPresent(V value) { - return ops.leftPushIfPresent(getKey(), value); - } - - @Override - public Long leftPush(V pivot, V value) { - return ops.leftPush(getKey(), pivot, value); - } - - @Override - public Long size() { - return ops.size(getKey()); - } - - @Override - public List range(long start, long end) { - return ops.range(getKey(), start, end); - } - - @Override - public Long remove(long i, Object value) { - return ops.remove(getKey(), i, value); - } - - @Override - public V rightPop() { - return ops.rightPop(getKey()); - } - - @Override - public List rightPop(long count) { - return ops.rightPop(getKey(), count); - } - - @Override - public V rightPop(long timeout, TimeUnit unit) { - return ops.rightPop(getKey(), timeout, unit); - } - - @Override - public Long rightPushIfPresent(V value) { - return ops.rightPushIfPresent(getKey(), value); - } - - @Override - public Long rightPush(V value) { - return ops.rightPush(getKey(), value); - } - - @Override - public Long rightPushAll(V... values) { - return ops.rightPushAll(getKey(), values); - } - - @Override - public Long rightPush(V pivot, V value) { - return ops.rightPush(getKey(), pivot, value); - } - - @Override - public V move(Direction from, K destinationKey, Direction to) { - return ops.move(getKey(), from, destinationKey, to); - } - - @Override - public V move(Direction from, K destinationKey, Direction to, Duration timeout) { - return ops.move(getKey(), from, destinationKey, to, timeout); - } - - @Override - public V move(Direction from, K destinationKey, Direction to, long timeout, TimeUnit unit) { - return ops.move(getKey(), from, destinationKey, to, timeout, unit); - } - - @Override - public void trim(long start, long end) { - ops.trim(getKey(), start, end); - } - - @Override - public void set(long index, V value) { - ops.set(getKey(), index, value); - } - - @Override - public DataType getType() { - return DataType.LIST; - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java deleted file mode 100644 index 4b262e649..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.data.redis.connection.DataType; - -/** - * Default implementation for {@link BoundSetOperations}. - * - * @author Costin Leau - * @author Christoph Strobl - */ -class DefaultBoundSetOperations extends DefaultBoundKeyOperations implements BoundSetOperations { - - private final SetOperations ops; - - /** - * Constructs a new DefaultBoundSetOperations instance. - * - * @param key - * @param operations - */ - DefaultBoundSetOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForSet(); - } - - @Override - public Long add(V... values) { - return ops.add(getKey(), values); - } - - @Override - public Set diff(K key) { - return ops.difference(getKey(), key); - } - - @Override - public Set diff(Collection keys) { - return ops.difference(getKey(), keys); - } - - @Override - public void diffAndStore(K key, K destKey) { - ops.differenceAndStore(getKey(), key, destKey); - } - - @Override - public void diffAndStore(Collection keys, K destKey) { - ops.differenceAndStore(getKey(), keys, destKey); - } - - @Override - public RedisOperations getOperations() { - return ops.getOperations(); - } - - @Override - public Set intersect(K key) { - return ops.intersect(getKey(), key); - } - - @Override - public Set intersect(Collection keys) { - return ops.intersect(getKey(), keys); - } - - @Override - public void intersectAndStore(K key, K destKey) { - ops.intersectAndStore(getKey(), key, destKey); - } - - @Override - public void intersectAndStore(Collection keys, K destKey) { - ops.intersectAndStore(getKey(), keys, destKey); - } - - @Override - public Boolean isMember(Object o) { - return ops.isMember(getKey(), o); - } - - @Override - public Map isMember(Object... objects) { - return ops.isMember(getKey(), objects); - } - - @Override - public Set members() { - return ops.members(getKey()); - } - - @Override - public Boolean move(K destKey, V value) { - return ops.move(getKey(), value, destKey); - } - - @Override - public V randomMember() { - return ops.randomMember(getKey()); - } - - @Override - public Set distinctRandomMembers(long count) { - return ops.distinctRandomMembers(getKey(), count); - } - - @Override - public List randomMembers(long count) { - return ops.randomMembers(getKey(), count); - } - - @Override - public Long remove(Object... values) { - return ops.remove(getKey(), values); - } - - @Override - public V pop() { - return ops.pop(getKey()); - } - - @Override - public Long size() { - return ops.size(getKey()); - } - - @Override - public Set union(K key) { - return ops.union(getKey(), key); - } - - @Override - public Set union(Collection keys) { - return ops.union(getKey(), keys); - } - - @Override - public void unionAndStore(K key, K destKey) { - ops.unionAndStore(getKey(), key, destKey); - } - - @Override - public void unionAndStore(Collection keys, K destKey) { - ops.unionAndStore(getKey(), keys, destKey); - } - - @Override - public DataType getType() { - return DataType.SET; - } - - @Override - public Cursor scan(ScanOptions options) { - return ops.scan(getKey(), options); - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundStreamOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundStreamOperations.java deleted file mode 100644 index 579c527b3..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundStreamOperations.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2018-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.core; - -import java.util.List; -import java.util.Map; - -import org.springframework.data.domain.Range; -import org.springframework.data.redis.connection.DataType; -import org.springframework.data.redis.connection.Limit; -import org.springframework.data.redis.connection.stream.Consumer; -import org.springframework.data.redis.connection.stream.MapRecord; -import org.springframework.data.redis.connection.stream.ReadOffset; -import org.springframework.data.redis.connection.stream.RecordId; -import org.springframework.data.redis.connection.stream.StreamOffset; -import org.springframework.data.redis.connection.stream.StreamReadOptions; -import org.springframework.lang.Nullable; - -/** - * Default implementation for {@link BoundStreamOperations}. - * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.2 - */ -class DefaultBoundStreamOperations extends DefaultBoundKeyOperations - implements BoundStreamOperations { - - private final StreamOperations ops; - - /** - * Constructs a new DefaultBoundSetOperations instance. - * - * @param key - * @param operations - */ - DefaultBoundStreamOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForStream(); - } - - @Nullable - @Override - public Long acknowledge(String group, String... recordIds) { - return ops.acknowledge(getKey(), group, recordIds); - } - - @Nullable - @Override - public RecordId add(Map body) { - return ops.add(getKey(), body); - } - - @Nullable - @Override - public Long delete(String... recordIds) { - return ops.delete(getKey(), recordIds); - } - - @Nullable - @Override - public String createGroup(ReadOffset readOffset, String group) { - return ops.createGroup(getKey(), readOffset, group); - } - - @Nullable - @Override - public Boolean deleteConsumer(Consumer consumer) { - return ops.deleteConsumer(getKey(), consumer); - } - - @Nullable - @Override - public Boolean destroyGroup(String group) { - return ops.destroyGroup(getKey(), group); - } - - @Nullable - @Override - public Long size() { - return ops.size(getKey()); - } - - @Nullable - @Override - public List> range(Range range, Limit limit) { - return ops.range(getKey(), range, limit); - } - - @Nullable - @Override - public List> read(StreamReadOptions readOptions, ReadOffset readOffset) { - return ops.read(readOptions, StreamOffset.create(getKey(), readOffset)); - } - - @Nullable - @Override - public List> read(Consumer consumer, StreamReadOptions readOptions, ReadOffset readOffset) { - return ops.read(consumer, readOptions, StreamOffset.create(getKey(), readOffset)); - } - - @Nullable - @Override - public List> reverseRange(Range range, Limit limit) { - return ops.reverseRange(getKey(), range, limit); - } - - @Nullable - @Override - public Long trim(long count) { - return trim(count, false); - } - - @Nullable - @Override - public Long trim(long count, boolean approximateTrimming) { - return ops.trim(getKey(), count, approximateTrimming); - } - - @Nullable - @Override - public DataType getType() { - return DataType.STREAM; - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java deleted file mode 100644 index f3861848b..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import org.springframework.data.redis.connection.DataType; -import org.springframework.lang.Nullable; - -/** - * @author Costin Leau - * @author Jiahe Cai - * @author Mark Paluch - */ -class DefaultBoundValueOperations extends DefaultBoundKeyOperations implements BoundValueOperations { - - private final ValueOperations ops; - - /** - * Constructs a new {@link DefaultBoundValueOperations} instance. - * - * @param key - * @param operations - */ - DefaultBoundValueOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForValue(); - } - - @Override - public V get() { - return ops.get(getKey()); - } - - @Nullable - @Override - public V getAndDelete() { - return ops.getAndDelete(getKey()); - } - - @Nullable - @Override - public V getAndExpire(long timeout, TimeUnit unit) { - return ops.getAndExpire(getKey(), timeout, unit); - } - - @Nullable - @Override - public V getAndExpire(Duration timeout) { - return ops.getAndExpire(getKey(), timeout); - } - - @Nullable - @Override - public V getAndPersist() { - return ops.getAndPersist(getKey()); - } - - @Override - public V getAndSet(V value) { - return ops.getAndSet(getKey(), value); - } - - @Override - public Long increment() { - return ops.increment(getKey()); - } - - @Override - public Long increment(long delta) { - return ops.increment(getKey(), delta); - } - - @Override - public Double increment(double delta) { - return ops.increment(getKey(), delta); - } - - @Override - public Long decrement() { - return ops.decrement(getKey()); - } - - @Override - public Long decrement(long delta) { - return ops.decrement(getKey(), delta); - } - - @Override - public Integer append(String value) { - return ops.append(getKey(), value); - } - - @Override - public String get(long start, long end) { - return ops.get(getKey(), start, end); - } - - @Override - public void set(V value, long timeout, TimeUnit unit) { - ops.set(getKey(), value, timeout, unit); - } - - @Override - public void set(V value) { - ops.set(getKey(), value); - } - - @Override - public Boolean setIfAbsent(V value) { - return ops.setIfAbsent(getKey(), value); - } - - @Override - public Boolean setIfAbsent(V value, long timeout, TimeUnit unit) { - return ops.setIfAbsent(getKey(), value, timeout, unit); - } - - @Nullable - @Override - public Boolean setIfPresent(V value) { - return ops.setIfPresent(getKey(), value); - } - - @Nullable - @Override - public Boolean setIfPresent(V value, long timeout, TimeUnit unit) { - return ops.setIfPresent(getKey(), value, timeout, unit); - } - - @Override - public void set(V value, long offset) { - ops.set(getKey(), value, offset); - } - - @Override - public Long size() { - return ops.size(getKey()); - } - - @Override - public RedisOperations getOperations() { - return ops.getOperations(); - } - - @Override - public DataType getType() { - return DataType.STRING; - } -} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java deleted file mode 100644 index b440ba08f..000000000 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2011-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.core; - -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import org.springframework.data.redis.connection.DataType; -import org.springframework.data.redis.connection.Limit; -import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; -import org.springframework.data.redis.connection.RedisZSetCommands.Range; -import org.springframework.data.redis.connection.zset.Weights; -import org.springframework.data.redis.core.ZSetOperations.TypedTuple; -import org.springframework.lang.Nullable; - -/** - * Default implementation for {@link BoundZSetOperations}. - * - * @author Costin Leau - * @author Christoph Strobl - * @author Mark Paluch - * @author Wongoo (望哥) - * @author Andrey Shlykov - */ -class DefaultBoundZSetOperations extends DefaultBoundKeyOperations implements BoundZSetOperations { - - private final ZSetOperations ops; - - /** - * Constructs a new DefaultBoundZSetOperations instance. - * - * @param key - * @param operations - */ - DefaultBoundZSetOperations(K key, RedisOperations operations) { - - super(key, operations); - this.ops = operations.opsForZSet(); - } - - @Override - public Boolean add(V value, double score) { - return ops.add(getKey(), value, score); - } - - @Override - public Boolean addIfAbsent(V value, double score) { - return ops.addIfAbsent(getKey(), value, score); - } - - @Override - public Long add(Set> tuples) { - return ops.add(getKey(), tuples); - } - - @Override - public Long addIfAbsent(Set> tuples) { - return ops.addIfAbsent(getKey(), tuples); - } - - @Override - public Double incrementScore(V value, double delta) { - return ops.incrementScore(getKey(), value, delta); - } - - @Override - public V randomMember() { - return ops.randomMember(getKey()); - } - - @Nullable - @Override - public Set distinctRandomMembers(long count) { - return ops.distinctRandomMembers(getKey(), count); - } - - @Nullable - @Override - public List randomMembers(long count) { - return ops.randomMembers(getKey(), count); - } - - @Override - public TypedTuple randomMemberWithScore() { - return ops.randomMemberWithScore(getKey()); - } - - @Nullable - @Override - public Set> distinctRandomMembersWithScore(long count) { - return ops.distinctRandomMembersWithScore(getKey(), count); - } - - @Nullable - @Override - public List> randomMembersWithScore(long count) { - return ops.randomMembersWithScore(getKey(), count); - } - - @Override - public RedisOperations getOperations() { - return ops.getOperations(); - } - - @Override - public Set range(long start, long end) { - return ops.range(getKey(), start, end); - } - - @Override - public Set rangeByScore(double min, double max) { - return ops.rangeByScore(getKey(), min, max); - } - - @Override - public Set> rangeByScoreWithScores(double min, double max) { - return ops.rangeByScoreWithScores(getKey(), min, max); - } - - @Override - public Set> rangeWithScores(long start, long end) { - return ops.rangeWithScores(getKey(), start, end); - } - - @Override - public Set reverseRangeByScore(double min, double max) { - return ops.reverseRangeByScore(getKey(), min, max); - } - - @Override - public Set> reverseRangeByScoreWithScores(double min, double max) { - return ops.reverseRangeByScoreWithScores(getKey(), min, max); - } - - @Override - public Set> reverseRangeWithScores(long start, long end) { - return ops.reverseRangeWithScores(getKey(), start, end); - } - - @Override - public Set rangeByLex(Range range, Limit limit) { - return ops.rangeByLex(getKey(), range, limit); - } - - @Override - public Set reverseRangeByLex(Range range, Limit limit) { - return ops.reverseRangeByLex(getKey(), range, limit); - } - - @Override - public Long rank(Object o) { - return ops.rank(getKey(), o); - } - - @Override - public Long reverseRank(Object o) { - return ops.reverseRank(getKey(), o); - } - - @Override - public Double score(Object o) { - return ops.score(getKey(), o); - } - - @Override - public List score(Object... o) { - return ops.score(getKey(), o); - } - - @Override - public Long remove(Object... values) { - return ops.remove(getKey(), values); - } - - @Override - public Long removeRange(long start, long end) { - return ops.removeRange(getKey(), start, end); - } - - @Override - public Long removeRangeByLex(Range range) { - return ops.removeRangeByLex(getKey(), range); - } - - @Override - public Long removeRangeByScore(double min, double max) { - return ops.removeRangeByScore(getKey(), min, max); - } - - @Override - public Set reverseRange(long start, long end) { - return ops.reverseRange(getKey(), start, end); - } - - @Override - public Long count(double min, double max) { - return ops.count(getKey(), min, max); - } - - @Override - public Long lexCount(Range range) { - return ops.lexCount(getKey(), range); - } - - @Nullable - @Override - public TypedTuple popMin() { - return ops.popMin(getKey()); - } - - @Nullable - @Override - public Set> popMin(long count) { - return ops.popMin(getKey(), count); - } - - @Nullable - @Override - public TypedTuple popMin(long timeout, TimeUnit unit) { - return ops.popMin(getKey(), timeout, unit); - } - - @Nullable - @Override - public TypedTuple popMax() { - return ops.popMax(getKey()); - } - - @Nullable - @Override - public Set> popMax(long count) { - return ops.popMax(getKey(), count); - } - - @Nullable - @Override - public TypedTuple popMax(long timeout, TimeUnit unit) { - return ops.popMax(getKey(), timeout, unit); - } - - @Override - public Long size() { - return zCard(); - } - - @Override - public Long zCard() { - return ops.zCard(getKey()); - } - - @Nullable - @Override - public Set difference(Collection otherKeys) { - return ops.difference(getKey(), otherKeys); - } - - @Nullable - @Override - public Set> differenceWithScores(Collection otherKeys) { - return ops.differenceWithScores(getKey(), otherKeys); - } - - @Nullable - @Override - public Long differenceAndStore(Collection otherKeys, K destKey) { - return ops.differenceAndStore(getKey(), otherKeys, destKey); - } - - @Nullable - @Override - public Set intersect(Collection otherKeys) { - return ops.intersect(getKey(), otherKeys); - } - - @Nullable - @Override - public Set> intersectWithScores(Collection otherKeys) { - return ops.intersectWithScores(getKey(), otherKeys); - } - - @Nullable - @Override - public Set> intersectWithScores(Collection otherKeys, Aggregate aggregate, Weights weights) { - return ops.intersectWithScores(getKey(), otherKeys, aggregate, weights); - } - - @Override - public Long intersectAndStore(K otherKey, K destKey) { - return ops.intersectAndStore(getKey(), otherKey, destKey); - } - - @Override - public Long intersectAndStore(Collection otherKeys, K destKey) { - return ops.intersectAndStore(getKey(), otherKeys, destKey); - } - - @Override - public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate) { - return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate); - } - - @Override - public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { - return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate, weights); - } - - @Nullable - @Override - public Set union(Collection otherKeys) { - return ops.union(getKey(), otherKeys); - } - - @Nullable - @Override - public Set> unionWithScores(Collection otherKeys) { - return ops.unionWithScores(getKey(), otherKeys); - } - - @Nullable - @Override - public Set> unionWithScores(Collection otherKeys, Aggregate aggregate, Weights weights) { - return ops.unionWithScores(getKey(), otherKeys, aggregate, weights); - } - - @Override - public Long unionAndStore(K otherKey, K destKey) { - return ops.unionAndStore(getKey(), otherKey, destKey); - } - - @Override - public Long unionAndStore(Collection otherKeys, K destKey) { - return ops.unionAndStore(getKey(), otherKeys, destKey); - } - - @Override - public Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate) { - return ops.unionAndStore(getKey(), otherKeys, destKey, aggregate); - } - - @Override - public Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { - return ops.unionAndStore(getKey(), otherKeys, destKey, aggregate, weights); - } - - @Override - public DataType getType() { - return DataType.ZSET; - } - - @Override - public Cursor> scan(ScanOptions options) { - return ops.scan(getKey(), options); - } -} diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index cc9b4af92..518517584 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -105,6 +105,7 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation private @Nullable ScriptExecutor scriptExecutor; + private final BoundOperationsProxyFactory boundOperations = new BoundOperationsProxyFactory(); private final ValueOperations valueOps = new DefaultValueOperations<>(this); private final ListOperations listOps = new DefaultListOperations<>(this); private final SetOperations setOps = new DefaultSetOperations<>(this); @@ -1056,12 +1057,12 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation @Override public BoundGeoOperations boundGeoOps(K key) { - return new DefaultBoundGeoOperations<>(key, this); + return boundOperations.createProxy(BoundGeoOperations.class, key, DataType.ZSET, this, RedisOperations::opsForGeo); } @Override public BoundHashOperations boundHashOps(K key) { - return new DefaultBoundHashOperations<>(key, this); + return boundOperations.createProxy(BoundHashOperations.class, key, DataType.HASH, this, it -> it.opsForHash()); } @Override @@ -1081,12 +1082,13 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation @Override public BoundListOperations boundListOps(K key) { - return new DefaultBoundListOperations<>(key, this); + return boundOperations.createProxy(BoundListOperations.class, key, DataType.LIST, this, + RedisOperations::opsForList); } @Override public BoundSetOperations boundSetOps(K key) { - return new DefaultBoundSetOperations<>(key, this); + return boundOperations.createProxy(BoundSetOperations.class, key, DataType.SET, this, RedisOperations::opsForSet); } @Override @@ -1101,18 +1103,18 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation @Override public StreamOperations opsForStream(HashMapper hashMapper) { - return new DefaultStreamOperations<>(this, hashMapper); } @Override public BoundStreamOperations boundStreamOps(K key) { - return new DefaultBoundStreamOperations<>(key, this); + return boundOperations.createProxy(BoundStreamOperations.class, key, DataType.STREAM, this, it -> opsForStream()); } @Override public BoundValueOperations boundValueOps(K key) { - return new DefaultBoundValueOperations<>(key, this); + return boundOperations.createProxy(BoundValueOperations.class, key, DataType.STRING, this, + RedisOperations::opsForValue); } @Override @@ -1122,7 +1124,8 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation @Override public BoundZSetOperations boundZSetOps(K key) { - return new DefaultBoundZSetOperations<>(key, this); + return boundOperations.createProxy(BoundZSetOperations.class, key, DataType.ZSET, this, + RedisOperations::opsForZSet); } @Override diff --git a/src/test/java/org/springframework/data/redis/core/BoundOperationsProxyFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/core/BoundOperationsProxyFactoryUnitTests.java new file mode 100644 index 000000000..c49e1eb40 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/BoundOperationsProxyFactoryUnitTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 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.core; + +import static org.assertj.core.api.Assertions.*; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * Unit tests for {@link BoundOperationsProxyFactory}. + * + * @author Mark Paluch + */ +class BoundOperationsProxyFactoryUnitTests { + + private void test(Class operationsTarget, BoundOperationsProxyFactory factory, Method method) { + + Method toCall = factory.lookupMethod(method, operationsTarget, true); + if (toCall == null) { + toCall = factory.lookupMethod(method, BoundOperationsProxyFactory.DefaultBoundKeyOperations.class, false); + } + + assertThat(toCall) + .describedAs("Backing method for method %s not found in %s".formatted(method, operationsTarget.getName())) + .isNotNull(); + } + + @TestFactory + Stream containers() { + + Stream, Class>> input = Stream.of( // + Tuples.of(BoundGeoOperations.class, GeoOperations.class), // + Tuples.of(BoundHashOperations.class, HashOperations.class), // + Tuples.of(BoundListOperations.class, ListOperations.class), // + Tuples.of(BoundStreamOperations.class, StreamOperations.class), // + Tuples.of(BoundSetOperations.class, SetOperations.class), // + Tuples.of(BoundValueOperations.class, ValueOperations.class), // + Tuples.of(BoundZSetOperations.class, ZSetOperations.class) // + ); + + return input.map(it -> containerFor(it.getT1(), it.getT2())); + } + + private DynamicContainer containerFor(Class boundInterface, Class operationsTarget) { + Stream methods = Arrays.stream(boundInterface.getMethods()) + .filter(it -> !it.getName().equals("getType") && !it.getName().equals("getOperations")) + .filter(Predicate.not(Method::isDefault)); + + BoundOperationsProxyFactory factory = new BoundOperationsProxyFactory(); + + Stream stream = DynamicTest.stream(methods, Method::toString, method -> { + + test(operationsTarget, factory, method); + + }); + + return DynamicContainer.dynamicContainer(boundInterface.getSimpleName(), stream); + } + +} diff --git a/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java deleted file mode 100644 index fee2bbd43..000000000 --- a/src/test/java/org/springframework/data/redis/core/DefaultBoundValueOperationsUnitTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018-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.core; - -import static org.mockito.Mockito.*; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -/** - * Unit tests for {@link DefaultBoundValueOperations} - * - * @author Christoph Strobl - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class DefaultBoundValueOperationsUnitTests { - - private DefaultBoundValueOperations boundValueOps; - - @Mock ValueOperations valueOps; - - private static final String KEY = "key-1"; - private static final Object VALUE = "value"; - - @BeforeEach - void setUp() { - - RedisOperations redisOps = mock(RedisOperations.class); - when(redisOps.opsForValue()).thenReturn(valueOps); - - boundValueOps = new DefaultBoundValueOperations<>(KEY, redisOps); - } - - @Test // DATAREDIS-786 - void setIfPresentShouldDelegateCorrectly() { - - boundValueOps.setIfPresent(VALUE); - - verify(valueOps).setIfPresent(eq(KEY), eq(VALUE)); - } - - @Test // DATAREDIS-786 - void setIfPresentWithTimeoutShouldDelegateCorrectly() { - - boundValueOps.setIfPresent(VALUE, 10, TimeUnit.SECONDS); - - verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(10L), eq(TimeUnit.SECONDS)); - } - - @Test // DATAREDIS-815 - void setWithDurationOfSecondsShouldDelegateCorrectly() { - - boundValueOps.set(VALUE, Duration.ofSeconds(1)); - - verify(valueOps).set(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); - } - - @Test // DATAREDIS-815 - void setWithDurationOfMillisShouldDelegateCorrectly() { - - boundValueOps.set(VALUE, Duration.ofMillis(250)); - - verify(valueOps).set(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); - } - - @Test // DATAREDIS-815 - void setIfAbsentWithDurationOfSecondsShouldDelegateCorrectly() { - - boundValueOps.setIfAbsent(VALUE, Duration.ofSeconds(1)); - - verify(valueOps).setIfAbsent(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); - } - - @Test // DATAREDIS-815 - void setIfAbsentWithDurationOfMillisShouldDelegateCorrectly() { - - boundValueOps.setIfAbsent(VALUE, Duration.ofMillis(250)); - - verify(valueOps).setIfAbsent(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); - } - - @Test // DATAREDIS-815 - void setIfPresentWithDurationOfSecondsShouldDelegateCorrectly() { - - boundValueOps.setIfPresent(VALUE, Duration.ofSeconds(1)); - - verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(1L), eq(TimeUnit.SECONDS)); - } - - @Test // DATAREDIS-815 - void setIfPresentWithDurationOfMillisShouldDelegateCorrectly() { - - boundValueOps.setIfPresent(VALUE, Duration.ofMillis(250)); - - verify(valueOps).setIfPresent(eq(KEY), eq(VALUE), eq(250L), eq(TimeUnit.MILLISECONDS)); - } -} diff --git a/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java deleted file mode 100644 index f97fa5eee..000000000 --- a/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.core; - -import static org.mockito.ArgumentMatchers.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.data.redis.connection.RedisZSetCommands.Range; - -/** - * Unit tests for {@link DefaultBoundZSetOperations}. - * - * @author Christoph Strobl - */ -class DefaultBoundZSetOperationsUnitTests { - - DefaultBoundZSetOperations zSetOperations; - ConnectionMockingRedisTemplate template; - - @BeforeEach - void beforeEach() { - - template = ConnectionMockingRedisTemplate.template(); - zSetOperations = new DefaultBoundZSetOperations<>("key", template); - } - - @Test // GH-1816 - void delegatesRemoveRangeByLex() { - - Range range = Range.range().gte("alpha").lte("omega"); - zSetOperations.removeRangeByLex(range); - - template.verify().zRemRangeByLex(eq(template.serializeKey("key")), eq(range)); - } -}