/** * Copyright 2018 Nikita Koksharov * * 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 com.fr.third.org.redisson; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import com.fr.third.org.redisson.api.RFuture; import com.fr.third.org.redisson.api.RSet; import com.fr.third.org.redisson.api.RSetMultimap; import com.fr.third.org.redisson.client.codec.Codec; import com.fr.third.org.redisson.client.protocol.RedisCommand; import com.fr.third.org.redisson.client.protocol.RedisCommands; import com.fr.third.org.redisson.client.protocol.RedisStrictCommand; import com.fr.third.org.redisson.client.protocol.convertor.BooleanAmountReplayConvertor; import com.fr.third.org.redisson.client.protocol.convertor.BooleanReplayConvertor; import com.fr.third.org.redisson.api.RFuture; import com.fr.third.org.redisson.api.RSet; import com.fr.third.org.redisson.api.RSetMultimap; import com.fr.third.org.redisson.client.codec.Codec; import com.fr.third.org.redisson.client.protocol.RedisCommand; import com.fr.third.org.redisson.client.protocol.RedisCommands; import com.fr.third.org.redisson.client.protocol.RedisStrictCommand; import com.fr.third.org.redisson.client.protocol.convertor.BooleanAmountReplayConvertor; import com.fr.third.org.redisson.client.protocol.convertor.BooleanReplayConvertor; import com.fr.third.org.redisson.command.CommandAsyncExecutor; import io.netty.buffer.ByteBuf; /** * @author Nikita Koksharov * * @param key * @param value */ public class RedissonSetMultimap extends RedissonMultimap implements RSetMultimap { private static final RedisStrictCommand SCARD_VALUE = new RedisStrictCommand("SCARD", new BooleanAmountReplayConvertor()); private static final RedisCommand SISMEMBER_VALUE = new RedisCommand("SISMEMBER", new BooleanReplayConvertor()); public RedissonSetMultimap(CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); } public RedissonSetMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); } @Override public RFuture sizeAsync() { return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_INTEGER, "local keys = redis.call('hgetall', KEYS[1]); " + "local size = 0; " + "for i, v in ipairs(keys) do " + "if i % 2 == 0 then " + "local name = ARGV[1] .. v; " + "size = size + redis.call('scard', name); " + "end;" + "end; " + "return size; ", Arrays.asList(getName()), prefix); } @Override public RFuture containsKeyAsync(Object key) { ByteBuf keyState = encodeMapKey(key); String keyHash = hashAndRelease(keyState); String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName); } @Override public RFuture containsValueAsync(Object value) { ByteBuf valueState = encodeMapValue(value); return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, "local keys = redis.call('hgetall', KEYS[1]); " + "for i, v in ipairs(keys) do " + "if i % 2 == 0 then " + "local name = ARGV[2] .. v; " + "if redis.call('sismember', name, ARGV[1]) == 1 then " + "return 1; " + "end;" + "end;" + "end; " + "return 0; ", Arrays.asList(getName()), valueState, prefix); } @Override public RFuture containsEntryAsync(Object key, Object value) { ByteBuf keyState = encodeMapKey(key); String keyHash = hashAndRelease(keyState); ByteBuf valueState = encodeMapValue(value); String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState); } @Override public RFuture putAsync(K key, V value) { ByteBuf keyState = encodeMapKey(key); String keyHash = hash(keyState); ByteBuf valueState = encodeMapValue(value); String setName = getValuesName(keyHash); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "return redis.call('sadd', KEYS[2], ARGV[3]); ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } @Override public RFuture removeAsync(Object key, Object value) { ByteBuf keyState = encodeMapKey(key); String keyHash = hash(keyState); ByteBuf valueState = encodeMapValue(value); String setName = getValuesName(keyHash); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, "local res = redis.call('srem', KEYS[2], ARGV[2]); " + "if res == 1 and redis.call('scard', KEYS[2]) == 0 then " + "redis.call('hdel', KEYS[1], ARGV[1]); " + "end; " + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } @Override public RFuture putAllAsync(K key, Iterable values) { List params = new ArrayList(); ByteBuf keyState = encodeMapKey(key); params.add(keyState); String keyHash = hash(keyState); params.add(keyHash); for (Object value : values) { ByteBuf valueState = encodeMapValue(value); params.add(valueState); } String setName = getValuesName(keyHash); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_AMOUNT, "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } @Override public RSet get(final K key) { final ByteBuf keyState = encodeMapKey(key); final String keyHash = hashAndRelease(keyState); final String setName = getValuesName(keyHash); return new RedissonSet(codec, commandExecutor, setName, null) { @Override public RFuture deleteAsync() { ByteBuf keyState = encodeMapKey(key); return RedissonSetMultimap.this.fastRemoveAsync(Arrays.asList(keyState), Arrays.asList(setName), RedisCommands.EVAL_BOOLEAN_AMOUNT); } @Override public RFuture clearExpireAsync() { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } @Override public RFuture expireAsync(long timeToLive, TimeUnit timeUnit) { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } @Override public RFuture expireAtAsync(long timestamp) { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } @Override public RFuture remainTimeToLiveAsync() { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } @Override public RFuture renameAsync(String newName) { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } @Override public RFuture renamenxAsync(String newName) { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); } }; } @Override public Set getAll(K key) { return (Set) super.getAll(key); } @Override public RFuture> getAllAsync(K key) { ByteBuf keyState = encodeMapKey(key); String keyHash = hashAndRelease(keyState); String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName); } @Override public Set removeAll(Object key) { return (Set) get(removeAllAsync(key)); } @Override public RFuture> removeAllAsync(Object key) { ByteBuf keyState = encodeMapKey(key); String keyHash = hash(keyState); String setName = getValuesName(keyHash); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, "redis.call('hdel', KEYS[1], ARGV[1]); " + "local members = redis.call('smembers', KEYS[2]); " + "redis.call('del', KEYS[2]); " + "return members; ", Arrays.asList(getName(), setName), keyState); } @Override public Set> entries() { return (Set>) super.entries(); } @Override public Set replaceValues(K key, Iterable values) { return (Set) get(replaceValuesAsync(key, values)); } @Override Iterator valuesIterator() { return new RedissonSetMultimapIterator(RedissonSetMultimap.this, commandExecutor, codec) { @Override V getValue(V entry) { return (V) entry; } }; } @Override RedissonSetMultimapIterator> entryIterator() { return new RedissonSetMultimapIterator>(RedissonSetMultimap.this, commandExecutor, codec); } @Override public RFuture> replaceValuesAsync(K key, Iterable values) { List params = new ArrayList(); ByteBuf keyState = encodeMapKey(key); params.add(keyState); String keyHash = hash(keyState); params.add(keyHash); for (Object value : values) { ByteBuf valueState = encodeMapValue(value); params.add(valueState); } String setName = getValuesName(keyHash); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "local members = redis.call('smembers', KEYS[2]); " + "redis.call('del', KEYS[2]); " + "redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); " + "return members; ", Arrays.asList(getName(), setName), params.toArray()); } }