diff --git a/.gitignore b/.gitignore index 6cfdf14b5..0f61eb300 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /target /*.cmd + +nb-configuration.xml diff --git a/pom.xml b/pom.xml index 2411aa90c..106c3a909 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10-SNAPSHOT + 2.2.11-SNAPSHOT bundle Redisson @@ -351,9 +351,6 @@ org.apache.maven.plugins maven-surefire-plugin 2.16 - - -Dfile.encoding=utf-8 - diff --git a/src/main/java/org/redisson/EvictionScheduler.java b/src/main/java/org/redisson/EvictionScheduler.java index 570db18b8..d73f861cc 100644 --- a/src/main/java/org/redisson/EvictionScheduler.java +++ b/src/main/java/org/redisson/EvictionScheduler.java @@ -49,6 +49,7 @@ public class EvictionScheduler { final String name; final String timeoutSetName; final String maxIdleSetName; + final boolean multimap; final Deque sizeHistory = new LinkedList(); int delay = 10; @@ -56,10 +57,11 @@ public class EvictionScheduler { final int maxDelay = 2*60*60; final int keysLimit = 300; - public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName) { + public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName, boolean multimap) { this.name = name; this.timeoutSetName = timeoutSetName; this.maxIdleSetName = maxIdleSetName; + this.multimap = multimap; } public void schedule() { @@ -68,7 +70,7 @@ public class EvictionScheduler { @Override public void run() { - Future future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit); + Future future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit, multimap); future.addListener(new FutureListener() { @Override @@ -123,16 +125,25 @@ public class EvictionScheduler { this.executor = executor; } + public void scheduleCleanMultimap(String name, String timeoutSetName) { + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, true); + RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); + if (prevTask == null) { + task.schedule(); + } + } + public void schedule(String name, String timeoutSetName) { - RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null); + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, false); RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); if (prevTask == null) { task.schedule(); } } + public void schedule(String name, String timeoutSetName, String maxIdleSetName) { - RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName); + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false); RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); if (prevTask == null) { task.schedule(); @@ -155,7 +166,7 @@ public class EvictionScheduler { return; } - Future future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean); + Future future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean, false); future.addListener(new FutureListener() { @Override @@ -175,7 +186,27 @@ public class EvictionScheduler { }); } - private Future cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit) { + private Future cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit, boolean multimap) { + if (multimap) { + return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, + "local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " + + "if #expiredKeys > 0 then " + + "redis.call('zrem', KEYS[2], unpack(expiredKeys)); " + + + "local values = redis.call('hmget', KEYS[1], unpack(expiredKeys)); " + + "local keys = {}; " + + "for i, v in ipairs(values) do " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end; " + + "redis.call('del', unpack(keys)); " + + + "redis.call('hdel', KEYS[1], unpack(expiredKeys)); " + + "end; " + + "return #expiredKeys;", + Arrays.asList(name, timeoutSetName), System.currentTimeMillis(), keysLimit); + } + if (maxIdleSetName != null) { return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local expiredKeys1 = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " @@ -193,6 +224,7 @@ public class EvictionScheduler { + "return #expiredKeys1 + #expiredKeys2;", Arrays.asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit); } + return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " + "if #expiredKeys > 0 then " diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index f02e14c0d..f1fdf2953 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -54,18 +54,21 @@ import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; import org.redisson.core.RList; import org.redisson.core.RListMultimap; +import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -90,15 +93,21 @@ public class Redisson implements RedissonClient { Redisson(Config config) { this.config = config; Config configCopy = new Config(config); + if (configCopy.getMasterSlaveServersConfig() != null) { + validate(configCopy.getMasterSlaveServersConfig()); connectionManager = new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy); } else if (configCopy.getSingleServerConfig() != null) { + validate(configCopy.getSingleServerConfig()); connectionManager = new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy); } else if (configCopy.getSentinelServersConfig() != null) { + validate(configCopy.getSentinelServersConfig()); connectionManager = new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy); } else if (configCopy.getClusterServersConfig() != null) { + validate(configCopy.getClusterServersConfig()); connectionManager = new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy); } else if (configCopy.getElasticacheServersConfig() != null) { + validate(configCopy.getElasticacheServersConfig()); connectionManager = new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy); } else { throw new IllegalArgumentException("server(s) address(es) not defined!"); @@ -107,7 +116,23 @@ public class Redisson implements RedissonClient { evictionScheduler = new EvictionScheduler(commandExecutor); } + private void validate(SingleServerConfig config) { + if (config.getConnectionPoolSize() < config.getConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("connectionPoolSize can't be lower than connectionMinimumIdleSize"); + } + } + private void validate(BaseMasterSlaveServersConfig config) { + if (config.getSlaveConnectionPoolSize() < config.getSlaveConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveConnectionPoolSize can't be lower than slaveConnectionMinimumIdleSize"); + } + if (config.getMasterConnectionPoolSize() < config.getMasterConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("masterConnectionPoolSize can't be lower than masterConnectionMinimumIdleSize"); + } + if (config.getSlaveSubscriptionConnectionPoolSize() < config.getSlaveSubscriptionConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveSubscriptionConnectionMinimumIdleSize can't be lower than slaveSubscriptionConnectionPoolSize"); + } + } /** * Create sync/async Redisson instance with default config @@ -179,10 +204,12 @@ public class Redisson implements RedissonClient { return buckets; } + @Override public Map loadBucketValues(Collection keys) { return loadBucketValues(keys.toArray(new String[keys.size()])); } + @Override public Map loadBucketValues(String ... keys) { if (keys.length == 0) { return Collections.emptyMap(); @@ -203,6 +230,7 @@ public class Redisson implements RedissonClient { return result; } + @Override public void saveBuckets(Map buckets) { if (buckets.isEmpty()) { return; @@ -265,6 +293,26 @@ public class Redisson implements RedissonClient { public RSetMultimap getSetMultimap(String name) { return new RedissonSetMultimap(commandExecutor, name); } + + @Override + public RSetMultimapCache getSetMultimapCache(String name) { + return new RedissonSetMultimapCache(evictionScheduler, commandExecutor, name); + } + + @Override + public RSetMultimapCache getSetMultimapCache(String name, Codec codec) { + return new RedissonSetMultimapCache(evictionScheduler, codec, commandExecutor, name); + } + + @Override + public RListMultimapCache getListMultimapCache(String name) { + return new RedissonListMultimapCache(evictionScheduler, commandExecutor, name); + } + + @Override + public RListMultimapCache getListMultimapCache(String name, Codec codec) { + return new RedissonListMultimapCache(evictionScheduler, codec, commandExecutor, name); + } @Override public RSetMultimap getSetMultimap(String name, Codec codec) { @@ -321,6 +369,10 @@ public class Redisson implements RedissonClient { return new RedissonScript(commandExecutor); } + public RRemoteService getRemoteSerivce() { + return new RedissonRemoteService(this); + } + @Override public RSortedSet getSortedSet(String name) { return new RedissonSortedSet(commandExecutor, name); @@ -461,10 +513,12 @@ public class Redisson implements RedissonClient { return config; } + @Override public NodesGroup getNodesGroup() { return new RedisNodes(connectionManager); } + @Override public NodesGroup getClusterNodesGroup() { if (!config.isClusterConfig()) { throw new IllegalStateException("Redisson is not in cluster mode!"); diff --git a/src/main/java/org/redisson/RedissonBloomFilter.java b/src/main/java/org/redisson/RedissonBloomFilter.java index 6b6913039..06fb7b6db 100644 --- a/src/main/java/org/redisson/RedissonBloomFilter.java +++ b/src/main/java/org/redisson/RedissonBloomFilter.java @@ -200,7 +200,7 @@ public class RedissonBloomFilter extends RedissonExpirable implements RBloomF @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getConfigName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getConfigName()); } private void readConfig() { diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 66fe5b49f..edb10dced 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -31,7 +31,6 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; -import org.redisson.core.RMapCache; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; import org.redisson.core.RHyperLogLog; @@ -39,17 +38,21 @@ import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; import org.redisson.core.RList; import org.redisson.core.RListMultimap; +import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; +import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -218,7 +221,7 @@ public interface RedissonClient { RList getList(String name, Codec codec); /** - * Returns List based MultiMap instance by name. + * Returns List based Multimap instance by name. * * @param name * @return @@ -226,7 +229,7 @@ public interface RedissonClient { RListMultimap getListMultimap(String name); /** - * Returns List based MultiMap instance by name + * Returns List based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -235,6 +238,29 @@ public interface RedissonClient { */ RListMultimap getListMultimap(String name, Codec codec); + /** + * Returns List based Multimap instance by name. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.

+ * + * @param name + * @return + */ + RListMultimapCache getListMultimapCache(String name); + + /** + * Returns List based Multimap instance by name + * using provided codec for both map keys and values. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.

+ * + * @param name + * @return + */ + RListMultimapCache getListMultimapCache(String name, Codec codec); + /** * Returns map instance by name. * @@ -254,15 +280,15 @@ public interface RedissonClient { RMap getMap(String name, Codec codec); /** - * Returns Set based MultiMap instance by name. + * Returns Set based Multimap instance by name. * * @param name * @return */ RSetMultimap getSetMultimap(String name); - + /** - * Returns Set based MultiMap instance by name + * Returns Set based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -271,6 +297,29 @@ public interface RedissonClient { */ RSetMultimap getSetMultimap(String name, Codec codec); + /** + * Returns Set based Multimap instance by name. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.

+ * + * @param name + * @return + */ + RSetMultimapCache getSetMultimapCache(String name); + + /** + * Returns Set based Multimap instance by name + * using provided codec for both map keys and values. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.

+ * + * @param name + * @return + */ + RSetMultimapCache getSetMultimapCache(String name, Codec codec); + /** * Returns semaphore instance by name * @@ -537,6 +586,13 @@ public interface RedissonClient { */ RScript getScript(); + /** + * Returns object for remote operations + * + * @return + */ + RRemoteService getRemoteSerivce(); + /** * Return batch object which executes group of * command in pipeline. diff --git a/src/main/java/org/redisson/RedissonListMultimap.java b/src/main/java/org/redisson/RedissonListMultimap.java index 3eb98f9ea..d2e9db44e 100644 --- a/src/main/java/org/redisson/RedissonListMultimap.java +++ b/src/main/java/org/redisson/RedissonListMultimap.java @@ -74,7 +74,7 @@ public class RedissonListMultimap extends RedissonMultimap implement String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, LLEN_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -99,7 +99,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -126,7 +126,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Collections.singletonList(setName), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -148,7 +148,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 1; ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -167,7 +167,7 @@ public class RedissonListMultimap extends RedissonMultimap implement + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -189,7 +189,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -203,7 +203,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return new RedissonList(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, setName, 0, -1); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -242,7 +242,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -272,7 +272,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java new file mode 100644 index 000000000..2ba95e527 --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -0,0 +1,232 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RListMultimapCache; + +import io.netty.util.concurrent.Future; + +/** + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class RedissonListMultimapCache extends RedissonListMultimap implements RListMultimapCache { + + private final RedissonMultimapCache baseCache; + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); + } + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); + } + + public Future containsKeyAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value ~= false then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', ARGV[3]) > 0 and 1 or 0;" + + "end;" + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, valuesName); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + String getTimeoutSetName() { + return "redisson_list_multimap_ttl{" + getName() + "}"; + } + + + public Future containsValueAsync(Object value) { + try { + byte[] valueState = codec.getMapValueEncoder().encode(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 expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[2]) then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + + "local items = redis.call('lrange', name, 0, -1) " + + "for i=1,#items do " + + "if items[i] == ARGV[1] then " + + "return 1; " + + "end; " + + "end; " + + + "end; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public Future containsEntryAsync(Object key, Object value) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + byte[] valueState = codec.getMapValueEncoder().encode(value); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "local items = redis.call('lrange', KEYS[1], 0, -1); " + + "for i=0, #items do " + + "if items[i] == ARGV[3] then " + + "return 1; " + + "end; " + + "end; " + + "end; " + + "return 0; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public List get(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return new RedissonListMultimapValues(codec, commandExecutor, valuesName, getTimeoutSetName(), key); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public Future> getAllAsync(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return redis.call('lrange', KEYS[1], 0, -1); " + + "end; " + + "return {}; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + + } + + public Future> removeAllAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, + "redis.call('hdel', KEYS[1], ARGV[1]); " + + "local members = redis.call('lrange', KEYS[2], 0, -1); " + + "redis.call('del', KEYS[2]); " + + "redis.call('zrem', KEYS[3], ARGV[1]); " + + "return members; ", + Arrays.asList(getName(), valuesName, getTimeoutSetName()), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) { + return get(expireKeyAsync(key, timeToLive, timeUnit)); + } + + @Override + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); + } + +} diff --git a/src/main/java/org/redisson/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java new file mode 100644 index 000000000..1c6ed6d5f --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -0,0 +1,688 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import static org.redisson.client.protocol.RedisCommands.EVAL_OBJECT; +import static org.redisson.client.protocol.RedisCommands.LPOP; +import static org.redisson.client.protocol.RedisCommands.LPUSH_BOOLEAN; +import static org.redisson.client.protocol.RedisCommands.RPUSH_BOOLEAN; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanNumberReplayConvertor; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.convertor.Convertor; +import org.redisson.client.protocol.convertor.IntegerReplayConvertor; +import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RList; + +import io.netty.util.concurrent.Future; + +/** + * List based Multimap Cache values holder + * + * @author Nikita Koksharov + * + * @param the type of elements held in this collection + */ +public class RedissonListMultimapValues extends RedissonExpirable implements RList { + + private static final RedisCommand LAST_INDEX = new RedisCommand("EVAL", new IntegerReplayConvertor(), 4, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_SIZE = new RedisCommand("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_GET = new RedisCommand("EVAL", 7, ValueType.MAP_KEY); + private static final RedisCommand> EVAL_READALL = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); + + + public static final RedisCommand EVAL_BOOLEAN_ARGS2 = new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS); + + private final Object key; + private final String timeoutSetName; + + public RedissonListMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) { + super(codec, commandExecutor, name); + this.timeoutSetName = timeoutSetName; + this.key = key; + } + + @Override + public int size() { + return get(sizeAsync()); + } + + public Future sizeAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return get(containsAsync(o)); + } + + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public Object[] toArray() { + List list = readAll(); + return list.toArray(); + } + + @Override + public List readAll() { + return get(readAllAsync()); + } + + @Override + public Future> readAllAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {};" + + "end; " + + "return redis.call('lrange', KEYS[2], 0, -1);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public T[] toArray(T[] a) { + List list = readAll(); + return list.toArray(a); + } + + @Override + public boolean add(V e) { + return get(addAsync(e)); + } + + @Override + public Future addAsync(V e) { + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, getName(), e); + } + + @Override + public boolean remove(Object o) { + return get(removeAsync(o)); + } + + @Override + public Future removeAsync(Object o) { + return removeAsync(o, 1); + } + + protected Future removeAsync(Object o, int count) { + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('lrem', KEYS[2], ARGV[2], ARGV[4]) > 0 and 1 or 0;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), count, key, o); + } + + protected boolean remove(Object o, int count) { + return get(removeAsync(o, count)); + } + + @Override + public Future containsAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "local items = redis.call('lrange', KEYS[2], 0, -1);" + + "for i = 0, #items, 1 do " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == items[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return table.getn(ARGV) == 2 and 1 or 0; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + + } + + @Override + public boolean containsAll(Collection c) { + return get(containsAllAsync(c)); + } + + @Override + public boolean addAll(Collection c) { + return get(addAllAsync(c)); + } + + @Override + public Future addAllAsync(final Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } + + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, args.toArray()); + } + + public Future addAllAsync(int index, Collection coll) { + if (index < 0) { + throw new IndexOutOfBoundsException("index: " + index); + } + + if (coll.isEmpty()) { + return newSucceededFuture(false); + } + + if (index == 0) { // prepend elements to list + List elements = new ArrayList(coll); + Collections.reverse(elements); + elements.add(0, getName()); + + return commandExecutor.writeAsync(getName(), codec, LPUSH_BOOLEAN, elements.toArray()); + } + + List args = new ArrayList(coll.size() + 1); + args.add(index); + args.addAll(coll); + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_BOOLEAN_ARGS2, + "local ind = table.remove(ARGV, 1); " + // index is the first parameter + "local size = redis.call('llen', KEYS[1]); " + + "assert(tonumber(ind) <= size, 'index: ' .. ind .. ' but current size: ' .. size); " + + "local tail = redis.call('lrange', KEYS[1], ind, -1); " + + "redis.call('ltrim', KEYS[1], 0, ind - 1); " + + "for i=1, #ARGV, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); " + + "end " + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return 1;", + Collections.singletonList(getName()), args.toArray()); + } + + @Override + public boolean addAll(int index, Collection coll) { + return get(addAllAsync(index, coll)); + } + + @Override + public Future removeAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local v = 0 " + + "for i = 2, #ARGV, 1 do " + + "if redis.call('lrem', KEYS[2], 0, ARGV[i]) == 1 " + + "then v = 1 end " + +"end " + + "return v ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean removeAll(Collection c) { + return get(removeAllAsync(c)); + } + + @Override + public boolean retainAll(Collection c) { + return get(retainAllAsync(c)); + } + + @Override + public Future retainAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local changed = 0; " + + "local s = redis.call('lrange', KEYS[2], 0, -1); " + + "local i = 0; " + + "while i <= #s do " + + "local element = s[i]; " + + "local isInAgrs = false; " + + "for j = 2, #ARGV, 1 do " + + "if ARGV[j] == element then " + + "isInAgrs = true; " + + "break; " + + "end; " + + "end; " + + "if isInAgrs == false then " + + "redis.call('lrem', KEYS[2], 0, element); " + + "changed = 1; " + + "end; " + + "i = i + 1; " + + "end; " + + "return changed; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + + @Override + public void clear() { + delete(); + } + + @Override + public Future getAsync(int index) { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_GET, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil;" + + "end; " + + "return redis.call('lindex', KEYS[2], ARGV[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), index, key); + } + + @Override + public V get(int index) { + checkIndex(index); + return getValue(index); + } + + V getValue(int index) { + return get(getAsync(index)); + } + + private void checkIndex(int index) { + int size = size(); + if (!isInRange(index, size)) + throw new IndexOutOfBoundsException("index: " + index + " but current size: "+ size); + } + + private boolean isInRange(int index, int size) { + return index >= 0 && index < size; + } + + @Override + public V set(int index, V element) { + checkIndex(index); + return get(setAsync(index, element)); + } + + @Override + public Future setAsync(int index, V element) { + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", 5), + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "redis.call('lset', KEYS[1], ARGV[1], ARGV[2]); " + + "return v", + Collections.singletonList(getName()), index, element); + } + + @Override + public void fastSet(int index, V element) { + get(fastSetAsync(index, element)); + } + + @Override + public Future fastSetAsync(int index, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LSET, getName(), index, element); + } + + @Override + public void add(int index, V element) { + addAll(index, Collections.singleton(element)); + } + + @Override + public V remove(int index) { + checkIndex(index); + + if (index == 0) { + Future f = commandExecutor.writeAsync(getName(), codec, LPOP, getName()); + return get(f); + } + + Future f = commandExecutor.evalWriteAsync(getName(), codec, EVAL_OBJECT, + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "local tail = redis.call('lrange', KEYS[1], ARGV[1] + 1, -1);" + + "redis.call('ltrim', KEYS[1], 0, ARGV[1] - 1);" + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return v", + Collections.singletonList(getName()), index); + return get(f); + } + + @Override + public int indexOf(Object o) { + return get(indexOfAsync(o)); + } + + @Override + public Future containsAsync(Object o) { + return indexOfAsync(o, new BooleanNumberReplayConvertor(-1L)); + } + + private Future indexOfAsync(Object o, Convertor convertor) { + return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", convertor, 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)), + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[2], 0, -1); " + + "for i=1,#items do " + + "if items[i] == ARGV[3] then " + + "return i - 1; " + + "end; " + + "end; " + + "return -1;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public Future indexOfAsync(Object o) { + return indexOfAsync(o, new IntegerReplayConvertor()); + } + + @Override + public int lastIndexOf(Object o) { + return get(lastIndexOfAsync(o)); + } + + @Override + public Future lastIndexOfAsync(Object o) { + return commandExecutor.evalReadAsync(getName(), codec, LAST_INDEX, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[1], 0, -1) " + + "for i = #items, 0, -1 do " + + "if items[i] == ARGV[1] then " + + "return i - 1 " + + "end " + + "end " + + "return -1", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public void trim(int fromIndex, int toIndex) { + get(trimAsync(fromIndex, toIndex)); + } + + @Override + public Future trimAsync(int fromIndex, int toIndex) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(final int ind) { + return new ListIterator() { + + private V prevCurrentValue; + private V nextCurrentValue; + private V currentValueHasRead; + private int currentIndex = ind - 1; + private boolean hasBeenModified = true; + + @Override + public boolean hasNext() { + V val = RedissonListMultimapValues.this.getValue(currentIndex+1); + if (val != null) { + nextCurrentValue = val; + } + return val != null; + } + + @Override + public V next() { + if (nextCurrentValue == null && !hasNext()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex++; + currentValueHasRead = nextCurrentValue; + nextCurrentValue = null; + hasBeenModified = false; + return currentValueHasRead; + } + + @Override + public void remove() { + if (currentValueHasRead == null) { + throw new IllegalStateException("Neither next nor previous have been called"); + } + if (hasBeenModified) { + throw new IllegalStateException("Element been already deleted"); + } + RedissonListMultimapValues.this.remove(currentIndex); + currentIndex--; + hasBeenModified = true; + currentValueHasRead = null; + } + + @Override + public boolean hasPrevious() { + if (currentIndex < 0) { + return false; + } + V val = RedissonListMultimapValues.this.getValue(currentIndex); + if (val != null) { + prevCurrentValue = val; + } + return val != null; + } + + @Override + public V previous() { + if (prevCurrentValue == null && !hasPrevious()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex--; + hasBeenModified = false; + currentValueHasRead = prevCurrentValue; + prevCurrentValue = null; + return currentValueHasRead; + } + + @Override + public int nextIndex() { + return currentIndex + 1; + } + + @Override + public int previousIndex() { + return currentIndex; + } + + @Override + public void set(V e) { + if (hasBeenModified) { + throw new IllegalStateException(); + } + + RedissonListMultimapValues.this.fastSet(currentIndex, e); + } + + @Override + public void add(V e) { + RedissonListMultimapValues.this.add(currentIndex+1, e); + currentIndex++; + hasBeenModified = true; + } + }; + } + + @Override + public RList subList(int fromIndex, int toIndex) { + int size = size(); + if (fromIndex < 0 || toIndex > size) { + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " toIndex: " + toIndex + " size: " + size); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex: " + fromIndex + " toIndex: " + toIndex); + } + + return new RedissonSubList(codec, commandExecutor, getName(), fromIndex, toIndex); + } + + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + V e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + Iterator e1 = iterator(); + Iterator e2 = ((List) o).iterator(); + while (e1.hasNext() && e2.hasNext()) { + V o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (V e : this) { + hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); + } + return hashCode; + } + +} diff --git a/src/main/java/org/redisson/RedissonMapCache.java b/src/main/java/org/redisson/RedissonMapCache.java index 8900fd119..d529c2b03 100644 --- a/src/main/java/org/redisson/RedissonMapCache.java +++ b/src/main/java/org/redisson/RedissonMapCache.java @@ -655,7 +655,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 475660b82..a072520a5 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -20,15 +20,18 @@ import java.net.InetSocketAddress; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import java.util.Set; import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; @@ -152,10 +155,12 @@ public abstract class RedissonMultimap extends RedissonExpirable implement return new EntrySet(); } + @Override public long fastRemove(K ... keys) { return get(fastRemoveAsync(keys)); } + @Override public Future fastRemoveAsync(K ... keys) { if (keys == null || keys.length == 0) { return newSucceededFuture(0L); @@ -184,6 +189,69 @@ public abstract class RedissonMultimap extends RedissonExpirable implement throw new RuntimeException(e); } } + + @Override + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(getName())); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timeUnit.toMillis(timeToLive)); + } + + @Override + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timestamp); + } + + @Override + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(getName())); + } + MapScanResult scanIterator(InetSocketAddress client, long startPos) { Future> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos); diff --git a/src/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java new file mode 100644 index 000000000..765ab8021 --- /dev/null +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -0,0 +1,129 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; + +import io.netty.util.concurrent.Future; + +public class RedissonMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + private final CommandAsyncExecutor commandExecutor; + private final String name; + private final Codec codec; + private final String timeoutSetName; + + public RedissonMultimapCache(CommandAsyncExecutor commandExecutor, String name, Codec codec, String timeoutSetName) { + this.commandExecutor = commandExecutor; + this.name = name; + this.codec = codec; + this.timeoutSetName = timeoutSetName; + } + + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(name, codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(name, timeoutSetName), ttlTimeout, key); + } + + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1], KEYS[2]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(name, timeoutSetName)); + } + + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpire', KEYS[2], ARGV[1]); " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timeUnit.toMillis(timeToLive)); + } + + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpireat', KEYS[2], ARGV[1]); " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timestamp); + } + + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "redis.call('persist', KEYS[2]); " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(name, timeoutSetName)); + } + + +} diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java new file mode 100644 index 000000000..b80124260 --- /dev/null +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -0,0 +1,165 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.redisson.client.RedisException; +import org.redisson.client.RedisTimeoutException; +import org.redisson.core.MessageListener; +import org.redisson.core.RBlockingQueue; +import org.redisson.core.RRemoteService; +import org.redisson.core.RTopic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.buffer.ByteBufUtil; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThreadLocalRandom; + +public class RedissonRemoteService implements RRemoteService { + + private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); + + private final Map beans = PlatformDependent.newConcurrentHashMap(); + + private final Redisson redisson; + + public RedissonRemoteService(Redisson redisson) { + this.redisson = redisson; + } + + @Override + public void register(Class remoteInterface, T object) { + register(remoteInterface, object, 1); + } + + @Override + public void register(Class remoteInterface, T object, int executorsAmount) { + if (executorsAmount < 1) { + throw new IllegalArgumentException("executorsAmount can't be lower than 1"); + } + for (Method method : remoteInterface.getMethods()) { + RemoteServiceMethod value = new RemoteServiceMethod(method, object); + RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName()); + if (beans.put(key, value) != null) { + return; + } + } + + for (int i = 0; i < executorsAmount; i++) { + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + subscribe(remoteInterface, requestQueue); + } + } + + private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue) { + Future take = requestQueue.takeAsync(); + take.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + return; + } + + RemoteServiceRequest request = future.getNow(); + RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); + RTopic topic = redisson.getTopic(responseName); + RemoteServiceResponse response; + try { + Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); + response = new RemoteServiceResponse(result); + } catch (Exception e) { + response = new RemoteServiceResponse(e.getCause()); + log.error("Can't execute: " + request, e); + } + + long clients = topic.publish(response); + if (clients == 0) { + log.error("None of clients has not received a response: {} for request: {}", response, request); + } + + subscribe(remoteInterface, requestQueue); + } + }); + } + + @Override + public T get(Class remoteInterface) { + return get(remoteInterface, -1, null); + } + + @Override + public T get(final Class remoteInterface, final int timeout, final TimeUnit timeUnit) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String requestId = generateRequestId(); + + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args); + requestQueue.add(request); + + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; + final RTopic topic = redisson.getTopic(responseName); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference response = new AtomicReference(); + int listenerId = topic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, RemoteServiceResponse msg) { + response.set(msg); + latch.countDown(); + } + }); + + if (timeout == -1) { + latch.await(); + } else { + if (!latch.await(timeout, timeUnit)) { + topic.removeListener(listenerId); + throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request); + } + } + topic.removeListener(listenerId); + RemoteServiceResponse msg = response.get(); + if (msg.getError() != null) { + throw msg.getError(); + } + return msg.getResult(); + } + }; + return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); + } + + private String generateRequestId() { + byte[] id = new byte[16]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + return ByteBufUtil.hexDump(id); + } + +} diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index e6751cc78..e4755f679 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -480,4 +480,14 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count); } + @Override + public Future revRankAsync(V o) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANK_INT, getName(), o); + } + + @Override + public int revRank(V o) { + return get(revRankAsync(o)); + } + } diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 42726bafb..97f8526a7 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -39,6 +39,10 @@ import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSetCache; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import net.openhft.hashing.LongHashFunction; @@ -114,10 +118,12 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< long h1 = LongHashFunction.farmUo().hashBytes(objectState); long h2 = LongHashFunction.xx_r39().hashBytes(objectState); - return ByteBuffer.allocate((2 * Long.SIZE) / Byte.SIZE) - .putLong(h1) - .putLong(h2) - .array(); + ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2); + try { + return buf.array(); + } finally { + buf.release(); + } } String getTimeoutSetName() { @@ -468,7 +474,7 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/RedissonSetMultimap.java b/src/main/java/org/redisson/RedissonSetMultimap.java index 35da45b53..3989bb762 100644 --- a/src/main/java/org/redisson/RedissonSetMultimap.java +++ b/src/main/java/org/redisson/RedissonSetMultimap.java @@ -77,7 +77,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -98,7 +98,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -111,7 +111,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -127,7 +127,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], ARGV[3]); ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -146,7 +146,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -168,7 +168,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -181,7 +181,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return new RedissonSet(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -198,7 +198,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -268,7 +268,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java new file mode 100644 index 000000000..4254d3ed6 --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -0,0 +1,224 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RSetMultimapCache; + +import io.netty.util.concurrent.Future; + +/** + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class RedissonSetMultimapCache extends RedissonSetMultimap implements RSetMultimapCache { + + private final RedissonMultimapCache baseCache; + + RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); + } + + RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); + } + + public Future containsKeyAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String setName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value ~= false then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('scard', ARGV[3]) > 0 and 1 or 0;" + + "end;" + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, setName); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + String getTimeoutSetName() { + return "redisson_set_multimap_ttl{" + getName() + "}"; + } + + + public Future containsValueAsync(Object value) { + try { + byte[] valueState = codec.getMapValueEncoder().encode(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 expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[2]) then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "if redis.call('sismember', name, ARGV[1]) == 1 then " + + "return 1; " + + "end;" + + "end; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Future containsEntryAsync(Object key, Object value) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + byte[] valueState = codec.getMapValueEncoder().encode(value); + + String setName = getValuesName(keyHash); + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "if redis.call('sismember', KEYS[1], ARGV[3]) == 1 then " + + "return 1; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Set get(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String setName = getValuesName(keyHash); + + return new RedissonSetMultimapValues(codec, commandExecutor, setName, getTimeoutSetName(), key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Future> getAllAsync(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String setName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_SET, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return redis.call('smembers', KEYS[1]); " + + "end; " + + "return {}; ", + Arrays.asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public Future> removeAllAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(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]); " + + "redis.call('zrem', KEYS[3], ARGV[1]); " + + "return members; ", + Arrays.asList(getName(), setName, getTimeoutSetName()), keyState); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) { + return get(expireKeyAsync(key, timeToLive, timeUnit)); + } + + @Override + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); + } + +} diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java new file mode 100644 index 000000000..215c82cd4 --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -0,0 +1,446 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.convertor.IntegerReplayConvertor; +import org.redisson.client.protocol.decoder.ListScanResult; +import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder; +import org.redisson.client.protocol.decoder.NestedMultiDecoder; +import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; +import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RSet; + +import io.netty.util.concurrent.Future; + +/** + * Set based Multimap Cache values holder + * + * @author Nikita Koksharov + * + * @param value + */ +public class RedissonSetMultimapValues extends RedissonExpirable implements RSet { + + private static final RedisCommand> EVAL_SSCAN = new RedisCommand>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder(), new ListScanResultReplayDecoder()), 7, ValueType.MAP_KEY, ValueType.OBJECT); + private static final RedisCommand EVAL_SIZE = new RedisCommand("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); + private static final RedisCommand> EVAL_READALL = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); + + private final Object key; + private final String timeoutSetName; + + public RedissonSetMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) { + super(codec, commandExecutor, name); + this.timeoutSetName = timeoutSetName; + this.key = key; + } + + @Override + public int size() { + return get(sizeAsync()); + } + + @Override + public Future sizeAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('scard', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return get(containsAsync(o)); + } + + @Override + public Future containsAsync(Object o) { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('sismember', KEYS[2], ARGV[3]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + private ListScanResult scanIterator(InetSocketAddress client, long startPos) { + Future> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_SSCAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {0, {}};" + + "end;" + + + "return redis.call('sscan', KEYS[2], ARGV[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), startPos, key); + return get(f); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private List firstValues; + private Iterator iter; + private InetSocketAddress client; + private long nextIterPos; + + private boolean currentElementRemoved; + private boolean removeExecuted; + private V value; + + @Override + public boolean hasNext() { + if (iter == null || !iter.hasNext()) { + if (nextIterPos == -1) { + return false; + } + long prevIterPos = nextIterPos; + ListScanResult res = scanIterator(client, nextIterPos); + client = res.getRedisClient(); + if (nextIterPos == 0 && firstValues == null) { + firstValues = res.getValues(); + } else if (res.getValues().equals(firstValues)) { + return false; + } + iter = res.getValues().iterator(); + nextIterPos = res.getPos(); + if (prevIterPos == nextIterPos && !removeExecuted) { + nextIterPos = -1; + } + } + return iter.hasNext(); + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException("No such element at index"); + } + + value = iter.next(); + currentElementRemoved = false; + return value; + } + + @Override + public void remove() { + if (currentElementRemoved) { + throw new IllegalStateException("Element been already deleted"); + } + if (iter == null) { + throw new IllegalStateException(); + } + + iter.remove(); + RedissonSetMultimapValues.this.remove(value); + currentElementRemoved = true; + removeExecuted = true; + } + + }; + } + + @Override + public Future> readAllAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {};" + + "end; " + + "return redis.call('smembers', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public Set readAll() { + return get(readAllAsync()); + } + + @Override + public Object[] toArray() { + Set res = (Set) get(readAllAsync()); + return res.toArray(); + } + + @Override + public T[] toArray(T[] a) { + Set res = (Set) get(readAllAsync()); + return res.toArray(a); + } + + @Override + public boolean add(V e) { + return get(addAsync(e)); + } + + @Override + public Future addAsync(V e) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_SINGLE, getName(), e); + } + + @Override + public V removeRandom() { + return get(removeRandomAsync()); + } + + @Override + public Future removeRandomAsync() { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP_SINGLE, getName()); + } + + @Override + public Future removeAsync(Object o) { + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('srem', KEYS[2], ARGV[3]) > 0 and 1 or 0;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public boolean remove(Object value) { + return get(removeAsync((V)value)); + } + + @Override + public Future moveAsync(String destination, V member) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SMOVE, getName(), destination, member); + } + + @Override + public boolean move(String destination, V member) { + return get(moveAsync(destination, member)); + } + + @Override + public boolean containsAll(Collection c) { + return get(containsAllAsync(c)); + } + + @Override + public Future containsAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "local s = redis.call('smembers', KEYS[2]);" + + "for i = 0, table.getn(s), 1 do " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == s[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return table.getn(ARGV) == 2 and 1 or 0; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean addAll(Collection c) { + if (c.isEmpty()) { + return false; + } + + return get(addAllAsync(c)); + } + + @Override + public Future addAllAsync(Collection c) { + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_BOOL, args.toArray()); + } + + @Override + public boolean retainAll(Collection c) { + return get(retainAllAsync(c)); + } + + @Override + public Future retainAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local changed = 0 " + + "local s = redis.call('smembers', KEYS[2]) " + + "local i = 0 " + + "while i <= table.getn(s) do " + + "local element = s[i] " + + "local isInAgrs = false " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == element then " + + "isInAgrs = true " + + "break " + + "end " + + "end " + + "if isInAgrs == false then " + + "redis.call('SREM', KEYS[2], element) " + + "changed = 1 " + + "end " + + "i = i + 1 " + + "end " + + "return changed ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public Future removeAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local v = 0 " + + "for i = 2, table.getn(ARGV), 1 do " + + "if redis.call('srem', KEYS[2], ARGV[i]) == 1 " + + "then v = 1 end " + +"end " + + "return v ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean removeAll(Collection c) { + return get(removeAllAsync(c)); + } + + @Override + public int union(String... names) { + return get(unionAsync(names)); + } + + @Override + public Future unionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNIONSTORE_INT, args.toArray()); + } + + @Override + public Set readUnion(String... names) { + return get(readUnionAsync(names)); + } + + @Override + public Future> readUnionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNION, args.toArray()); + } + + @Override + public void clear() { + delete(); + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceKey.java b/src/main/java/org/redisson/RemoteServiceKey.java new file mode 100644 index 000000000..5cadb3a5a --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceKey.java @@ -0,0 +1,68 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +public class RemoteServiceKey { + + private final Class serviceInterface; + private final String methodName; + + public RemoteServiceKey(Class serviceInterface, String methodName) { + super(); + this.serviceInterface = serviceInterface; + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + public Class getServiceInterface() { + return serviceInterface; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((methodName == null) ? 0 : methodName.hashCode()); + result = prime * result + ((serviceInterface == null) ? 0 : serviceInterface.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RemoteServiceKey other = (RemoteServiceKey) obj; + if (methodName == null) { + if (other.methodName != null) + return false; + } else if (!methodName.equals(other.methodName)) + return false; + if (serviceInterface == null) { + if (other.serviceInterface != null) + return false; + } else if (!serviceInterface.equals(other.serviceInterface)) + return false; + return true; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceMethod.java b/src/main/java/org/redisson/RemoteServiceMethod.java new file mode 100644 index 000000000..153c82d19 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceMethod.java @@ -0,0 +1,39 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.lang.reflect.Method; + +public class RemoteServiceMethod { + + private final Object bean; + private final Method method; + + public RemoteServiceMethod(Method method, Object bean) { + super(); + this.method = method; + this.bean = bean; + } + + public Object getBean() { + return bean; + } + + public Method getMethod() { + return method; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/RemoteServiceRequest.java new file mode 100644 index 000000000..434784456 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceRequest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +import java.util.Arrays; + +public class RemoteServiceRequest { + + private String requestId; + private String methodName; + private Object[] args; + + public RemoteServiceRequest() { + } + + public RemoteServiceRequest(String requestId, String methodName, Object[] args) { + super(); + this.requestId = requestId; + this.methodName = methodName; + this.args = args; + } + + public String getRequestId() { + return requestId; + } + + public Object[] getArgs() { + return args; + } + + public String getMethodName() { + return methodName; + } + + @Override + public String toString() { + return "RemoteServiceRequest[requestId=" + requestId + ", methodName=" + methodName + ", args=" + + Arrays.toString(args) + "]"; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/RemoteServiceResponse.java new file mode 100644 index 000000000..09ebc5999 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceResponse.java @@ -0,0 +1,47 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson; + +public class RemoteServiceResponse { + + private Object result; + private Throwable error; + + public RemoteServiceResponse() { + } + + public RemoteServiceResponse(Object result) { + this.result = result; + } + + public RemoteServiceResponse(Throwable error) { + this.error = error; + } + + public Throwable getError() { + return error; + } + + public Object getResult() { + return result; + } + + @Override + public String toString() { + return "RemoteServiceResponse [result=" + result + ", error=" + error + "]"; + } + +} diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 85ff35041..53f1c9383 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -174,8 +174,8 @@ public class RedisConnection implements RedisCommands { return closed; } - public void forceReconnect() { - channel.close(); + public ChannelFuture forceReconnectAsync() { + return channel.close(); } /** diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index 4ef080b03..d8ce3d733 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -84,12 +84,12 @@ public class CommandDecoder extends ReplayingDecoder { currentDecoder = StringCodec.INSTANCE.getValueDecoder(); } - if (log.isTraceEnabled()) { - log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); - } - if (state() == null) { state(new State()); + + if (log.isTraceEnabled()) { + log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); + } } state().setDecoderState(null); @@ -100,8 +100,10 @@ public class CommandDecoder extends ReplayingDecoder { try { // if (state().getSize() > 0) { // List respParts = new ArrayList(); -// respParts.addAll(state().getRespParts()); -// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts); +// if (state().getRespParts() != null) { +// respParts = state().getRespParts(); +// } +// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts, true); // } else { decode(in, cmd, null, ctx.channel(), currentDecoder); // } @@ -137,7 +139,11 @@ public class CommandDecoder extends ReplayingDecoder { cmd.getPromise().tryFailure(e); } if (!cmd.getPromise().isSuccess()) { - error = (RedisException) cmd.getPromise().cause(); + if (!(cmd.getPromise().cause() instanceof RedisMovedException + || cmd.getPromise().cause() instanceof RedisAskException + || cmd.getPromise().cause() instanceof RedisLoadingException)) { + error = (RedisException) cmd.getPromise().cause(); + } } } @@ -149,7 +155,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { if (!promise.trySuccess(null) && promise.cause() instanceof RedisTimeoutException) { - // TODO try increase timeout log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); } } @@ -214,21 +219,27 @@ public class CommandDecoder extends ReplayingDecoder { handleResult(data, parts, result, false, channel); } else if (code == '*') { long size = readLong(in); -// state().setSizeOnce(size); - List respParts = new ArrayList(); + boolean top = false; +// if (state().trySetSize(size)) { +// state().setRespParts(respParts); +// top = true; +// } - decodeMulti(in, data, parts, channel, currentDecoder, size, respParts); + decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top); } else { throw new IllegalStateException("Can't decode replay " + (char)code); } } private void decodeMulti(ByteBuf in, CommandData data, List parts, - Channel channel, Decoder currentDecoder, long size, List respParts) + Channel channel, Decoder currentDecoder, long size, List respParts, boolean top) throws IOException { for (int i = respParts.size(); i < size; i++) { decode(in, data, respParts, channel, currentDecoder); +// if (top) { +// checkpoint(); +// } } MultiDecoder decoder = messageDecoder(data, respParts, channel); @@ -250,9 +261,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { handleMultiResult(data, parts, channel, result); -// if (parts != null && !decoder.isApplicable(parts.size(), state())) { -// state().setRespParts(parts); -// } } } diff --git a/src/main/java/org/redisson/client/handler/State.java b/src/main/java/org/redisson/client/handler/State.java index 8eed56b08..d9d1ba579 100644 --- a/src/main/java/org/redisson/client/handler/State.java +++ b/src/main/java/org/redisson/client/handler/State.java @@ -29,11 +29,12 @@ public class State { super(); } - public void setSizeOnce(long size) { + public boolean trySetSize(long size) { if (this.size != 0) { - return; + return false; } this.size = size; + return true; } public long getSize() { return size; diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index f4b741800..9528685ce 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -83,6 +83,7 @@ public interface RedisCommands { RedisCommand ZSCORE_CONTAINS = new RedisCommand("ZSCORE", new BooleanNotNullReplayConvertor(), 2); RedisStrictCommand ZSCORE = new RedisStrictCommand("ZSCORE", new DoubleReplayConvertor(), 2); RedisCommand ZRANK_INT = new RedisCommand("ZRANK", new IntegerReplayConvertor(), 2); + RedisCommand ZREVRANK_INT = new RedisCommand("ZREVRANK", new IntegerReplayConvertor(), 2); RedisStrictCommand ZRANK = new RedisStrictCommand("ZRANK", 2); RedisCommand ZRANGE_SINGLE = new RedisCommand("ZRANGE", new ObjectFirstResultReplayDecoder()); RedisCommand> ZRANGE = new RedisCommand>("ZRANGE", new ObjectListReplayDecoder()); @@ -207,6 +208,7 @@ public interface RedisCommands { RedisStrictCommand DEL = new RedisStrictCommand("DEL"); RedisStrictCommand DBSIZE = new RedisStrictCommand("DBSIZE"); RedisStrictCommand DEL_BOOL = new RedisStrictCommand("DEL", new BooleanReplayConvertor()); + RedisStrictCommand DEL_OBJECTS = new RedisStrictCommand("DEL", new BooleanAmountReplayConvertor()); RedisStrictCommand DEL_VOID = new RedisStrictCommand("DEL", new VoidReplayConvertor()); RedisCommand GET = new RedisCommand("GET"); diff --git a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java index 22729473c..d0bc0cf36 100644 --- a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java +++ b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java @@ -96,11 +96,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } if (lastPartitions.isEmpty()) { - try { - group.shutdownGracefully().await(); - } catch (Exception e) { - // skip it - } + stopThreads(); throw new RedisConnectionException("Can't connect to servers!", lastException); } diff --git a/src/main/java/org/redisson/codec/JsonJacksonCodec.java b/src/main/java/org/redisson/codec/JsonJacksonCodec.java index 711e1156f..ab518b0ed 100755 --- a/src/main/java/org/redisson/codec/JsonJacksonCodec.java +++ b/src/main/java/org/redisson/codec/JsonJacksonCodec.java @@ -23,8 +23,15 @@ import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.Encoder; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIdentityReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MapperFeature; @@ -32,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import io.netty.buffer.ByteBuf; @@ -49,6 +57,12 @@ public class JsonJacksonCodec implements Codec { public static final JsonJacksonCodec INSTANCE = new JsonJacksonCodec(); + @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id") + @JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.PUBLIC_ONLY, isGetterVisibility = Visibility.PUBLIC_ONLY) + public static class ThrowableMixIn { + + } + private final ObjectMapper mapObjectMapper = initObjectMapper(); protected ObjectMapper initObjectMapper() { @@ -111,6 +125,7 @@ public class JsonJacksonCodec implements Codec { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); } @Override diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d8c44d2fa..faba6d1fc 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -462,6 +462,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (skipTimeout.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); + handleBlockingOperations(details, connection); if (popTimeout == 0) { return; } @@ -482,6 +483,29 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.setTimeout(timeout); } + private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection) { + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + details.getMainPromise().cancel(true); + } + }; + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + connectionManager.getShutdownPromise().removeListener(listener); + if (!future.isCancelled()) { + return; + } + // cancel handling for commands from skipTimeout collection + if (details.getAttemptPromise().cancel(true)) { + connection.forceReconnectAsync(); + } + } + }); + connectionManager.getShutdownPromise().addListener(listener); + } + private void checkConnectionFuture(final NodeSource source, final AsyncDetails details) { if (details.getAttemptPromise().isDone() || details.getMainPromise().isCancelled() || details.getConnectionFuture().isCancelled()) { diff --git a/src/main/java/org/redisson/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 48b2e2c98..21a275ddb 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -107,5 +107,7 @@ public interface ConnectionManager { Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); InfinitySemaphoreLatch getShutdownLatch(); + + Future getShutdownPromise(); } diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 5b6698058..065e66f75 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -122,6 +122,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected final Map entries = PlatformDependent.newConcurrentHashMap(); + private final Promise shutdownPromise; + private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch(); private final Set clients = Collections.newSetFromMap(PlatformDependent.newConcurrentHashMap()); @@ -156,6 +158,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this.socketChannelClass = NioSocketChannel.class; } this.codec = cfg.getCodec(); + this.shutdownPromise = group.next().newPromise(); this.isClusterMode = cfg.isClusterConfig(); } @@ -202,11 +205,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { try { initEntry(config); } catch (RuntimeException e) { - try { - group.shutdownGracefully().await(); - } catch (Exception e1) { - // skip - } + stopThreads(); throw e; } } @@ -678,6 +677,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { + shutdownPromise.trySuccess(true); shutdownLatch.closeAndAwaitUninterruptibly(); for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); @@ -735,10 +735,23 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public InfinitySemaphoreLatch getShutdownLatch() { return shutdownLatch; } + + @Override + public Future getShutdownPromise() { + return shutdownPromise; + } @Override public ConnectionEventsHub getConnectionEventsHub() { return connectionEventsHub; } + protected void stopThreads() { + timer.stop(); + try { + group.shutdownGracefully().await(); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } } diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index b29baa9f9..58998a2dd 100644 --- a/src/main/java/org/redisson/connection/pool/ConnectionPool.java +++ b/src/main/java/org/redisson/connection/pool/ConnectionPool.java @@ -153,7 +153,7 @@ abstract class ConnectionPool { } } - StringBuilder errorMsg = new StringBuilder("Connection pool exhausted! All connections are busy. Try to increase connection pool size."); + StringBuilder errorMsg = new StringBuilder("Publish/Subscribe connection pool exhausted! All connections are busy. Try to increase Publish/Subscribe connection pool size."); // if (!freezed.isEmpty()) { // errorMsg.append(" Disconnected hosts: " + freezed); // } diff --git a/src/main/java/org/redisson/core/RListMultimapCache.java b/src/main/java/org/redisson/core/RListMultimapCache.java new file mode 100644 index 000000000..01d97baa5 --- /dev/null +++ b/src/main/java/org/redisson/core/RListMultimapCache.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson.core; + +public interface RListMultimapCache extends RListMultimap, RMultimapCache { + +} diff --git a/src/main/java/org/redisson/core/RMultimap.java b/src/main/java/org/redisson/core/RMultimap.java index b7c7d3c7a..7f39b53ce 100644 --- a/src/main/java/org/redisson/core/RMultimap.java +++ b/src/main/java/org/redisson/core/RMultimap.java @@ -120,6 +120,7 @@ public interface RMultimap extends RExpirable, RMultimapAsync { *

Once this method returns, {@code key} will not be mapped to any values, * so it will not appear in {@link #keySet()}, {@link #asMap()}, or any other * views. + *

Use {@link #fastRemove()} if values are not needed.

* * @return the values that were removed (possibly empty). The returned * collection may be modifiable, but updating it will have no @@ -186,7 +187,7 @@ public interface RMultimap extends RExpirable, RMultimapAsync { /** * Removes keys from map by one operation * - * Works faster than RMap.remove but not returning + * Works faster than RMultimap.remove but not returning * the value associated with key * * @param keys diff --git a/src/main/java/org/redisson/core/RMultimapCache.java b/src/main/java/org/redisson/core/RMultimapCache.java new file mode 100644 index 000000000..7fc06a11a --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCache.java @@ -0,0 +1,33 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson.core; + +import java.util.concurrent.TimeUnit; + +public interface RMultimapCache extends RMultimap, RMultimapCacheAsync { + + /** + * Set a timeout for key. After the timeout has expired, + * the key and its values will automatically be deleted. + * + * @param key + * @param timeToLive - timeout before key will be deleted + * @param timeUnit - timeout time unit + * @return true if key exists and the timeout was set and false if key not exists + */ + boolean expireKey(K key, long timeToLive, TimeUnit timeUnit); + +} diff --git a/src/main/java/org/redisson/core/RMultimapCacheAsync.java b/src/main/java/org/redisson/core/RMultimapCacheAsync.java new file mode 100644 index 000000000..e910a7405 --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCacheAsync.java @@ -0,0 +1,35 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson.core; + +import java.util.concurrent.TimeUnit; + +import io.netty.util.concurrent.Future; + +public interface RMultimapCacheAsync extends RMultimapAsync { + + /** + * Set a timeout for key in async mode. After the timeout has expired, + * the key and its values will automatically be deleted. + * + * @param key + * @param timeToLive - timeout before key will be deleted + * @param timeUnit - timeout time unit + * @return true if key exists and the timeout was set and false if key not exists + */ + Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit); + +} diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java new file mode 100644 index 000000000..1b75c5dc3 --- /dev/null +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -0,0 +1,58 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson.core; + +import java.util.concurrent.TimeUnit; + +public interface RRemoteService { + + /** + * Register remote service with single executor + * + * @param remoteInterface + * @param object + */ + void register(Class remoteInterface, T object); + + /** + * Register remote service with custom executors amount + * + * @param remoteInterface + * @param object + * @param executorsAmount + */ + void register(Class remoteInterface, T object, int executorsAmount); + + /** + * Get remote service object for remote invocations + * + * @param remoteInterface + * @return + */ + T get(Class remoteInterface); + + /** + * Get remote service object for remote invocations + * with specified invocation timeout + * + * @param remoteInterface + * @param timeout - invocation timeout + * @param timeUnit + * @return + */ + T get(Class remoteInterface, int timeout, TimeUnit timeUnit); + +} diff --git a/src/main/java/org/redisson/core/RScoredSortedSet.java b/src/main/java/org/redisson/core/RScoredSortedSet.java index ddaeef9b9..ce4e6fccf 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSet.java +++ b/src/main/java/org/redisson/core/RScoredSortedSet.java @@ -37,6 +37,8 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< int removeRangeByRank(int startIndex, int endIndex); int rank(V o); + + int revRank(V o); Double getScore(V o); diff --git a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java index fe6ea081a..83e41798e 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java +++ b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java @@ -39,6 +39,8 @@ public interface RScoredSortedSetAsync extends RExpirableAsync { Future removeRangeByRankAsync(int startIndex, int endIndex); Future rankAsync(V o); + + Future revRankAsync(V o); Future getScoreAsync(V o); diff --git a/src/main/java/org/redisson/core/RSetMultimapCache.java b/src/main/java/org/redisson/core/RSetMultimapCache.java new file mode 100644 index 000000000..1029b2c3f --- /dev/null +++ b/src/main/java/org/redisson/core/RSetMultimapCache.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * 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.redisson.core; + +public interface RSetMultimapCache extends RSetMultimap, RMultimapCache { + +} diff --git a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java index 183f9e460..85acad239 100644 --- a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java @@ -361,7 +361,7 @@ public class RedissonMapCacheReactive extends RedissonMapReactive im @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java index 3bb427930..bce238a28 100644 --- a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java @@ -305,7 +305,7 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/test/java/org/redisson/BaseTest.java b/src/test/java/org/redisson/BaseTest.java index e1190e384..48c549c92 100644 --- a/src/test/java/org/redisson/BaseTest.java +++ b/src/test/java/org/redisson/BaseTest.java @@ -1,10 +1,8 @@ package org.redisson; -import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; -import org.redisson.client.codec.StringCodec; -import org.redisson.codec.MsgPackJacksonCodec; public abstract class BaseTest { @@ -43,9 +41,9 @@ public abstract class BaseTest { return Redisson.create(config); } - @After - public void after() { - redisson.flushdb(); + @Before + public void before() { + redisson.getKeys().flushall(); } } diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index dfdf7bdc6..532e9c1b1 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -1,22 +1,635 @@ package org.redisson; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Inet4Address; import java.net.URL; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; public class RedisRunner { - private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; + public enum REDIS_OPTIONS { - public static Process runRedis(String configPath) throws IOException, InterruptedException { + BINARY_PATH, + DAEMONIZE, + PIDFILE, + PORT, + TCP_BACKLOG, + BIND(true), + UNIXSOCKET, + UNIXSOCKETPERM, + TIMEOUT, + TCP_KEEPALIVE, + LOGLEVEL, + LOGFILE, + SYSLOG_ENABLED, + SYSLOG_IDENT, + SYSLOG_FACILITY, + DATABASES, + SAVE(true), + STOP_WRITES_ON_BGSAVE_ERROR, + RDBCOMPRESSION, + RDBCHECKSUM, + DBFILENAME, + DIR, + SLAVEOF, + MASTERAUTH, + SLAVE_SERVE_STALE_DATA, + SLAVE_READ_ONLY, + REPL_DISKLESS_SYNC, + REPL_DISKLESS_SYNC_DELAY, + REPL_PING_SLAVE_PERIOD, + REPL_TIMEOUT, + REPL_DISABLE_TCP_NODELAY, + REPL_BACKLOG_SIZE, + REPL_BACKLOG_TTL, + SLAVE_PRIORITY, + MIN_SLAVES_TO_WRITE, + MIN_SLAVES_MAX_LAG, + REQUREPASS, + RENAME_COMMAND(true), + MAXCLIENTS, + MAXMEMORY, + MAXMEMORY_POLICY, + MAXMEMORY_SAMPLE, + APPENDONLY, + APPENDFILENAME, + APPENDFSYNC, + NO_APPENDFSYNC_ON_REWRITE, + AUTO_AOF_REWRITE_PERCENTAGE, + AUTO_AOF_REWRITE_MIN_SIZE, + AOF_LOAD_TRUNCATED, + LUA_TIME_LIMIT, + CLUSTER_ENABLED, + CLUSTER_CONFIG_FILE, + CLUSTER_NODE_TIMEOUT, + CLUSTER_SLAVE_VALIDITY_FACTOR, + CLUSTER_MIGRATION_BARRIER, + CLUSTER_REQUIRE_FULL_COVERAGE, + SLOWLOG_LOG_SLOWER_THAN, + SLOWLOG_MAX_LEN, + LATENCY_MONITOR_THRESHOLD, + NOFITY_KEYSPACE_EVENTS, + HASH_MAX_ZIPLIST_ENTRIES, + HASH_MAX_ZIPLIST_VALUE, + LIST_MAX_ZIPLIST_ENTRIES, + LIST_MAX_ZIPLIST_VALUE, + SET_MAX_INTSET_ENTRIES, + ZSET_MAX_ZIPLIST_ENTRIES, + ZSET_MAX_ZIPLIST_VALUE, + HLL_SPARSE_MAX_BYTES, + ACTIVEREHASHING, + CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, + CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, + CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, + HZ, + AOF_REWRITE_INCREMENTAL_FSYNC; + + private final boolean allowMutiple; + + private REDIS_OPTIONS() { + this.allowMutiple = false; + } + + private REDIS_OPTIONS(boolean allowMutiple) { + this.allowMutiple = allowMutiple; + } + + public boolean isAllowMultiple() { + return allowMutiple; + } + } + + public enum LOGLEVEL_OPTIONS { + + DEBUG, + VERBOSE, + NOTICE, + WARNING + } + + public enum SYSLOG_FACILITY_OPTIONS { + + USER, + LOCAL0, + LOCAL1, + LOCAL2, + LOCAL3, + LOCAL4, + LOCAL5, + LOCAL6, + LOCAL7 + } + + public enum MAX_MEMORY_POLICY_OPTIONS { + + VOLATILE_LRU, + ALLKEYS_LRU, + VOLATILE_RANDOM, + ALLKEYS_RANDOM, + VOLATILE_TTL, + NOEVICTION + } + + public enum APPEND_FSYNC_MODE_OPTIONS { + + ALWAYS, + EVERYSEC, + NO + } + + public enum KEYSPACE_EVENTS_OPTIONS { + + K, + E, + g, + $, + l, + s, + h, + z, + x, + e, + A + } + + private static final String redisBinary; + + private final LinkedHashMap options = new LinkedHashMap<>(); + + static { + redisBinary = Optional.ofNullable(System.getProperty("redisBinary")) + .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe"); + } + + { + this.options.put(REDIS_OPTIONS.BINARY_PATH, redisBinary); + } + + /** + * To change the redisBinary system property for running the test, + * use argLine option from surefire plugin: + * + * $ mvn -DargLine="-DredisBinary=`which redis-server`" -Punit-test clean \ + * verify + * + * @param configPath + * @return Process running redis instance + * @throws IOException + * @throws InterruptedException + * @see + * + * http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#argLine + */ + public static RedisProcess runRedisWithConfigFile(String configPath) throws IOException, InterruptedException { URL resource = RedisRunner.class.getResource(configPath); + return runWithOptions(redisBinary, resource.getFile()); + } - ProcessBuilder master = new ProcessBuilder(redisFolder + "redis-server.exe", resource.getFile().substring(1)); - master.directory(new File(redisFolder)); + private static RedisProcess runWithOptions(String... options) throws IOException, InterruptedException { + List launchOptions = Arrays.stream(options) + .map(x -> Arrays.asList(x.split(" "))).flatMap(x -> x.stream()) + .collect(Collectors.toList()); + System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(launchOptions.toArray())); + ProcessBuilder master = new ProcessBuilder(launchOptions) + .redirectErrorStream(true) + .directory(new File(redisBinary).getParentFile()); Process p = master.start(); + new Thread(() -> { + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + try { + while (p.isAlive() && (line = reader.readLine()) != null) { + System.out.println("REDIS PROCESS: " + line); + } + } catch (IOException ex) { + System.out.println("Exception: " + ex.getLocalizedMessage()); + } + }).start(); Thread.sleep(1000); - return p; + return new RedisProcess(p); + } + + public RedisProcess run() throws IOException, InterruptedException { + return runWithOptions(options.values().toArray(new String[0])); + } + + private void addConfigOption(REDIS_OPTIONS option, Object... args) { + StringBuilder sb = new StringBuilder("--") + .append(option.toString() + .replaceAll("_", "-") + .replaceAll("\\$", " ") + .toLowerCase()) + .append(" ") + .append(Arrays.stream(args).map(Object::toString) + .collect(Collectors.joining(" "))); + this.options.put(option, + option.isAllowMultiple() + ? sb.insert(0, this.options.getOrDefault(option, "")).toString() + : sb.toString()); + } + + private String convertBoolean(boolean b) { + return b ? "yes" : "no"; + } + + public RedisRunner daemonize(boolean daemonize) { + addConfigOption(REDIS_OPTIONS.DAEMONIZE, convertBoolean(daemonize)); + return this; + } + + public RedisRunner pidfile(String pidfile) { + addConfigOption(REDIS_OPTIONS.PIDFILE, pidfile); + return this; + } + + public RedisRunner port(int port) { + addConfigOption(REDIS_OPTIONS.PORT, port); + return this; + } + + public RedisRunner tcpBacklog(long tcpBacklog) { + addConfigOption(REDIS_OPTIONS.TCP_BACKLOG, tcpBacklog); + return this; + } + + public RedisRunner bind(String bind) { + addConfigOption(REDIS_OPTIONS.BIND, bind); + return this; + } + + public RedisRunner unixsocket(String unixsocket) { + addConfigOption(REDIS_OPTIONS.UNIXSOCKET, unixsocket); + return this; + } + + public RedisRunner unixsocketperm(int unixsocketperm) { + addConfigOption(REDIS_OPTIONS.UNIXSOCKETPERM, unixsocketperm); + return this; + } + + public RedisRunner timeout(long timeout) { + addConfigOption(REDIS_OPTIONS.TIMEOUT, timeout); + return this; + } + + public RedisRunner tcpKeepalive(long tcpKeepalive) { + addConfigOption(REDIS_OPTIONS.TCP_KEEPALIVE, tcpKeepalive); + return this; + } + + public RedisRunner loglevel(LOGLEVEL_OPTIONS loglevel) { + addConfigOption(REDIS_OPTIONS.LOGLEVEL, loglevel.toString()); + return this; + } + + public RedisRunner logfile(String logfile) { + addConfigOption(REDIS_OPTIONS.LOGLEVEL, logfile); + return this; + } + + public RedisRunner syslogEnabled(boolean syslogEnabled) { + addConfigOption(REDIS_OPTIONS.SYSLOG_ENABLED, convertBoolean(syslogEnabled)); + return this; + } + + public RedisRunner syslogIdent(String syslogIdent) { + addConfigOption(REDIS_OPTIONS.SYSLOG_IDENT, syslogIdent); + return this; + } + + public RedisRunner syslogFacility(SYSLOG_FACILITY_OPTIONS syslogFacility) { + addConfigOption(REDIS_OPTIONS.SYSLOG_IDENT, syslogFacility.toString()); + return this; + } + + public RedisRunner databases(int databases) { + addConfigOption(REDIS_OPTIONS.DATABASES, databases); + return this; + } + + public RedisRunner save(long seconds, long changes) { + addConfigOption(REDIS_OPTIONS.SAVE, seconds, changes); + return this; + } + + public RedisRunner stopWritesOnBgsaveError(boolean stopWritesOnBgsaveError) { + addConfigOption(REDIS_OPTIONS.STOP_WRITES_ON_BGSAVE_ERROR, convertBoolean(stopWritesOnBgsaveError)); + return this; + } + + public RedisRunner rdbcompression(boolean rdbcompression) { + addConfigOption(REDIS_OPTIONS.RDBCOMPRESSION, convertBoolean(rdbcompression)); + return this; + } + + public RedisRunner rdbchecksum(boolean rdbchecksum) { + addConfigOption(REDIS_OPTIONS.RDBCHECKSUM, convertBoolean(rdbchecksum)); + return this; + } + + public RedisRunner dbfilename(String dbfilename) { + addConfigOption(REDIS_OPTIONS.DBFILENAME, dbfilename); + return this; + } + + public RedisRunner dir(String dir) { + addConfigOption(REDIS_OPTIONS.DIR, dir); + return this; + } + + public RedisRunner slaveof(Inet4Address masterip, int port) { + addConfigOption(REDIS_OPTIONS.SLAVEOF, masterip.getHostAddress(), port); + return this; + } + + public RedisRunner masterauth(String masterauth) { + addConfigOption(REDIS_OPTIONS.MASTERAUTH, masterauth); + return this; + } + + public RedisRunner slaveServeStaleData(boolean slaveServeStaleData) { + addConfigOption(REDIS_OPTIONS.SLAVE_SERVE_STALE_DATA, convertBoolean(slaveServeStaleData)); + return this; + } + + public RedisRunner slaveReadOnly(boolean slaveReadOnly) { + addConfigOption(REDIS_OPTIONS.SLAVE_READ_ONLY, convertBoolean(slaveReadOnly)); + return this; + } + + public RedisRunner replDisklessSync(boolean replDisklessSync) { + addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC, convertBoolean(replDisklessSync)); + return this; + } + + public RedisRunner replDisklessSyncDelay(long replDisklessSyncDelay) { + addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC_DELAY, replDisklessSyncDelay); + return this; + } + + public RedisRunner replPingSlavePeriod(long replPingSlavePeriod) { + addConfigOption(REDIS_OPTIONS.REPL_PING_SLAVE_PERIOD, replPingSlavePeriod); + return this; + } + + public RedisRunner replTimeout(long replTimeout) { + addConfigOption(REDIS_OPTIONS.REPL_TIMEOUT, replTimeout); + return this; + } + + public RedisRunner replDisableTcpNodelay(boolean replDisableTcpNodelay) { + addConfigOption(REDIS_OPTIONS.REPL_DISABLE_TCP_NODELAY, convertBoolean(replDisableTcpNodelay)); + return this; + } + + public RedisRunner replBacklogSize(String replBacklogSize) { + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_SIZE, replBacklogSize); + return this; + } + + public RedisRunner replBacklogTtl(long replBacklogTtl) { + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_TTL, replBacklogTtl); + return this; + } + + public RedisRunner slavePriority(long slavePriority) { + addConfigOption(REDIS_OPTIONS.SLAVE_PRIORITY, slavePriority); + return this; + } + + public RedisRunner minSlaveToWrite(long minSlaveToWrite) { + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_TO_WRITE, minSlaveToWrite); + return this; + } + + public RedisRunner minSlaveMaxLag(long minSlaveMaxLag) { + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_MAX_LAG, minSlaveMaxLag); + return this; + } + + public RedisRunner requirepass(String requirepass) { + addConfigOption(REDIS_OPTIONS.REQUREPASS, requirepass); + return this; + } + + public RedisRunner renameCommand(String renameCommand) { + addConfigOption(REDIS_OPTIONS.RENAME_COMMAND, renameCommand); + return this; + } + + public RedisRunner maxclients(long maxclients) { + addConfigOption(REDIS_OPTIONS.MAXCLIENTS, maxclients); + return this; + } + + public RedisRunner maxmemory(String maxmemory) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemory); + return this; + } + + public RedisRunner maxmemoryPolicy(MAX_MEMORY_POLICY_OPTIONS maxmemoryPolicy) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemoryPolicy.toString()); + return this; + } + + public RedisRunner maxmemorySamples(long maxmemorySamples) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemorySamples); + return this; + } + + public RedisRunner appendonly(boolean appendonly) { + addConfigOption(REDIS_OPTIONS.APPENDONLY, convertBoolean(appendonly)); + return this; + } + + public RedisRunner appendfilename(String appendfilename) { + addConfigOption(REDIS_OPTIONS.APPENDFILENAME, appendfilename); + return this; + } + + public RedisRunner appendfsync(APPEND_FSYNC_MODE_OPTIONS appendfsync) { + addConfigOption(REDIS_OPTIONS.APPENDFSYNC, appendfsync.toString()); + return this; + } + + public RedisRunner noAppendfsyncOnRewrite(boolean noAppendfsyncOnRewrite) { + addConfigOption(REDIS_OPTIONS.NO_APPENDFSYNC_ON_REWRITE, convertBoolean(noAppendfsyncOnRewrite)); + return this; + } + + public RedisRunner autoAofRewritePercentage(int autoAofRewritePercentage) { + addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_PERCENTAGE, autoAofRewritePercentage); + return this; + } + + public RedisRunner autoAofRewriteMinSize(String autoAofRewriteMinSize) { + addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_MIN_SIZE, autoAofRewriteMinSize); + return this; + } + + public RedisRunner aofLoadTruncated(boolean aofLoadTruncated) { + addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, convertBoolean(aofLoadTruncated)); + return this; + } + + public RedisRunner luaTimeLimit(long luaTimeLimit) { + addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, luaTimeLimit); + return this; + } + + public RedisRunner clusterEnabled(boolean clusterEnabled) { + addConfigOption(REDIS_OPTIONS.CLUSTER_ENABLED, convertBoolean(clusterEnabled)); + return this; + } + + public RedisRunner clusterConfigFile(String clusterConfigFile) { + addConfigOption(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, clusterConfigFile); + return this; + } + + public RedisRunner clusterNodeTimeout(long clusterNodeTimeout) { + addConfigOption(REDIS_OPTIONS.CLUSTER_NODE_TIMEOUT, clusterNodeTimeout); + return this; + } + + public RedisRunner clusterSlaveValidityFactor(long clusterSlaveValidityFactor) { + addConfigOption(REDIS_OPTIONS.CLUSTER_SLAVE_VALIDITY_FACTOR, clusterSlaveValidityFactor); + return this; } + public RedisRunner clusterMigrationBarrier(long clusterMigrationBarrier) { + addConfigOption(REDIS_OPTIONS.CLUSTER_MIGRATION_BARRIER, clusterMigrationBarrier); + return this; + } + + public RedisRunner clusterRequireFullCoverage(boolean clusterRequireFullCoverage) { + addConfigOption(REDIS_OPTIONS.CLUSTER_REQUIRE_FULL_COVERAGE, convertBoolean(clusterRequireFullCoverage)); + return this; + } + + public RedisRunner slowlogLogSlowerThan(long slowlogLogSlowerThan) { + addConfigOption(REDIS_OPTIONS.SLOWLOG_LOG_SLOWER_THAN, slowlogLogSlowerThan); + return this; + } + + public RedisRunner slowlogMaxLen(long slowlogMaxLen) { + addConfigOption(REDIS_OPTIONS.SLOWLOG_MAX_LEN, slowlogMaxLen); + return this; + } + + public RedisRunner latencyMonitorThreshold(long latencyMonitorThreshold) { + addConfigOption(REDIS_OPTIONS.LATENCY_MONITOR_THRESHOLD, latencyMonitorThreshold); + return this; + } + + public RedisRunner notifyKeyspaceEvents(KEYSPACE_EVENTS_OPTIONS notifyKeyspaceEvents) { + String existing = this.options.getOrDefault(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, ""); + addConfigOption(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, + existing.contains(notifyKeyspaceEvents.toString()) + ? existing + : (existing + notifyKeyspaceEvents.toString())); + return this; + } + + public RedisRunner hashMaxZiplistEntries(long hashMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_ENTRIES, hashMaxZiplistEntries); + return this; + } + + public RedisRunner hashMaxZiplistValue(long hashMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_VALUE, hashMaxZiplistValue); + return this; + } + + public RedisRunner listMaxZiplistEntries(long listMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_ENTRIES, listMaxZiplistEntries); + return this; + } + + public RedisRunner listMaxZiplistValue(long listMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_VALUE, listMaxZiplistValue); + return this; + } + + public RedisRunner setMaxIntsetEntries(long setMaxIntsetEntries) { + addConfigOption(REDIS_OPTIONS.SET_MAX_INTSET_ENTRIES, setMaxIntsetEntries); + return this; + } + + public RedisRunner zsetMaxZiplistEntries(long zsetMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_ENTRIES, zsetMaxZiplistEntries); + return this; + } + + public RedisRunner zsetMaxZiplistValue(long zsetMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_VALUE, zsetMaxZiplistValue); + return this; + } + + public RedisRunner hllSparseMaxBytes(long hllSparseMaxBytes) { + addConfigOption(REDIS_OPTIONS.HLL_SPARSE_MAX_BYTES, hllSparseMaxBytes); + return this; + } + + public RedisRunner activerehashing(boolean activerehashing) { + addConfigOption(REDIS_OPTIONS.ACTIVEREHASHING, convertBoolean(activerehashing)); + return this; + } + + public RedisRunner clientOutputBufferLimit$Normal(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, hardLimit, softLimit, softSeconds); + return this; + } + + public RedisRunner clientOutputBufferLimit$Slave(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, hardLimit, softLimit, softSeconds); + return this; + } + + public RedisRunner clientOutputBufferLimit$Pubsub(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, hardLimit, softLimit, softSeconds); + return this; + } + + public RedisRunner hz(int hz) { + addConfigOption(REDIS_OPTIONS.HZ, hz); + return this; + } + + public RedisRunner aofRewriteIncrementalFsync(boolean aofRewriteIncrementalFsync) { + addConfigOption(REDIS_OPTIONS.AOF_REWRITE_INCREMENTAL_FSYNC, convertBoolean(aofRewriteIncrementalFsync)); + return this; + } + + public static final class RedisProcess { + + private final Process redisProcess; + + private RedisProcess(Process redisProcess) { + this.redisProcess = redisProcess; + } + + public int stop() throws InterruptedException { + redisProcess.destroy(); + int exitCode = redisProcess.waitFor(); + return exitCode == 1 && isWindows() ? 0 : exitCode; + } + + public Process getRedisProcess() { + return redisProcess; + } + + private boolean isWindows() { + return System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH).contains("win"); + } + } } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index d28a5d3da..5fab2294d 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,9 +1,6 @@ package org.redisson; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.HashSet; @@ -22,8 +19,47 @@ import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBlockingQueue; +import io.netty.util.concurrent.Future; + public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testTakeAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.takeAsync(); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test + public void testPollAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.pollAsync(1, TimeUnit.SECONDS); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test public void testPollFromAny() throws InterruptedException { final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); @@ -225,14 +261,14 @@ public class RedissonBlockingQueueTest extends BaseTest { try { // blocking int item = queue.take(); - assertTrue(item > 0 && item <= total); + assertThat(item > 0 && item <= total).isTrue(); } catch (InterruptedException exception) { - fail(); + Assert.fail(); } count++; } - assertThat(counter.get(), equalTo(total)); + assertThat(counter.get()).isEqualTo(total); queue.delete(); } diff --git a/src/test/java/org/redisson/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 26f22b751..4380d26df 100644 --- a/src/test/java/org/redisson/RedissonKeysTest.java +++ b/src/test/java/org/redisson/RedissonKeysTest.java @@ -1,14 +1,12 @@ package org.redisson; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBucket; @@ -26,7 +24,7 @@ public class RedissonKeysTest extends BaseTest { Iterator iterator = redisson.getKeys().getKeysByPattern("test?").iterator(); for (; iterator.hasNext();) { String key = iterator.next(); - MatcherAssert.assertThat(key, Matchers.isOneOf("test1", "test2")); + assertThat(key).isIn("test1", "test2"); } } @@ -57,7 +55,7 @@ public class RedissonKeysTest extends BaseTest { RBucket bucket2 = redisson.getBucket("test2"); bucket2.set("someValue2"); - MatcherAssert.assertThat(redisson.getKeys().randomKey(), Matchers.isOneOf("test1", "test2")); + assertThat(redisson.getKeys().randomKey()).isIn("test1", "test2"); redisson.getKeys().delete("test1"); Assert.assertEquals(redisson.getKeys().randomKey(), "test2"); redisson.flushdb(); @@ -95,10 +93,10 @@ public class RedissonKeysTest extends BaseTest { map.fastPut("1", "2"); Collection keys = redisson.getKeys().findKeysByPattern("test?"); - MatcherAssert.assertThat(keys, Matchers.containsInAnyOrder("test1", "test2")); + assertThat(keys).containsOnly("test1", "test2"); Collection keys2 = redisson.getKeys().findKeysByPattern("test"); - MatcherAssert.assertThat(keys2, Matchers.empty()); + assertThat(keys2).isEmpty(); } @Test diff --git a/src/test/java/org/redisson/RedissonListMultimapCacheTest.java b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java new file mode 100644 index 000000000..5f5e500e4 --- /dev/null +++ b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java @@ -0,0 +1,139 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.redisson.core.RMultimapCache; + +public class RedissonListMultimapCacheTest extends BaseTest { + + @Test + public void testContains() { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.containsKey("1")).isTrue(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isTrue(); + assertThat(multimap.containsValue("3")).isTrue(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isTrue(); + assertThat(multimap.containsEntry("1", "3")).isTrue(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testContainsExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.containsKey("1")).isFalse(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isFalse(); + assertThat(multimap.containsValue("3")).isFalse(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isFalse(); + assertThat(multimap.containsEntry("1", "3")).isFalse(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testGetAll() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.getAll("1")).containsOnlyOnce("1", "2", "3"); + } + + @Test + public void testGetAllExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.getAll("1")).isEmpty(); + } + + @Test + public void testValues() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.put("1", "3"); + + assertThat(multimap.get("1").size()).isEqualTo(4); + assertThat(multimap.get("1")).containsExactly("1", "2", "3", "3"); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").contains("2")).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1", "2"))).isTrue(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isTrue(); + } + + @Test + public void testValuesExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.get("1").size()).isZero(); + assertThat(multimap.get("1")).contains(); + assertThat(multimap.get("1").remove("3")).isFalse(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); + } + + @Test + public void testScheduler() throws InterruptedException { + RMultimapCache cache = redisson.getListMultimapCache("simple33"); + assertThat(cache.put("1", "1")).isTrue(); + assertThat(cache.put("1", "2")).isTrue(); + assertThat(cache.put("1", "3")).isTrue(); + assertThat(cache.put("2", "1")).isTrue(); + assertThat(cache.put("2", "2")).isTrue(); + assertThat(cache.put("2", "3")).isTrue(); + + assertThat(cache.expireKey("1", 2, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("2", 3, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("3", 3, TimeUnit.SECONDS)).isFalse(); + + assertThat(cache.size()).isEqualTo(6); + + Thread.sleep(10000); + + assertThat(cache.size()).isZero(); + + } + + +} diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index d7a2e2a2e..9b5446a80 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -12,14 +12,15 @@ import org.redisson.core.RLock; import org.redisson.core.RedissonMultiLock; import io.netty.channel.nio.NioEventLoopGroup; +import org.redisson.RedisRunner.RedisProcess; public class RedissonMultiLockTest { @Test public void test() throws IOException, InterruptedException { - Process redis1 = RedisRunner.runRedis("/redis_multiLock_test_instance1.conf"); - Process redis2 = RedisRunner.runRedis("/redis_multiLock_test_instance2.conf"); - Process redis3 = RedisRunner.runRedis("/redis_multiLock_test_instance3.conf"); + RedisProcess redis1 = redisTestMultilockInstance1(); + RedisProcess redis2 = redisTestMultilockInstance2(); + RedisProcess redis3 = redisTestMultilockInstance3(); NioEventLoopGroup group = new NioEventLoopGroup(); Config config1 = new Config(); @@ -65,14 +66,28 @@ public class RedissonMultiLockTest { lock.unlock(); - redis1.destroy(); - assertThat(redis1.waitFor()).isEqualTo(1); + assertThat(redis1.stop()).isEqualTo(0); - redis2.destroy(); - assertThat(redis2.waitFor()).isEqualTo(1); + assertThat(redis2.stop()).isEqualTo(0); - redis3.destroy(); - assertThat(redis3.waitFor()).isEqualTo(1); + assertThat(redis3.stop()).isEqualTo(0); + } + + private RedisProcess redisTestMultilockInstance1() throws IOException, InterruptedException { + return new RedisRunner() + .port(6320) + .run(); + } + + private RedisProcess redisTestMultilockInstance2() throws IOException, InterruptedException { + return new RedisRunner() + .port(6321) + .run(); + } + + private RedisProcess redisTestMultilockInstance3() throws IOException, InterruptedException { + return new RedisRunner() + .port(6322) + .run(); } - } diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java new file mode 100644 index 000000000..c8daedd90 --- /dev/null +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -0,0 +1,108 @@ +package org.redisson; + +import org.junit.Assert; +import org.junit.Test; +import org.redisson.client.RedisTimeoutException; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class RedissonRemoteServiceTest extends BaseTest { + + public interface RemoteInterface { + + void voidMethod(String name, Long param); + + Long resultMethod(Long value); + + void errorMethod() throws IOException; + + void errorMethodWithCause(); + + void timeoutMethod() throws InterruptedException; + + } + + public class RemoteImpl implements RemoteInterface { + + @Override + public void voidMethod(String name, Long param) { + System.out.println(name + " " + param); + } + + @Override + public Long resultMethod(Long value) { + return value*2; + } + + @Override + public void errorMethod() throws IOException { + throw new IOException("Checking error throw"); + } + + @Override + public void errorMethodWithCause() { + try { + int s = 2 / 0; + } catch (Exception e) { + throw new RuntimeException("Checking error throw", e); + } + } + + @Override + public void timeoutMethod() throws InterruptedException { + Thread.sleep(2000); + } + + + } + + @Test(expected = RedisTimeoutException.class) + public void testTimeout() throws InterruptedException { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class, 1, TimeUnit.SECONDS); + + try { + ri.timeoutMethod(); + } finally { + r1.shutdown(); + r2.shutdown(); + } + } + + @Test + public void testInvocations() { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class); + + ri.voidMethod("someName", 100L); + assertThat(ri.resultMethod(100L)).isEqualTo(200); + + try { + ri.errorMethod(); + Assert.fail(); + } catch (IOException e) { + assertThat(e.getMessage()).isEqualTo("Checking error throw"); + } + + try { + ri.errorMethodWithCause(); + Assert.fail(); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); + assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); + } + + r1.shutdown(); + r2.shutdown(); + } + +} diff --git a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java index 8da3dacd0..3aeab3d72 100644 --- a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java +++ b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java @@ -161,6 +161,21 @@ public class RedissonScoredSortedSetTest extends BaseTest { Assert.assertEquals(3, (int)set.rank("d")); } + + @Test + public void testRevRank() { + RScoredSortedSet set = redisson.getScoredSortedSet("simple"); + set.add(0.1, "a"); + set.add(0.2, "b"); + set.add(0.3, "c"); + set.add(0.4, "d"); + set.add(0.5, "e"); + set.add(0.6, "f"); + set.add(0.7, "g"); + + Assert.assertEquals(1, (int)set.revRank("f")); + } + @Test public void testAddAsync() throws InterruptedException, ExecutionException { diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 69acd3519..9d7c71188 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -31,6 +31,15 @@ public class RedissonSetCacheTest extends BaseTest { } } + + @Test + public void testDelete() { + RSetCache set = redisson.getSetCache("set"); + assertThat(set.delete()).isFalse(); + set.add(1, 1, TimeUnit.SECONDS); + assertThat(set.delete()).isTrue(); + assertThat(set.delete()).isFalse(); + } @Test public void testEmptyReadAll() { @@ -169,7 +178,7 @@ public class RedissonSetCacheTest extends BaseTest { } @Test - public void testRetainAll() { + public void testRetainAll() throws InterruptedException { RSetCache set = redisson.getSetCache("set"); for (int i = 0; i < 10000; i++) { set.add(i); @@ -177,6 +186,7 @@ public class RedissonSetCacheTest extends BaseTest { } Assert.assertTrue(set.retainAll(Arrays.asList(1, 2))); + Thread.sleep(500); assertThat(set).containsOnly(1, 2); Assert.assertEquals(2, set.size()); } diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java new file mode 100644 index 000000000..442855b8c --- /dev/null +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -0,0 +1,190 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.redisson.core.RMultimapCache; +import org.redisson.core.RSetMultimap; + +public class RedissonSetMultimapCacheTest extends BaseTest { + + @Test + public void testContains() { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.containsKey("1")).isTrue(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isTrue(); + assertThat(multimap.containsValue("3")).isTrue(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isTrue(); + assertThat(multimap.containsEntry("1", "3")).isTrue(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testContainsExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.containsKey("1")).isFalse(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isFalse(); + assertThat(multimap.containsValue("3")).isFalse(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isFalse(); + assertThat(multimap.containsEntry("1", "3")).isFalse(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testGetAll() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.getAll("1")).containsOnlyOnce("1", "2", "3"); + } + + @Test + public void testGetAllExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.getAll("1")).isEmpty(); + } + + @Test + public void testValues() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.put("1", "3"); + + assertThat(multimap.get("1").size()).isEqualTo(3); + assertThat(multimap.get("1")).containsOnlyOnce("1", "2", "3"); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").contains("2")).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1", "2"))).isTrue(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isTrue(); + } + + @Test + public void testValuesExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.get("1").size()).isZero(); + assertThat(multimap.get("1")).contains(); + assertThat(multimap.get("1").remove("3")).isFalse(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); + } + + @Test + public void testScheduler() throws InterruptedException { + RMultimapCache cache = redisson.getSetMultimapCache("simple33"); + assertThat(cache.put("1", "1")).isTrue(); + assertThat(cache.put("1", "2")).isTrue(); + assertThat(cache.put("1", "3")).isTrue(); + assertThat(cache.put("2", "1")).isTrue(); + assertThat(cache.put("2", "2")).isTrue(); + assertThat(cache.put("2", "3")).isTrue(); + + assertThat(cache.expireKey("1", 2, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("2", 3, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("3", 3, TimeUnit.SECONDS)).isFalse(); + + assertThat(cache.size()).isEqualTo(6); + + Thread.sleep(10000); + + assertThat(cache.size()).isZero(); + + } + + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimapCache("simple1"); + assertThat(map2.delete()).isFalse(); + } + +} diff --git a/src/test/java/org/redisson/RedissonSetMultimapTest.java b/src/test/java/org/redisson/RedissonSetMultimapTest.java index ef47fd980..c30256c53 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapTest.java @@ -1,15 +1,17 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.redisson.core.RSetMultimap; -import static org.assertj.core.api.Assertions.*; public class RedissonSetMultimapTest extends BaseTest { @@ -273,5 +275,56 @@ public class RedissonSetMultimapTest extends BaseTest { assertThat(allValues).containsOnlyElementsOf(values); } + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimap("simple1"); + assertThat(map2.delete()).isFalse(); + } } diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 449669448..92b551c11 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -1,6 +1,7 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static com.jayway.awaitility.Awaitility.await; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,35 +13,15 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Test; -import org.redisson.client.RedisConnection; +import org.redisson.RedisRunner.RedisProcess; import org.redisson.client.RedisConnectionException; import org.redisson.client.RedisOutOfMemoryException; import org.redisson.client.WriteRedisConnectionException; -import org.redisson.client.codec.StringCodec; -import org.redisson.client.handler.CommandDecoder; -import org.redisson.client.handler.CommandEncoder; -import org.redisson.client.handler.CommandsListEncoder; -import org.redisson.client.handler.CommandsQueue; -import org.redisson.client.handler.ConnectionWatchdog; import org.redisson.codec.SerializationCodec; import org.redisson.connection.ConnectionListener; import org.redisson.core.ClusterNode; import org.redisson.core.Node; import org.redisson.core.NodesGroup; -import org.redisson.core.RBatch; -import org.redisson.core.RMap; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.util.concurrent.GenericFutureListener; - -import static com.jayway.awaitility.Awaitility.*; public class RedissonTest { @@ -61,43 +42,53 @@ public class RedissonTest { @Test(expected = RedisOutOfMemoryException.class) public void testMemoryScript() throws IOException, InterruptedException { - Process p = RedisRunner.runRedis("/redis_oom_test.conf"); + RedisProcess p = redisTestSmallMemory(); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").put("" + i, "" + i); } } finally { - p.destroy(); + p.stop(); } } @Test(expected = RedisOutOfMemoryException.class) public void testMemoryCommand() throws IOException, InterruptedException { - Process p = RedisRunner.runRedis("/redis_oom_test.conf"); + RedisProcess p = redisTestSmallMemory(); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").fastPut("" + i, "" + i); } } finally { - p.destroy(); + p.stop(); } } + @Test(expected = IllegalArgumentException.class) + public void testConfigValidation() { + Config redissonConfig = new Config(); + redissonConfig.useSingleServer() + .setAddress("127.0.0.1:6379") + .setConnectionPoolSize(2); + Redisson.create(redissonConfig); + } @Test public void testConnectionListener() throws IOException, InterruptedException, TimeoutException { - Process p = RedisRunner.runRedis("/redis_connectionListener_test.conf"); + RedisProcess p = redisTestConnection(); final AtomicInteger connectCounter = new AtomicInteger(); final AtomicInteger disconnectCounter = new AtomicInteger(); @@ -126,22 +117,20 @@ public class RedissonTest { assertThat(id).isNotZero(); r.getBucket("1").get(); - p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(0, p.stop()); try { r.getBucket("1").get(); } catch (Exception e) { } - p = RedisRunner.runRedis("/redis_connectionListener_test.conf"); + p = redisTestConnection(); r.getBucket("1").get(); r.shutdown(); - p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(0, p.stop()); await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(connectCounter.get()).isEqualTo(2)); await().until(() -> assertThat(disconnectCounter.get()).isEqualTo(1)); @@ -212,7 +201,7 @@ public class RedissonTest { assertThat(c.toJSON()).isEqualTo(t); } - @Test +// @Test public void testCluster() { NodesGroup nodes = redisson.getClusterNodesGroup(); Assert.assertEquals(2, nodes.getNodes().size()); @@ -282,5 +271,17 @@ public class RedissonTest { r.shutdown(); } + private RedisProcess redisTestSmallMemory() throws IOException, InterruptedException { + return new RedisRunner() + .maxmemory("1mb") + .port(6319) + .run(); + } + private RedisProcess redisTestConnection() throws IOException, InterruptedException { + return new RedisRunner() + .port(6319) + .run(); + } + } diff --git a/src/test/java/org/redisson/RedissonTopicTest.java b/src/test/java/org/redisson/RedissonTopicTest.java index 5d1d09ef4..7373a5365 100644 --- a/src/test/java/org/redisson/RedissonTopicTest.java +++ b/src/test/java/org/redisson/RedissonTopicTest.java @@ -105,7 +105,8 @@ public class RedissonTopicTest { }); topic1.removeListener(listenerId); topic1.removeListener(listenerId2); - l.await(); + + Assert.assertTrue(l.await(5, TimeUnit.SECONDS)); } @Test @@ -166,7 +167,7 @@ public class RedissonTopicTest { }); topic2.publish(new Message("123")); - messageRecieved.await(); + Assert.assertTrue(messageRecieved.await(5, TimeUnit.SECONDS)); redisson1.shutdown(); redisson2.shutdown(); @@ -231,7 +232,7 @@ public class RedissonTopicTest { } }); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < 5000; i++) { topic2.publish(new Message("123")); } @@ -239,7 +240,7 @@ public class RedissonTopicTest { Thread.sleep(1000); - Assert.assertEquals(500, counter); + Assert.assertEquals(5000, counter); redisson1.shutdown(); redisson2.shutdown();