/* * Copyright 2016-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.connection; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import org.reactivestreams.Publisher; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.Command; import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; import org.springframework.util.Assert; /** * Redis Hash commands executed using reactive infrastructure. * * @author Christoph Strobl * @author Mark Paluch * @since 2.0 */ public interface ReactiveHashCommands { /** * {@literal HSET} {@link Command}. * * @author Christoph Strobl * @see <a href="http://redis.io/commands/hset">Redis Documentation: HSET</a> */ class HSetCommand extends KeyCommand { private static final ByteBuffer SINGLE_VALUE_KEY = ByteBuffer.allocate(0); private final Map<ByteBuffer, ByteBuffer> fieldValueMap; private final boolean upsert; private HSetCommand(ByteBuffer key, Map<ByteBuffer, ByteBuffer> keyValueMap, boolean upsert) { super(key); this.fieldValueMap = keyValueMap; this.upsert = upsert; } /** * Creates a new {@link HSetCommand} given a {@link ByteBuffer key}. * * @param value must not be {@literal null}. * @return a new {@link HSetCommand} for {@link ByteBuffer key}. */ public static HSetCommand value(ByteBuffer value) { Assert.notNull(value, "Value must not be null!"); return new HSetCommand(null, Collections.singletonMap(SINGLE_VALUE_KEY, value), Boolean.TRUE); } /** * Creates a new {@link HSetCommand} given a {@link Map} of field values. * * @param fieldValueMap must not be {@literal null}. * @return a new {@link HSetCommand} for a {@link Map} of field values. */ public static HSetCommand fieldValues(Map<ByteBuffer, ByteBuffer> fieldValueMap) { Assert.notNull(fieldValueMap, "Field values map must not be null!"); return new HSetCommand(null, fieldValueMap, Boolean.TRUE); } /** * Applies a field. Constructs a new command instance with all previously configured properties. * * @param field must not be {@literal null}. * @return a new {@link HSetCommand} with {@literal field} applied. */ public HSetCommand ofField(ByteBuffer field) { if (!fieldValueMap.containsKey(SINGLE_VALUE_KEY)) { throw new InvalidDataAccessApiUsageException("Value has not been set."); } Assert.notNull(field, "Field not be null!"); return new HSetCommand(getKey(), Collections.singletonMap(field, fieldValueMap.get(SINGLE_VALUE_KEY)), upsert); } /** * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. * * @param key must not be {@literal null}. * @return a new {@link HSetCommand} with {@literal key} applied. */ public HSetCommand forKey(ByteBuffer key) { Assert.notNull(key, "Key not be null!"); return new HSetCommand(key, fieldValueMap, upsert); } /** * Disable upsert. Constructs a new command instance with all previously configured properties. * * @return a new {@link HSetCommand} with upsert disabled. */ public HSetCommand ifValueNotExists() { return new HSetCommand(getKey(), fieldValueMap, Boolean.FALSE); } /** * @return */ public boolean isUpsert() { return upsert; } /** * @return */ public Map<ByteBuffer, ByteBuffer> getFieldValueMap() { return fieldValueMap; } } /** * Set the {@literal value} of a hash {@literal field}. * * @param key must not be {@literal null}. * @param field must not be {@literal null}. * @param value must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hset">Redis Documentation: HSET</a> */ default Mono<Boolean> hSet(ByteBuffer key, ByteBuffer field, ByteBuffer value) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(field, "Field must not be null!"); Assert.notNull(value, "Value must not be null!"); return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key))).next().map(BooleanResponse::getOutput); } /** * Set the {@literal value} of a hash {@literal field}. * * @param key must not be {@literal null}. * @param field must not be {@literal null}. * @param value must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hsetnx">Redis Documentation: HSETNX</a> */ default Mono<Boolean> hSetNX(ByteBuffer key, ByteBuffer field, ByteBuffer value) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(field, "Field must not be null!"); Assert.notNull(value, "Value must not be null!"); return hSet(Mono.just(HSetCommand.value(value).ofField(field).forKey(key).ifValueNotExists())).next() .map(BooleanResponse::getOutput); } /** * Set multiple hash fields to multiple values using data provided in {@literal fieldValueMap}. * * @param key must not be {@literal null}. * @param fieldValueMap must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hmset">Redis Documentation: HMSET</a> */ default Mono<Boolean> hMSet(ByteBuffer key, Map<ByteBuffer, ByteBuffer> fieldValueMap) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(fieldValueMap, "Field must not be null!"); return hSet(Mono.just(HSetCommand.fieldValues(fieldValueMap).forKey(key).ifValueNotExists())).next() .map(BooleanResponse::getOutput); } /** * Set the {@literal value} of a hash {@literal field}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hset">Redis Documentation: HSET</a> */ Flux<BooleanResponse<HSetCommand>> hSet(Publisher<HSetCommand> commands); /** * {@literal HGET} {@link Command}. * * @author Christoph Strobl * @see <a href="http://redis.io/commands/hget">Redis Documentation: HGET</a> */ class HGetCommand extends KeyCommand { private List<ByteBuffer> fields; private HGetCommand(ByteBuffer key, List<ByteBuffer> fields) { super(key); this.fields = fields; } /** * Creates a new {@link HGetCommand} given a {@link ByteBuffer field name}. * * @param field must not be {@literal null}. * @return a new {@link HGetCommand} for a {@link ByteBuffer field name}. */ public static HGetCommand field(ByteBuffer field) { Assert.notNull(field, "Field must not be null!"); return new HGetCommand(null, Collections.singletonList(field)); } /** * Creates a new {@link HGetCommand} given a {@link Collection} of field names. * * @param fields must not be {@literal null}. * @return a new {@link HGetCommand} for a {@link Collection} of field names. */ public static HGetCommand fields(Collection<ByteBuffer> fields) { Assert.notNull(fields, "Fields must not be null!"); return new HGetCommand(null, new ArrayList<>(fields)); } /** * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties. * * @param key must not be {@literal null}. * @return a new {@link HGetCommand} with {@literal key} applied. */ public HGetCommand from(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return new HGetCommand(key, fields); } /** * @return */ public List<ByteBuffer> getFields() { return fields; } } /** * Get value for given {@literal field} from hash at {@literal key}. * * @param key must not be {@literal null}. * @param field must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hget">Redis Documentation: HGET</a> */ default Mono<ByteBuffer> hGet(ByteBuffer key, ByteBuffer field) { return hMGet(key, Collections.singletonList(field)).map(val -> val.isEmpty() ? null : val.iterator().next()); } /** * Get values for given {@literal fields} from hash at {@literal key}. * * @param key must not be {@literal null}. * @param fields must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hmget">Redis Documentation: HMGET</a> */ default Mono<List<ByteBuffer>> hMGet(ByteBuffer key, Collection<ByteBuffer> fields) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(fields, "Fields must not be null!"); return hMGet(Mono.just(HGetCommand.fields(fields).from(key))).next().map(MultiValueResponse::getOutput); } /** * Get values for given {@literal fields} from hash at {@literal key}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hmget">Redis Documentation: HMGET</a> */ Flux<MultiValueResponse<HGetCommand, ByteBuffer>> hMGet(Publisher<HGetCommand> commands); /** * {@literal HEXISTS} {@link Command}. * * @author Christoph Strobl * @see <a href="http://redis.io/commands/hexists">Redis Documentation: HEXISTS</a> */ class HExistsCommand extends KeyCommand { private final ByteBuffer field; private HExistsCommand(ByteBuffer key, ByteBuffer field) { super(key); this.field = field; } /** * Creates a new {@link HExistsCommand} given a {@link ByteBuffer field name}. * * @param field must not be {@literal null}. * @return a new {@link HExistsCommand} for a {@link ByteBuffer field name}. */ public static HExistsCommand field(ByteBuffer field) { Assert.notNull(field, "Field must not be null!"); return new HExistsCommand(null, field); } /** * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties. * * @param key must not be {@literal null}. * @return a new {@link HExistsCommand} with {@literal key} applied. */ public HExistsCommand in(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return new HExistsCommand(key, field); } /** * @return */ public ByteBuffer getField() { return field; } } /** * Determine if given hash {@literal field} exists. * * @param key must not be {@literal null}. * @param field must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hexists">Redis Documentation: HEXISTS</a> */ default Mono<Boolean> hExists(ByteBuffer key, ByteBuffer field) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(field, "Field must not be null!"); return hExists(Mono.just(HExistsCommand.field(field).in(key))).next().map(BooleanResponse::getOutput); } /** * Determine if given hash {@literal field} exists. * * @param commands * @return * @see <a href="http://redis.io/commands/hexists">Redis Documentation: HEXISTS</a> */ Flux<BooleanResponse<HExistsCommand>> hExists(Publisher<HExistsCommand> commands); /** * @author Christoph Strobl * @see <a href="http://redis.io/commands/hdel">Redis Documentation: HDEL</a> */ class HDelCommand extends KeyCommand { private final List<ByteBuffer> fields; private HDelCommand(ByteBuffer key, List<ByteBuffer> fields) { super(key); this.fields = fields; } /** * Creates a new {@link HDelCommand} given a {@link ByteBuffer field name}. * * @param field must not be {@literal null}. * @return a new {@link HDelCommand} for a {@link ByteBuffer field name}. */ public static HDelCommand field(ByteBuffer field) { Assert.notNull(field, "Field must not be null!"); return new HDelCommand(null, Collections.singletonList(field)); } /** * Creates a new {@link HDelCommand} given a {@link Collection} of field names. * * @param fields must not be {@literal null}. * @return a new {@link HDelCommand} for a {@link Collection} of field names. */ public static HDelCommand fields(Collection<ByteBuffer> fields) { Assert.notNull(fields, "Fields must not be null!"); return new HDelCommand(null, new ArrayList<>(fields)); } /** * Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties. * * @param key must not be {@literal null}. * @return a new {@link HDelCommand} with {@literal key} applied. */ public HDelCommand from(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return new HDelCommand(key, fields); } /** * @return */ public List<ByteBuffer> getFields() { return fields; } } /** * Delete given hash {@literal field}. * * @param key must not be {@literal null}. * @param field must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hdel">Redis Documentation: HDEL</a> */ default Mono<Boolean> hDel(ByteBuffer key, ByteBuffer field) { Assert.notNull(field, "Field must not be null!"); return hDel(key, Collections.singletonList(field)).map(val -> val > 0 ? Boolean.TRUE : Boolean.FALSE); } /** * Delete given hash {@literal fields}. * * @param key must not be {@literal null}. * @param fields must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hdel">Redis Documentation: HDEL</a> */ default Mono<Long> hDel(ByteBuffer key, Collection<ByteBuffer> fields) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(fields, "Fields must not be null!"); return hDel(Mono.just(HDelCommand.fields(fields).from(key))).next().map(NumericResponse::getOutput); } /** * Delete given hash {@literal fields}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hdel">Redis Documentation: HDEL</a> */ Flux<NumericResponse<HDelCommand, Long>> hDel(Publisher<HDelCommand> commands); /** * Get size of hash at {@literal key}. * * @param key must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hlen">Redis Documentation: HLEN</a> */ default Mono<Long> hLen(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return hLen(Mono.just(new KeyCommand(key))).next().map(NumericResponse::getOutput); } /** * Get size of hash at {@literal key}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hlen">Redis Documentation: HLEN</a> */ Flux<NumericResponse<KeyCommand, Long>> hLen(Publisher<KeyCommand> commands); /** * Get key set (fields) of hash at {@literal key}. * * @param key must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hkeys">Redis Documentation: HKEYS</a> */ default Flux<ByteBuffer> hKeys(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return hKeys(Mono.just(new KeyCommand(key))).flatMap(CommandResponse::getOutput); } /** * Get key set (fields) of hash at {@literal key}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hkeys">Redis Documentation: HKEYS</a> */ Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> hKeys(Publisher<KeyCommand> commands); /** * Get entry set (values) of hash at {@literal key}. * * @param key must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hvals">Redis Documentation: HVALS</a> */ default Flux<ByteBuffer> hVals(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return hVals(Mono.just(new KeyCommand(key))).flatMap(CommandResponse::getOutput); } /** * Get entry set (values) of hash at {@literal key}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hvals">Redis Documentation: HVALS</a> */ Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> hVals(Publisher<KeyCommand> commands); /** * Get entire hash stored at {@literal key}. * * @param key must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hgetall">Redis Documentation: HGETALL</a> */ default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hGetAll(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); return hGetAll(Mono.just(new KeyCommand(key))).flatMap(CommandResponse::getOutput); } /** * Get entire hash stored at {@literal key}. * * @param commands must not be {@literal null}. * @return * @see <a href="http://redis.io/commands/hgetall">Redis Documentation: HGETALL</a> */ Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hGetAll(Publisher<KeyCommand> commands); }