/* * Copyright 2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.redis.core; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; import org.reactivestreams.Publisher; import org.springframework.data.redis.connection.ReactiveStringCommands; import org.springframework.data.redis.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.util.Assert; /** * Default implementation of {@link ReactiveValueOperations}. * * @author Mark Paluch * @author Christoph Strobl * @since 2.0 */ public class DefaultReactiveValueOperations<K, V> implements ReactiveValueOperations<K, V> { private final ReactiveRedisTemplate<?, ?> template; private final RedisSerializationContext<K, V> serializationContext; /** * Creates new {@link DefaultReactiveValueOperations}. * * @param template must not be {@literal null}. * @param serializationContext must not be {@literal null}. */ public DefaultReactiveValueOperations(ReactiveRedisTemplate<?, ?> template, RedisSerializationContext<K, V> serializationContext) { Assert.notNull(template, "ReactiveRedisTemplate must not be null!"); Assert.notNull(serializationContext, "RedisSerializationContext must not be null!"); this.template = template; this.serializationContext = serializationContext; } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#set(java.lang.Object, java.lang.Object) */ @Override public Mono<Boolean> set(K key, V value) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.set(rawKey(key), rawValue(value))); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#set(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit) */ @Override public Mono<Boolean> set(K key, V value, Duration timeout) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(timeout, "Duration must not be null!"); return createMono( connection -> connection.set(rawKey(key), rawValue(value), Expiration.from(timeout), SetOption.UPSERT)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfAbsent(java.lang.Object, java.lang.Object) */ @Override public Mono<Boolean> setIfAbsent(K key, V value) { Assert.notNull(key, "Key must not be null!"); return createMono( connection -> connection.set(rawKey(key), rawValue(value), Expiration.persistent(), SetOption.SET_IF_ABSENT)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfPresent(java.lang.Object, java.lang.Object) */ @Override public Mono<Boolean> setIfPresent(K key, V value) { Assert.notNull(key, "Key must not be null!"); return createMono( connection -> connection.set(rawKey(key), rawValue(value), Expiration.persistent(), SetOption.SET_IF_PRESENT)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#multiSet(java.util.Map) */ @Override public Mono<Boolean> multiSet(Map<? extends K, ? extends V> map) { Assert.notNull(map, "Map must not be null!"); return createMono(connection -> { Mono<Map<ByteBuffer, ByteBuffer>> serializedMap = Flux.fromIterable(() -> map.entrySet().iterator()) .collectMap(entry -> rawKey(entry.getKey()), entry -> rawValue(entry.getValue())); return serializedMap.flatMap(connection::mSet); }); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#multiSetIfAbsent(java.util.Map) */ @Override public Mono<Boolean> multiSetIfAbsent(Map<? extends K, ? extends V> map) { Assert.notNull(map, "Map must not be null!"); return createMono(connection -> { Mono<Map<ByteBuffer, ByteBuffer>> serializedMap = Flux.fromIterable(() -> map.entrySet().iterator()) .collectMap(entry -> rawKey(entry.getKey()), entry -> rawValue(entry.getValue())); return serializedMap.flatMap(connection::mSetNX); }); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#get(java.lang.Object) */ @SuppressWarnings("unchecked") @Override public Mono<V> get(Object key) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.get(rawKey((K) key)) // .map(this::readValue)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#getAndSet(java.lang.Object, java.lang.Object) */ @Override public Mono<V> getAndSet(K key, V value) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.getSet(rawKey(key), rawValue(value)).map(value()::read)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#multiGet(java.util.Collection) */ @Override public Mono<List<V>> multiGet(Collection<K> keys) { Assert.notNull(keys, "Keys must not be null!"); return createMono(connection -> Flux.fromIterable(keys).map(key()::write).collectList().flatMap(connection::mGet) .map(this::deserializeValues)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#append(java.lang.Object, java.lang.String) */ @Override public Mono<Long> append(K key, String value) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); return createMono( connection -> connection.append(rawKey(key), serializationContext.getStringSerializationPair().write(value))); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#get(java.lang.Object, long, long) */ @Override public Mono<String> get(K key, long start, long end) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.getRange(rawKey(key), start, end) // .map(stringSerializationPair()::read)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#set(java.lang.Object, java.lang.Object, long) */ @Override public Mono<Long> set(K key, V value, long offset) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.setRange(rawKey(key), rawValue(value), offset)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#size(java.lang.Object) */ @Override public Mono<Long> size(K key) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.strLen(rawKey(key))); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#setBit(java.lang.Object, long, boolean) */ @Override public Mono<Boolean> setBit(K key, long offset, boolean value) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.setBit(rawKey(key), offset, value)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#getBit(java.lang.Object, long) */ @Override public Mono<Boolean> getBit(K key, long offset) { Assert.notNull(key, "Key must not be null!"); return createMono(connection -> connection.getBit(rawKey(key), offset)); } /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#delete(java.lang.Object) */ @Override public Mono<Boolean> delete(K key) { Assert.notNull(key, "Key must not be null!"); return template.createMono(connection -> connection.keyCommands().del(rawKey(key))).map(l -> l != 0); } private <T> Mono<T> createMono(Function<ReactiveStringCommands, Publisher<T>> function) { Assert.notNull(function, "Function must not be null!"); return template.createMono(connection -> function.apply(connection.stringCommands())); } private ByteBuffer rawKey(K key) { return serializationContext.getKeySerializationPair().write(key); } private ByteBuffer rawValue(V value) { return serializationContext.getValueSerializationPair().write(value); } private V readValue(ByteBuffer buffer) { return serializationContext.getValueSerializationPair().read(buffer); } private SerializationPair<String> stringSerializationPair() { return serializationContext.getStringSerializationPair(); } private SerializationPair<K> key() { return serializationContext.getKeySerializationPair(); } private SerializationPair<V> value() { return serializationContext.getValueSerializationPair(); } private List<V> deserializeValues(List<ByteBuffer> source) { List<V> result = new ArrayList<>(source.size()); for (ByteBuffer buffer : source) { if (buffer == null) { result.add(null); } else { result.add(readValue(buffer)); } } return result; } }