Merge branch 'master' into 3.0.0

# Conflicts:
#	pom.xml
#	redisson-all/pom.xml
#	redisson-tomcat/pom.xml
#	redisson-tomcat/redisson-tomcat-6/pom.xml
#	redisson-tomcat/redisson-tomcat-7/pom.xml
#	redisson-tomcat/redisson-tomcat-8/pom.xml
#	redisson-tomcat/redisson-tomcat-9/pom.xml
#	redisson/pom.xml
#	redisson/src/main/java/org/redisson/RedissonReactive.java
#	redisson/src/main/java/org/redisson/command/CommandReactiveBatchService.java
#	redisson/src/main/java/org/redisson/reactive/RedissonBatchReactive.java
#	redisson/src/test/java/org/redisson/RedissonMapTest.java
pull/1821/head
Nikita 7 years ago
commit 09c5a6baf3

@ -4,6 +4,17 @@ Redisson Releases History
Try __[Redisson PRO](https://redisson.pro)__ version.
### 09-Apr-2018 - versions 2.11.5 and 3.6.5 released
Feature - `RKeys.copy` method added
Feature - `RObject.copy` method added
Feature - `RSetCache.getLock` method added
Fixed - `ClusterConnectionManager` throws `IllegalArgumentException`
Fixed - `CommandDecoder` doesn't remove command from commands queue when response was decoded with error
Fixed - `RSetMultimap.get()` doesn't create multimap entry in case of absence
Fixed - an error shouldn't appear if Redisson successfully got the information at least from one sentinel/cluster Redis node
Fixed - `RObject.migrate` method
Fixed - hdel comand wasn't used during remote service task removal
### 27-Mar-2018 - versions 2.11.4 and 3.6.4 released
Feature - `RSet.getLock` method added

@ -6,8 +6,8 @@ Based on high-performance async and lock-free Java Redis client and [Netty](http
| Stable <br/> Release Version | Release Date | JDK Version<br/> compatibility | `CompletionStage` <br/> support | `ProjectReactor` version<br/> compatibility |
| ------------- | ------------- | ------------| -----------| -----------|
| 3.6.4 | 27.03.2018 | 1.8, 1.9+ | Yes | 3.1.x |
| 2.11.4 | 27.03.2018 | 1.6, 1.7, 1.8, 1.9 and Android | No | 2.0.8 |
| 3.6.5 | 09.04.2018 | 1.8, 1.9+ | Yes | 3.1.x |
| 2.11.5 | 09.04.2018 | 1.6, 1.7, 1.8, 1.9 and Android | No | 2.0.8 |
Features
@ -95,23 +95,23 @@ Quick start
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.4</version>
<version>3.6.5</version>
</dependency>
<!-- JDK 1.6+ compatible -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.11.4</version>
<version>2.11.5</version>
</dependency>
#### Gradle
// JDK 1.8+ compatible
compile 'org.redisson:redisson:3.6.4'
compile 'org.redisson:redisson:3.6.5'
// JDK 1.6+ compatible
compile 'org.redisson:redisson:2.11.4'
compile 'org.redisson:redisson:2.11.5'
#### Java
@ -133,6 +133,15 @@ RExecutorService executor = redisson.getExecutorService("myExecutorService");
```
Downloads
===============================
[Redisson 3.6.5](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.6.5&e=jar),
[Redisson node 3.6.5](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.6.5&e=jar)
[Redisson 2.11.5](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.11.5&e=jar),
[Redisson node 2.11.5](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.11.5&e=jar)
FAQs
===============================
[Q: I saw a RedisTimeOutException, What does it mean? What shall I do? Can Redisson Team fix it?](https://github.com/redisson/redisson/wiki/16.-FAQ#q-i-saw-a-redistimeoutexception-what-does-it-mean-what-shall-i-do-can-redisson-team-fix-it)
@ -151,15 +160,6 @@ FAQs
[Q: Can I use different encoder/decoders for different tasks?](https://github.com/redisson/redisson/wiki/16.-FAQ#q-can-i-use-different-encoderdecoders-for-different-tasks)
Downloads
===============================
[Redisson 3.6.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.6.4&e=jar),
[Redisson node 3.6.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.6.4&e=jar)
[Redisson 2.11.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.11.4&e=jar),
[Redisson node 2.11.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.11.4&e=jar)
### Supported by
YourKit is kindly supporting this open source project with its full-featured Java Profiler.

@ -134,7 +134,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.22.Final</version>
<version>4.1.23.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>

@ -151,6 +151,12 @@
<version>[3.1,)</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>[3.1,5.0)</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.jpountz.lz4</groupId>

@ -18,6 +18,7 @@ package org.redisson;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.redisson.api.BatchOptions;
import org.redisson.api.ClusterNodesGroup;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.MapOptions;
@ -70,8 +71,10 @@ import org.redisson.api.RSetMultimap;
import org.redisson.api.RSetMultimapCache;
import org.redisson.api.RSortedSet;
import org.redisson.api.RTopic;
import org.redisson.api.RTransaction;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.api.TransactionOptions;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandExecutor;
import org.redisson.config.Config;
@ -81,6 +84,7 @@ import org.redisson.eviction.EvictionScheduler;
import org.redisson.misc.RedissonObjectFactory;
import org.redisson.pubsub.SemaphorePubSub;
import org.redisson.remote.ResponseEntry;
import org.redisson.transaction.RedissonTransaction;
import io.netty.util.internal.PlatformDependent;
@ -570,13 +574,23 @@ public class Redisson implements RedissonClient {
}
@Override
public RBatch createBatch() {
RedissonBatch batch = new RedissonBatch(evictionScheduler, connectionManager);
public RTransaction createTransaction(TransactionOptions options) {
return new RedissonTransaction(connectionManager.getCommandExecutor(), options);
}
@Override
public RBatch createBatch(BatchOptions options) {
RedissonBatch batch = new RedissonBatch(evictionScheduler, connectionManager, options);
if (config.isReferenceEnabled()) {
batch.enableRedissonReferenceSupport(this);
}
return batch;
}
@Override
public RBatch createBatch() {
return createBatch(BatchOptions.defaults());
}
@Override
public RLiveObjectService getLiveObjectService() {

@ -17,6 +17,7 @@ package org.redisson;
import java.util.concurrent.TimeUnit;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.RAtomicDoubleAsync;
import org.redisson.api.RAtomicLongAsync;
@ -57,19 +58,12 @@ public class RedissonBatch implements RBatch {
private final EvictionScheduler evictionScheduler;
private final CommandBatchService executorService;
private final BatchOptions options;
private long timeout;
private int retryAttempts;
private long retryInterval;
private int syncSlaves;
private long syncTimeout;
private boolean skipResult;
private boolean atomic;
public RedissonBatch(EvictionScheduler evictionScheduler, ConnectionManager connectionManager) {
public RedissonBatch(EvictionScheduler evictionScheduler, ConnectionManager connectionManager, BatchOptions options) {
this.executorService = new CommandBatchService(connectionManager);
this.evictionScheduler = evictionScheduler;
this.options = options;
}
@Override
@ -234,59 +228,48 @@ public class RedissonBatch implements RBatch {
@Override
public RBatch syncSlaves(int slaves, long timeout, TimeUnit unit) {
this.syncSlaves = slaves;
this.syncTimeout = unit.toMillis(timeout);
options.syncSlaves(slaves, timeout, unit);
return this;
}
@Override
public RBatch atomic() {
this.atomic = true;
options.atomic();
return this;
}
@Override
public RBatch skipResult() {
this.skipResult = true;
options.skipResult();
return this;
}
@Override
public RBatch retryAttempts(int retryAttempts) {
this.retryAttempts = retryAttempts;
options.retryAttempts(retryAttempts);
return this;
}
@Override
public RBatch retryInterval(long retryInterval, TimeUnit unit) {
this.retryInterval = unit.toMillis(retryInterval);
options.retryInterval(retryInterval, unit);
return this;
}
@Override
public RBatch timeout(long timeout, TimeUnit unit) {
this.timeout = unit.toMillis(timeout);
options.responseTimeout(timeout, unit);
return this;
}
@Override
public BatchResult<?> execute() {
return executorService.execute(syncSlaves, syncTimeout, skipResult, timeout, retryAttempts, retryInterval, atomic);
return executorService.execute(options);
}
@Override
public void executeSkipResult() {
executorService.execute(syncSlaves, syncTimeout, true, timeout, retryAttempts, retryInterval, atomic);
}
@Override
public RFuture<Void> executeSkipResultAsync() {
return executorService.executeAsync(syncSlaves, syncTimeout, true, timeout, retryAttempts, retryInterval, atomic);
}
@Override
public RFuture<BatchResult<?>> executeAsync() {
return executorService.executeAsync(syncSlaves, syncTimeout, skipResult, timeout, retryAttempts, retryInterval, atomic);
return executorService.executeAsync(options);
}
@Override

@ -39,7 +39,7 @@ public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V>
private final RedissonMultimapCache<K> baseCache;
RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
public RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
if (evictionScheduler != null) {
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
@ -47,7 +47,7 @@ public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V>
baseCache = new RedissonMultimapCache<K>(connectionManager, this, getTimeoutSetName(), prefix);
}
RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
public RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
if (evictionScheduler != null) {
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());

@ -37,21 +37,18 @@ import org.redisson.api.LocalCachedMapOptions.ReconnectionStrategy;
import org.redisson.api.LocalCachedMapOptions.SyncStrategy;
import org.redisson.api.RFuture;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.BaseStatusListener;
import org.redisson.api.listener.MessageListener;
import org.redisson.cache.Cache;
import org.redisson.cache.CacheKey;
import org.redisson.cache.LFUCacheMap;
import org.redisson.cache.LRUCacheMap;
import org.redisson.cache.LocalCacheListener;
import org.redisson.cache.LocalCachedMapClear;
import org.redisson.cache.LocalCachedMapInvalidate;
import org.redisson.cache.LocalCachedMapUpdate;
import org.redisson.cache.LocalCachedMessageCodec;
import org.redisson.cache.NoneCacheMap;
import org.redisson.cache.ReferenceCacheMap;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
@ -67,11 +64,8 @@ import org.redisson.eviction.EvictionScheduler;
import org.redisson.misc.Hash;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
@ -84,50 +78,10 @@ import io.netty.util.internal.PlatformDependent;
@SuppressWarnings("serial")
public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements RLocalCachedMap<K, V> {
private static final Logger log = LoggerFactory.getLogger(RedissonLocalCachedMap.class);
public static class CacheKey implements Serializable {
private final byte[] keyHash;
public CacheKey(byte[] keyHash) {
super();
this.keyHash = keyHash;
}
public byte[] getKeyHash() {
return keyHash;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(keyHash);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CacheKey other = (CacheKey) obj;
if (!Arrays.equals(keyHash, other.keyHash))
return false;
return true;
}
@Override
public String toString() {
return "CacheKey [keyHash=" + Arrays.toString(keyHash) + "]";
}
public static final String TOPIC_SUFFIX = "topic";
public static final String DISABLED_KEYS_SUFFIX = "disabled-keys";
public static final String DISABLED_ACK_SUFFIX = ":topic";
}
public static class CacheValue implements Serializable {
private final Object key;
@ -177,21 +131,18 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
private long cacheUpdateLogTime = TimeUnit.MINUTES.toMillis(10);
private byte[] instanceId;
private RTopic<Object> invalidationTopic;
private Cache<CacheKey, CacheValue> cache;
private int invalidateEntryOnChange;
private int syncListenerId;
private int reconnectionListenerId;
private volatile long lastInvalidate;
private SyncStrategy syncStrategy;
private final Codec topicCodec = new LocalCachedMessageCodec();
protected RedissonLocalCachedMap(CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions<K, V> options, EvictionScheduler evictionScheduler, RedissonClient redisson) {
private LocalCacheListener listener;
public RedissonLocalCachedMap(CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions<K, V> options, EvictionScheduler evictionScheduler, RedissonClient redisson) {
super(commandExecutor, name, redisson, options);
init(name, options, redisson, evictionScheduler);
}
protected RedissonLocalCachedMap(Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions<K, V> options, EvictionScheduler evictionScheduler, RedissonClient redisson) {
public RedissonLocalCachedMap(Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions<K, V> options, EvictionScheduler evictionScheduler, RedissonClient redisson) {
super(codec, connectionManager, name, redisson, options);
init(name, options, redisson, evictionScheduler);
}
@ -206,118 +157,33 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
if (options.getReconnectionStrategy() == ReconnectionStrategy.LOAD) {
invalidateEntryOnChange = 2;
evictionScheduler.schedule(getUpdatesLogName(), cacheUpdateLogTime + TimeUnit.MINUTES.toMillis(1));
evictionScheduler.schedule(listener.getUpdatesLogName(), cacheUpdateLogTime + TimeUnit.MINUTES.toMillis(1));
}
cache = createCache(options);
addListeners(name, options, redisson);
listener = new LocalCacheListener(name, commandExecutor, cache, this, instanceId, codec, options, cacheUpdateLogTime) {
@Override
protected void updateCache(ByteBuf keyBuf, ByteBuf valueBuf) throws IOException {
CacheKey cacheKey = toCacheKey(keyBuf);
Object key = codec.getMapKeyDecoder().decode(keyBuf, null);
Object value = codec.getMapValueDecoder().decode(valueBuf, null);
cachePut(cacheKey, key, value);
}
};
listener.add();
}
private void addListeners(String name, final LocalCachedMapOptions<K, V> options, final RedissonClient redisson) {
invalidationTopic = new RedissonTopic<Object>(topicCodec, commandExecutor, suffixName(name, "topic"));
if (options.getReconnectionStrategy() != ReconnectionStrategy.NONE) {
reconnectionListenerId = invalidationTopic.addListener(new BaseStatusListener() {
@Override
public void onSubscribe(String channel) {
if (options.getReconnectionStrategy() == ReconnectionStrategy.CLEAR) {
cache.clear();
}
if (options.getReconnectionStrategy() == ReconnectionStrategy.LOAD
// check if instance has already been used
&& lastInvalidate > 0) {
if (System.currentTimeMillis() - lastInvalidate > cacheUpdateLogTime) {
cache.clear();
return;
}
isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't check existance", future.cause());
return;
}
if (!future.getNow()) {
cache.clear();
return;
}
RScoredSortedSet<byte[]> logs = redisson.getScoredSortedSet(getUpdatesLogName(), ByteArrayCodec.INSTANCE);
logs.valueRangeAsync(lastInvalidate, true, Double.POSITIVE_INFINITY, true)
.addListener(new FutureListener<Collection<byte[]>>() {
@Override
public void operationComplete(Future<Collection<byte[]>> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't load update log", future.cause());
return;
}
for (byte[] entry : future.getNow()) {
byte[] keyHash = Arrays.copyOf(entry, 16);
CacheKey key = new CacheKey(keyHash);
cache.remove(key);
}
}
});
}
});
}
}
});
private void cachePut(CacheKey cacheKey, Object key, Object value) {
if (listener.isDisabled(cacheKey)) {
return;
}
if (options.getSyncStrategy() != SyncStrategy.NONE) {
syncListenerId = invalidationTopic.addListener(new MessageListener<Object>() {
@Override
public void onMessage(String channel, Object msg) {
if (msg instanceof LocalCachedMapClear) {
cache.clear();
}
if (msg instanceof LocalCachedMapInvalidate) {
LocalCachedMapInvalidate invalidateMsg = (LocalCachedMapInvalidate)msg;
if (!Arrays.equals(invalidateMsg.getExcludedId(), instanceId)) {
for (byte[] keyHash : invalidateMsg.getKeyHashes()) {
CacheKey key = new CacheKey(keyHash);
cache.remove(key);
}
}
}
if (msg instanceof LocalCachedMapUpdate) {
LocalCachedMapUpdate updateMsg = (LocalCachedMapUpdate) msg;
for (LocalCachedMapUpdate.Entry entry : updateMsg.getEntries()) {
ByteBuf keyBuf = Unpooled.wrappedBuffer(entry.getKey());
ByteBuf valueBuf = Unpooled.wrappedBuffer(entry.getValue());
try {
CacheKey cacheKey = toCacheKey(keyBuf);
Object key = codec.getMapKeyDecoder().decode(keyBuf, null);
Object value = codec.getMapValueDecoder().decode(valueBuf, null);
cache.put(cacheKey, new CacheValue(key, value));
} catch (IOException e) {
log.error("Can't decode map entry", e);
} finally {
keyBuf.release();
valueBuf.release();
}
}
}
if (options.getReconnectionStrategy() == ReconnectionStrategy.LOAD) {
lastInvalidate = System.currentTimeMillis();
}
}
});
}
cache.put(cacheKey, new CacheValue(key, value));
}
protected Cache<CacheKey, CacheValue> createCache(LocalCachedMapOptions<K, V> options) {
if (options.getEvictionPolicy() == EvictionPolicy.NONE) {
return new NoneCacheMap<CacheKey, CacheValue>(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis());
@ -337,7 +203,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
throw new IllegalArgumentException("Invalid eviction policy: " + options.getEvictionPolicy());
}
private CacheKey toCacheKey(Object key) {
public CacheKey toCacheKey(Object key) {
ByteBuf encoded = encodeMapKey(key);
try {
return toCacheKey(encoded);
@ -392,17 +258,13 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
V value = future.getNow();
if (value != null) {
cache.put(cacheKey, new CacheValue(key, value));
cachePut(cacheKey, key, value);
}
}
});
return future;
}
String getUpdatesLogName() {
return prefixName("redisson__cache_updates_log", getName());
}
protected static byte[] generateId() {
byte[] id = new byte[16];
// TODO JDK UPGRADE replace to native ThreadLocalRandom
@ -430,8 +292,8 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
CacheKey cacheKey = toCacheKey(mapKey);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = createSyncMessage(mapKey, mapValue, cacheKey);
CacheValue cacheValue = new CacheValue(key, value);
cache.put(cacheKey, cacheValue);
cachePut(cacheKey, key, value);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_VALUE,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
@ -443,7 +305,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "redis.call('publish', KEYS[2], ARGV[3]); "
+ "end;"
+ "return v; ",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
mapKey, mapValue, msg, invalidateEntryOnChange, System.currentTimeMillis(), entryId);
}
@ -461,8 +323,8 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
CacheKey cacheKey = toCacheKey(encodedKey);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = createSyncMessage(encodedKey, encodedValue, cacheKey);
CacheValue cacheValue = new CacheValue(key, value);
cache.put(cacheKey, cacheValue);
cachePut(cacheKey, key, value);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"if ARGV[4] == '1' then "
+ "redis.call('publish', KEYS[2], ARGV[3]); "
@ -475,22 +337,17 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "return 0; "
+ "end; "
+ "return 1; ",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
encodedKey, encodedValue, msg, invalidateEntryOnChange, System.currentTimeMillis(), entryId);
}
@Override
public void destroy() {
if (syncListenerId != 0) {
invalidationTopic.removeListener(syncListenerId);
}
if (reconnectionListenerId != 0) {
invalidationTopic.removeListener(reconnectionListenerId);
}
listener.remove();
}
@Override
public RFuture<V> removeOperationAsync(K key) {
protected RFuture<V> removeOperationAsync(K key) {
ByteBuf keyEncoded = encodeMapKey(key);
CacheKey cacheKey = toCacheKey(keyEncoded);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
@ -508,7 +365,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "end;"
+ "end; "
+ "return v",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyEncoded, msgEncoded, invalidateEntryOnChange, System.currentTimeMillis(), entryId);
}
@ -536,7 +393,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "table.insert(result, val);"
+ "end;"
+ "return result;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0)),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName()),
params.toArray());
}
@ -567,7 +424,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "table.insert(result, val);"
+ "end;"
+ "return result;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
params.toArray());
}
@ -616,7 +473,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "end;"
+ "end;"
+ "return counter;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0)),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName()),
params.toArray());
}
@ -646,7 +503,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "end;"
+ "end;"
+ "return counter;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
params.toArray());
}
@ -674,7 +531,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "return 1;"
+ "end; "
+ "return 0;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
msgEncoded, invalidateEntryOnChange);
}
@ -721,8 +578,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
private void cacheMap(Map<?, ?> map) {
for (java.util.Map.Entry<?, ?> entry : map.entrySet()) {
CacheKey cacheKey = toCacheKey(entry.getKey());
CacheValue cacheValue = new CacheValue(entry.getKey(), entry.getValue());
cache.put(cacheKey, cacheValue);
cachePut(cacheKey, entry.getKey(), entry.getValue());
}
}
@ -785,7 +641,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "end; "
+ "redis.call('publish', KEYS[2], ARGV[#ARGV]); "
+ "end;",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
params.toArray());
future.addListener(new FutureListener<Void>() {
@ -804,7 +660,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
@Override
public RFuture<V> addAndGetOperationAsync(final K key, Number value) {
protected RFuture<V> addAndGetOperationAsync(final K key, Number value) {
final ByteBuf keyState = encodeMapKey(key);
CacheKey cacheKey = toCacheKey(keyState);
ByteBuf msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
@ -819,7 +675,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "redis.call('publish', KEYS[2], ARGV[4]); "
+ "end;"
+ "return result; ",
Arrays.<Object>asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyState, new BigDecimal(value.toString()).toPlainString(), invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId);
future.addListener(new FutureListener<V>() {
@ -832,7 +688,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
V value = future.getNow();
if (value != null) {
CacheKey cacheKey = toCacheKey(key);
cache.put(cacheKey, new CacheValue(key, value));
cachePut(cacheKey, key, value);
}
}
});
@ -851,7 +707,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
if (future.getNow()) {
CacheKey cacheKey = toCacheKey(key);
cache.put(cacheKey, new CacheValue(key, value));
cachePut(cacheKey, key, value);
}
}
});
@ -927,7 +783,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
for (java.util.Map.Entry<K, V> entry : future.getNow().entrySet()) {
CacheKey cacheKey = toCacheKey(entry.getKey());
cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue()));
cachePut(cacheKey, entry.getKey(), entry.getValue());
}
result.putAll(future.getNow());
promise.trySuccess(result);
@ -945,7 +801,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
// few misses after this process
for(Entry<K,V> entry : super.entrySet()) {
CacheKey cacheKey = toCacheKey(entry.getKey());
cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue()));
cachePut(cacheKey, entry.getKey(), entry.getValue());
}
}
@ -956,21 +812,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
@Override
public RFuture<Void> clearLocalCacheAsync() {
final RPromise<Void> result = new RedissonPromise<Void>();
RFuture<Long> future = invalidationTopic.publishAsync(new LocalCachedMapClear());
future.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
result.trySuccess(null);
}
});
return result;
return listener.clearLocalCacheAsync();
}
@Override
@ -995,7 +837,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
for (java.util.Map.Entry<K, V> entry : future.getNow()) {
CacheKey cacheKey = toCacheKey(entry.getKey());
cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue()));
cachePut(cacheKey, entry.getKey(), entry.getValue());
}
result.addAll(future.getNow());
promise.trySuccess(result);
@ -1028,11 +870,58 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
mapKeys.toArray());
}
@Override
public RFuture<Boolean> fastReplaceAsync(final K key, final V value) {
RFuture<Boolean> future = super.fastReplaceAsync(key, value);
future.addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
return;
}
if (future.getNow()) {
CacheKey cacheKey = toCacheKey(key);
cachePut(cacheKey, key, value);
}
}
});
return future;
}
@Override
protected RFuture<Boolean> fastReplaceOperationAsync(K key, V value) {
ByteBuf keyState = encodeMapKey(key);
ByteBuf valueState = encodeMapValue(value);
CacheKey cacheKey = toCacheKey(keyState);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = createSyncMessage(keyState, valueState, cacheKey);
return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
+ "if ARGV[3] == '1' then "
+ "redis.call('publish', KEYS[2], ARGV[4]); "
+ "end;"
+ "if ARGV[3] == '2' then "
+ "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);"
+ "redis.call('publish', KEYS[2], ARGV[4]); "
+ "end;"
+ "return 1; "
+ "else "
+ "return 0; "
+ "end",
Arrays.<Object>asList(getName(key), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyState, valueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId);
}
@Override
protected RFuture<V> replaceOperationAsync(K key, V value) {
final ByteBuf keyState = encodeMapKey(key);
ByteBuf keyState = encodeMapKey(key);
ByteBuf valueState = encodeMapValue(value);
final CacheKey cacheKey = toCacheKey(keyState);
CacheKey cacheKey = toCacheKey(keyState);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = createSyncMessage(keyState, valueState, cacheKey);
return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE,
@ -1052,7 +941,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "else "
+ "return nil; "
+ "end",
Arrays.<Object>asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(key), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyState, valueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId);
}
@ -1068,7 +957,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
if (future.getNow() != null) {
CacheKey cacheKey = toCacheKey(key);
cache.put(cacheKey, new CacheValue(key, value));
cachePut(cacheKey, key, value);
}
}
});
@ -1078,10 +967,10 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
@Override
protected RFuture<Boolean> replaceOperationAsync(K key, V oldValue, V newValue) {
final ByteBuf keyState = encodeMapKey(key);
ByteBuf keyState = encodeMapKey(key);
ByteBuf oldValueState = encodeMapValue(oldValue);
ByteBuf newValueState = encodeMapValue(newValue);
final CacheKey cacheKey = toCacheKey(keyState);
CacheKey cacheKey = toCacheKey(keyState);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = createSyncMessage(keyState, newValueState, cacheKey);
return commandExecutor.evalWriteAsync(getName(key), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
@ -1098,7 +987,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "else "
+ "return 0; "
+ "end",
Arrays.<Object>asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(key), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyState, oldValueState, newValueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId);
}
@ -1115,7 +1004,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
if (future.getNow()) {
cache.put(cacheKey, new CacheValue(key, newValue));
cachePut(cacheKey, key, newValue);
}
}
});
@ -1125,9 +1014,9 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
@Override
protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
final ByteBuf keyState = encodeMapKey(key);
ByteBuf keyState = encodeMapKey(key);
ByteBuf valueState = encodeMapValue(value);
final CacheKey cacheKey = toCacheKey(keyState);
CacheKey cacheKey = toCacheKey(keyState);
byte[] entryId = generateLogEntryId(cacheKey.getKeyHash());
ByteBuf msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
@ -1144,7 +1033,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
+ "else "
+ "return 0 "
+ "end",
Arrays.<Object>asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()),
Arrays.<Object>asList(getName(key), listener.getInvalidationTopicName(), listener.getUpdatesLogName()),
keyState, valueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId);
}
@ -1181,7 +1070,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
if (future.getNow() == null) {
CacheKey cacheKey = toCacheKey(key);
cache.put(cacheKey, new CacheValue(key, value));
cachePut(cacheKey, key, value);
}
}
});
@ -1191,7 +1080,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
@Override
public ByteBuf encode(Object value) {
try {
return topicCodec.getValueEncoder().encode(value);
return LocalCachedMessageCodec.INSTANCE.getValueEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}

@ -623,6 +623,47 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
+ "end",
Collections.<Object>singletonList(getName(key)), encodeMapKey(key), encodeMapValue(value));
}
@Override
public boolean fastReplace(K key, V value) {
return get(fastReplaceAsync(key, value));
}
@Override
public RFuture<Boolean> fastReplaceAsync(final K key, final V value) {
checkKey(key);
checkValue(value);
RFuture<Boolean> future = fastReplaceOperationAsync(key, value);
if (hasNoWriter()) {
return future;
}
MapWriterTask<Boolean> listener = new MapWriterTask<Boolean>() {
@Override
public void execute() {
options.getWriter().write(key, value);
}
@Override
protected boolean condition(Future<Boolean> future) {
return future.getNow();
}
};
return mapWriterFuture(future, listener);
}
protected RFuture<Boolean> fastReplaceOperationAsync(final K key, final V value) {
return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
+ "return 1; "
+ "else "
+ "return 0; "
+ "end",
Collections.<Object>singletonList(getName(key)), encodeMapKey(key), encodeMapValue(value));
}
public RFuture<V> getOperationAsync(K key) {
return commandExecutor.readAsync(getName(key), codec, RedisCommands.HGET, getName(key), encodeMapKey(key));

@ -1602,6 +1602,37 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue));
}
@Override
protected RFuture<Boolean> fastReplaceOperationAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[2]); " +
"if value == false then " +
" return 0; " +
"end; " +
"local t, val = struct.unpack('dLc0', value); " +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " +
"if expireDateScore ~= false then " +
" expireDate = tonumber(expireDateScore) " +
"end; " +
"if t ~= 0 then " +
" local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " +
" if expireIdle ~= false then " +
" expireDate = math.min(expireDate, tonumber(expireIdle)) " +
" end; " +
"end; " +
"if expireDate <= tonumber(ARGV[1]) then " +
" return 0; " +
"end; " +
"local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " +
"redis.call('hset', KEYS[1], ARGV[2], value); " +
"local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " +
"redis.call('publish', KEYS[4], msg); " +
"return 1; ",
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)),
System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
@Override
protected RFuture<V> replaceOperationAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE,
@ -1631,7 +1662,6 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
"return val; ",
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)),
System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
@Override

@ -24,7 +24,7 @@ import org.redisson.api.RFuture;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.pubsub.SemaphorePubSub;
@ -45,13 +45,13 @@ public class RedissonPermitExpirableSemaphore extends RedissonExpirable implemen
private final SemaphorePubSub semaphorePubSub;
final CommandExecutor commandExecutor;
final CommandAsyncExecutor commandExecutor;
private final String timeoutName;
private final long nonExpirableTimeout = 922337203685477L;
protected RedissonPermitExpirableSemaphore(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub) {
public RedissonPermitExpirableSemaphore(CommandAsyncExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub) {
super(commandExecutor, name);
this.timeoutName = suffixName(name, "timeout");
this.commandExecutor = commandExecutor;

@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.redisson.api.BatchOptions;
import org.redisson.api.ClusterNode;
import org.redisson.api.MapOptions;
import org.redisson.api.Node;
@ -41,6 +42,7 @@ import org.redisson.api.RLockReactive;
import org.redisson.api.RMapCacheReactive;
import org.redisson.api.RMapReactive;
import org.redisson.api.RPatternTopicReactive;
import org.redisson.api.RPermitExpirableSemaphoreReactive;
import org.redisson.api.RQueueReactive;
import org.redisson.api.RReadWriteLockReactive;
import org.redisson.api.RScoredSortedSetReactive;
@ -76,6 +78,7 @@ import org.redisson.reactive.RedissonLockReactive;
import org.redisson.reactive.RedissonMapCacheReactive;
import org.redisson.reactive.RedissonMapReactive;
import org.redisson.reactive.RedissonPatternTopicReactive;
import org.redisson.reactive.RedissonPermitExpirableSemaphoreReactive;
import org.redisson.reactive.RedissonQueueReactive;
import org.redisson.reactive.RedissonReadWriteLockReactive;
import org.redisson.reactive.RedissonScoredSortedSetReactive;
@ -119,6 +122,11 @@ public class RedissonReactive implements RedissonReactiveClient {
return new RedissonSemaphoreReactive(commandExecutor, name, semaphorePubSub);
}
@Override
public RPermitExpirableSemaphoreReactive getPermitExpirableSemaphore(String name) {
return new RedissonPermitExpirableSemaphoreReactive(commandExecutor, name, semaphorePubSub);
}
@Override
public RReadWriteLockReactive getReadWriteLock(String name) {
return new RedissonReadWriteLockReactive(commandExecutor, name);
@ -321,14 +329,19 @@ public class RedissonReactive implements RedissonReactiveClient {
}
@Override
public RBatchReactive createBatch() {
RedissonBatchReactive batch = new RedissonBatchReactive(evictionScheduler, connectionManager, commandExecutor);
public RBatchReactive createBatch(BatchOptions options) {
RedissonBatchReactive batch = new RedissonBatchReactive(evictionScheduler, connectionManager, commandExecutor, options);
if (config.isReferenceEnabled()) {
batch.enableRedissonReferenceSupport(this);
}
return batch;
}
@Override
public RBatchReactive createBatch() {
return createBatch(BatchOptions.defaults());
}
@Override
public RKeysReactive getKeys() {
return new RedissonKeysReactive(commandExecutor);

@ -0,0 +1,155 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api;
import java.util.concurrent.TimeUnit;
/**
* Configuration for Batch.
*
* @author Nikita Koksharov
*
*/
public class BatchOptions {
private long responseTimeout;
private int retryAttempts;
private long retryInterval;
private long syncTimeout;
private int syncSlaves;
private boolean skipResult;
private boolean atomic;
private BatchOptions() {
}
public static BatchOptions defaults() {
return new BatchOptions();
}
public long getResponseTimeout() {
return responseTimeout;
}
/**
* Defines timeout for Redis response.
* Starts to countdown when Redis command has been successfully sent.
* <p>
* Default is <code>3000 milliseconds</code>
*
* @param timeout value
* @param unit value
* @return self instance
*/
public BatchOptions responseTimeout(long timeout, TimeUnit unit) {
this.responseTimeout = unit.toMillis(timeout);
return this;
}
public int getRetryAttempts() {
return retryAttempts;
}
/**
* Defines attempts amount to send Redis commands batch
* if it hasn't been sent already.
* <p>
* Default is <code>3 attempts</code>
*
* @param retryAttempts value
* @return self instance
*/
public BatchOptions retryAttempts(int retryAttempts) {
this.retryAttempts = retryAttempts;
return this;
}
public long getRetryInterval() {
return retryInterval;
}
/**
* Defines time interval for each attempt to send Redis commands batch
* if it hasn't been sent already.
* <p>
* Default is <code>1500 milliseconds</code>
*
* @param retryInterval - time interval
* @param retryIntervalUnit - time interval unit
* @return self instance
*/
public BatchOptions retryInterval(long retryInterval, TimeUnit retryIntervalUnit) {
this.retryInterval = retryIntervalUnit.toMillis(retryInterval);
return this;
}
/**
* Synchronize write operations execution within defined timeout
* across specified amount of Redis slave nodes.
* <p>
* NOTE: Redis 3.0+ required
*
* @param slaves - synchronization timeout
* @param timeout - synchronization timeout
* @param unit - synchronization timeout time unit
* @return self instance
*/
public BatchOptions syncSlaves(int slaves, long timeout, TimeUnit unit) {
this.syncSlaves = slaves;
this.syncTimeout = unit.toMillis(timeout);
return this;
}
public long getSyncTimeout() {
return syncTimeout;
}
public int getSyncSlaves() {
return syncSlaves;
}
/**
* Atomically executes all batched commands as a single command.
* <p>
* Please note, that in cluster mode all objects should be on the same cluster slot.
* https://github.com/antirez/redis/issues/3682
*
* @return self instance
*/
public BatchOptions atomic() {
atomic = true;
return this;
}
public boolean isAtomic() {
return atomic;
}
/**
* Inform Redis not to send reply. It may save network traffic.
* <p>
* NOTE: Redis 3.2+ required
*
* @return self instance
*/
public BatchOptions skipResult() {
skipResult = true;
return this;
}
public boolean isSkipResult() {
return skipResult;
}
}

@ -357,6 +357,12 @@ public interface RBatch {
*/
RLexSortedSetAsync getLexSortedSet(String name);
/**
* Returns bitSet instance by name.
*
* @param name - name of object
* @return BitSet object
*/
RBitSetAsync getBitSet(String name);
/**
@ -397,89 +403,39 @@ public interface RBatch {
RFuture<BatchResult<?>> executeAsync();
/*
* Use {@link #skipResult()}
* Use BatchOptions#atomic
*/
@Deprecated
void executeSkipResult();
/*
* Use {@link #skipResult()}
*/
@Deprecated
RFuture<Void> executeSkipResultAsync();
/**
* Atomically executes all batched commands as a single command.
* <p>
* Please note, that in cluster mode all objects should be on the same cluster slot.
* https://github.com/antirez/redis/issues/3682
*
* @return
*/
RBatch atomic();
/**
* Inform Redis not to send reply for this batch.
* Such approach saves network traffic.
* <p>
* NOTE: Redis 3.2+ required
*
* @return self instance
/*
* Use BatchOptions#skipResult
*/
@Deprecated
RBatch skipResult();
/**
* Synchronize write operations execution across defined amount
* of Redis slave nodes within defined timeout.
* <p>
* NOTE: Redis 3.0+ required
*
* @param slaves amount to sync
* @param timeout for sync operation
* @param unit value
* @return self instance
/*
* Use BatchOptions#syncSlaves
*/
@Deprecated
RBatch syncSlaves(int slaves, long timeout, TimeUnit unit);
/**
* Defines timeout for Redis response.
* Starts to countdown when Redis command has been successfully sent.
* <p>
* <code>0</code> value means use <code>Config.setTimeout</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param timeout value
* @param unit value
* @return self instance
/*
* Use BatchOptions#responseTimeout
*/
@Deprecated
RBatch timeout(long timeout, TimeUnit unit);
/**
* Defines time interval for each attempt to send Redis commands batch
* if it hasn't been sent already.
* <p>
* <code>0</code> value means use <code>Config.setRetryInterval</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param retryInterval value
* @param unit value
* @return self instance
/*
* Use BatchOptions#retryInterval
*/
@Deprecated
RBatch retryInterval(long retryInterval, TimeUnit unit);
/**
* Defines attempts amount to re-send Redis commands batch
* if it hasn't been sent already.
* <p>
* <code>0</code> value means use <code>Config.setRetryAttempts</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param retryAttempts value
* @return self instance
/*
* Use BatchOptions#retryAttempts
*/
@Deprecated
RBatch retryAttempts(int retryAttempts);
}

@ -251,66 +251,40 @@ public interface RBatchReactive {
*/
Publisher<BatchResult<?>> execute();
/**
* Command replies are skipped such approach saves response bandwidth.
* <p>
* NOTE: Redis 3.2+ required
*
* @return self instance
/*
* Use BatchOptions#atomic
*/
RBatchReactive skipResult();
@Deprecated
RBatchReactive atomic();
/**
*
* <p>
* NOTE: Redis 3.0+ required
*
* @param slaves number to sync
* @param timeout for sync operation
* @param unit value
* @return self instance
/*
* Use BatchOptions#skipResult
*/
@Deprecated
RBatchReactive skipResult();
/*
* Use BatchOptions#syncSlaves
*/
@Deprecated
RBatchReactive syncSlaves(int slaves, long timeout, TimeUnit unit);
/**
* Defines timeout for Redis response.
* Starts to countdown when Redis command has been successfully sent.
* <p>
* <code>0</code> value means use <code>Config.setTimeout</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param timeout value
* @param unit value
* @return self instance
/*
* Use BatchOptions#responseTimeout
*/
@Deprecated
RBatchReactive timeout(long timeout, TimeUnit unit);
/**
* Defines time interval for another one attempt send Redis commands batch
* if it hasn't been sent already.
* <p>
* <code>0</code> value means use <code>Config.setRetryInterval</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param retryInterval value
* @param unit value
* @return self instance
/*
* Use BatchOptions#retryInterval
*/
@Deprecated
RBatchReactive retryInterval(long retryInterval, TimeUnit unit);
/**
* Defines attempts amount to re-send Redis commands batch
* if it hasn't been sent already.
* <p>
* <code>0</code> value means use <code>Config.setRetryAttempts</code> value instead.
* <p>
* Default is <code>0</code>
*
* @param retryAttempts value
* @return self instance
/*
* Use BatchOptions#retryAttempts
*/
@Deprecated
RBatchReactive retryAttempts(int retryAttempts);
}

@ -219,7 +219,7 @@ public interface RMap<K, V> extends ConcurrentMap<K, V>, RExpirable, RMapAsync<K
/**
* Removes <code>keys</code> from map by one operation
* <p>
* Works faster than <code>{@link RMap#remove(Object)}</code> but not returning
* Works faster than <code>{@link #remove(Object)}</code> but not returning
* the value associated with <code>key</code>
* <p>
* If {@link MapWriter} is defined then <code>keys</code>are deleted in write-through mode.
@ -232,7 +232,7 @@ public interface RMap<K, V> extends ConcurrentMap<K, V>, RExpirable, RMapAsync<K
/**
* Associates the specified <code>value</code> with the specified <code>key</code>.
* <p>
* Works faster than <code>{@link RMap#put(Object, Object)}</code> but not returning
* Works faster than <code>{@link #put(Object, Object)}</code> but not returning
* the previous value associated with <code>key</code>
* <p>
* If {@link MapWriter} is defined then new map entry is stored in write-through mode.
@ -244,11 +244,26 @@ public interface RMap<K, V> extends ConcurrentMap<K, V>, RExpirable, RMapAsync<K
*/
boolean fastPut(K key, V value);
/**
* Replaces previous value with a new <code>value</code> associated with the <code>key</code>.
* <p>
* Works faster than <code>{@link #replace(Object, Object)}</code> but not returning
* the previous value associated with <code>key</code>
* <p>
* If {@link MapWriter} is defined then new map entry is stored in write-through mode.
*
* @param key - map key
* @param value - map value
* @return <code>true</code> if key exists and value was updated.
* <code>false</code> if key doesn't exists and value wasn't updated.
*/
boolean fastReplace(K key, V value);
/**
* Associates the specified <code>value</code> with the specified <code>key</code>
* only if there is no any association with specified<code>key</code>.
* <p>
* Works faster than <code>{@link RMap#putIfAbsent(Object, Object)}</code> but not returning
* Works faster than <code>{@link #putIfAbsent(Object, Object)}</code> but not returning
* the previous value associated with <code>key</code>
* <p>
* If {@link MapWriter} is defined then new map entry is stored in write-through mode.

@ -123,6 +123,21 @@ public interface RMapAsync<K, V> extends RExpirableAsync {
*/
RFuture<Boolean> fastPutAsync(K key, V value);
/**
* Replaces previous value with a new <code>value</code> associated with the <code>key</code>.
* <p>
* Works faster than <code>{@link RMap#replaceAsync(Object, Object)}</code> but not returning
* the previous value associated with <code>key</code>
* <p>
* If {@link MapWriter} is defined then new map entry is stored in write-through mode.
*
* @param key - map key
* @param value - map value
* @return <code>true</code> if key exists and value was updated.
* <code>false</code> if key doesn't exists and value wasn't updated.
*/
RFuture<Boolean> fastReplaceAsync(K key, V value);
/**
* Associates the specified <code>value</code> with the specified <code>key</code>
* only if there is no any association with specified<code>key</code>.

@ -0,0 +1,224 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api;
import java.util.concurrent.TimeUnit;
import org.reactivestreams.Publisher;
/**
* Semaphore object with support of lease time parameter for each acquired permit.
*
* <p>Each permit identified by own id and could be released only using its id.
* Permit id is a 128-bits unique random identifier generated each time during acquiring.
*
* <p>Works in non-fair mode. Therefore order of acquiring is unpredictable.
*
* @author Nikita Koksharov
*
*/
public interface RPermitExpirableSemaphoreReactive extends RExpirableReactive {
/**
* Acquires a permit from this semaphore, blocking until one is
* available, or the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>Acquires a permit, if one is available and returns its id,
* reducing the number of available permits by one.
*
* <p>If no permit is available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of two things happens:
* <ul>
* <li>Some other thread invokes the {@link #release(String)} method for this
* semaphore and the current thread is next to be assigned a permit; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* @return permit id
*/
Publisher<String> acquire();
/**
* Acquires a permit with defined lease time from this semaphore,
* blocking until one is available,
* or the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>Acquires a permit, if one is available and returns its id,
* reducing the number of available permits by one.
*
* <p>If no permit is available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of two things happens:
* <ul>
* <li>Some other thread invokes the {@link #release} method for this
* semaphore and the current thread is next to be assigned a permit; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* @param leaseTime - permit lease time
* @param unit - time unit
* @return permit id
*/
Publisher<String> acquire(long leaseTime, TimeUnit unit);
/**
* Acquires a permit only if one is available at the
* time of invocation.
*
* <p>Acquires a permit, if one is available and returns immediately,
* with the permit id,
* reducing the number of available permits by one.
*
* <p>If no permit is available then this method will return
* immediately with the value {@code null}.
*
* @return permit id if a permit was acquired and {@code null}
* otherwise
*/
Publisher<String> tryAcquire();
/**
* Acquires a permit from this semaphore, if one becomes available
* within the given waiting time and the current thread has not
* been {@linkplain Thread#interrupt interrupted}.
*
* <p>Acquires a permit, if one is available and returns immediately,
* with the permit id,
* reducing the number of available permits by one.
*
* <p>If no permit is available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of three things happens:
* <ul>
* <li>Some other thread invokes the {@link #release(String)} method for this
* semaphore and the current thread is next to be assigned a permit; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If a permit is acquired then the permit id is returned.
*
* <p>If the specified waiting time elapses then the value {@code null}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* @param waitTime the maximum time to wait for a permit
* @param unit the time unit of the {@code timeout} argument
* @return permit id if a permit was acquired and {@code null}
* if the waiting time elapsed before a permit was acquired
*/
Publisher<String> tryAcquire(long waitTime, TimeUnit unit);
/**
* Acquires a permit with defined lease time from this semaphore,
* if one becomes available
* within the given waiting time and the current thread has not
* been {@linkplain Thread#interrupt interrupted}.
*
* <p>Acquires a permit, if one is available and returns immediately,
* with the permit id,
* reducing the number of available permits by one.
*
* <p>If no permit is available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of three things happens:
* <ul>
* <li>Some other thread invokes the {@link #release(String)} method for this
* semaphore and the current thread is next to be assigned a permit; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If a permit is acquired then the permit id is returned.
*
* <p>If the specified waiting time elapses then the value {@code null}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* @param waitTime the maximum time to wait for a permit
* @param leaseTime permit lease time
* @param unit the time unit of the {@code timeout} argument
* @return permit id if a permit was acquired and {@code null}
* if the waiting time elapsed before a permit was acquired
*/
Publisher<String> tryAcquire(long waitTime, long leaseTime, TimeUnit unit);
/**
* Releases a permit by its id, returning it to the semaphore.
*
* <p>Releases a permit, increasing the number of available permits by
* one. If any threads of Redisson client are trying to acquire a permit,
* then one is selected and given the permit that was just released.
*
* <p>There is no requirement that a thread that releases a permit must
* have acquired that permit by calling {@link #acquire()}.
* Correct usage of a semaphore is established by programming convention
* in the application.
*
* @param permitId - permit id
* @return {@code true} if a permit has been released and {@code false}
* otherwise
*/
Publisher<Boolean> tryRelease(String permitId);
/**
* Releases a permit by its id, returning it to the semaphore.
*
* <p>Releases a permit, increasing the number of available permits by
* one. If any threads of Redisson client are trying to acquire a permit,
* then one is selected and given the permit that was just released.
*
* <p>There is no requirement that a thread that releases a permit must
* have acquired that permit by calling {@link #acquire()}.
* Correct usage of a semaphore is established by programming convention
* in the application.
*
* <p>Throws an exception if permit id doesn't exist or has already been release
*
* @param permitId - permit id
* @return void
*/
Publisher<Void> release(String permitId);
/**
* Returns the current number of available permits.
*
* @return number of available permits
*/
Publisher<Integer> availablePermits();
/**
* Sets number of permits.
*
* @param permits - number of permits
* @return <code>true</code> if permits has been set successfully, otherwise <code>false</code>.
*/
Publisher<Boolean> trySetPermits(int permits);
/**
* Increases or decreases the number of available permits by defined value.
*
* @param permits - number of permits to add/remove
* @return void
*/
Publisher<Void> addPermits(int permits);
}

@ -0,0 +1,166 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api;
import org.redisson.client.codec.Codec;
/**
* Transaction object allows to execute transactions over Redisson objects.
* Uses locks for write operations and maintains data modification operations list till the commit/rollback operation.
* <p>
* Transaction isolation level: <b>READ_COMMITTED</b>
*
* @author Nikita Koksharov
*
*/
public interface RTransaction {
/**
* Returns transactional object holder instance by name.
*
* @param <V> type of value
* @param name - name of object
* @return Bucket object
*/
<V> RBucket<V> getBucket(String name);
/**
* Returns transactional object holder instance by name
* using provided codec for object.
*
* @param <V> type of value
* @param name - name of object
* @param codec - codec for values
* @return Bucket object
*/
<V> RBucket<V> getBucket(String name, Codec codec);
/**
* Returns transactional map instance by name.
*
* @param <K> type of key
* @param <V> type of value
* @param name - name of object
* @return Map object
*/
<K, V> RMap<K, V> getMap(String name);
/**
* Returns transactional map instance by name
* using provided codec for both map keys and values.
*
* @param <K> type of key
* @param <V> type of value
* @param name - name of object
* @param codec - codec for keys and values
* @return Map object
*/
<K, V> RMap<K, V> getMap(String name, Codec codec);
/**
* Returns transactional set instance by name.
*
* @param <V> type of value
* @param name - name of object
* @return Set object
*/
<V> RSet<V> getSet(String name);
/**
* Returns transactional set instance by name
* using provided codec for set objects.
*
* @param <V> type of value
* @param name - name of object
* @param codec - codec for values
* @return Set object
*/
<V> RSet<V> getSet(String name, Codec codec);
/**
* Returns transactional set-based cache instance by <code>name</code>.
* Supports value eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSet(String)}.</p>
*
* @param <V> type of value
* @param name - name of object
* @return SetCache object
*/
<V> RSetCache<V> getSetCache(String name);
/**
* Returns transactional set-based cache instance by <code>name</code>.
* Supports value eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSet(String, Codec)}.</p>
*
* @param <V> type of value
* @param name - name of object
* @param codec - codec for values
* @return SetCache object
*/
<V> RSetCache<V> getSetCache(String name, Codec codec);
/**
* Returns transactional map-based cache instance by name.
* Supports entry eviction with a given MaxIdleTime and TTL settings.
* <p>
* If eviction is not required then it's better to use regular map {@link #getMap(String)}.</p>
*
* @param <K> type of key
* @param <V> type of value
* @param name - name of object
* @return MapCache object
*/
<K, V> RMapCache<K, V> getMapCache(String name);
/**
* Returns transactional map-based cache instance by <code>name</code>
* using provided <code>codec</code> for both cache keys and values.
* Supports entry eviction with a given MaxIdleTime and TTL settings.
* <p>
* If eviction is not required then it's better to use regular map {@link #getMap(String, Codec)}.
*
* @param <K> type of key
* @param <V> type of value
* @param name - object name
* @param codec - codec for keys and values
* @return MapCache object
*/
<K, V> RMapCache<K, V> getMapCache(String name, Codec codec);
/**
* Returns transactional local cached map proxy for specified local cached map instance.
*
* @param <K> type of key
* @param <V> type of value
* @param fromInstance - local cache map instance
* @return LocalCachedMap object
*/
<K, V> RLocalCachedMap<K, V> getLocalCachedMap(RLocalCachedMap<K, V> fromInstance);
/**
* Commits all changes made on this transaction.
*/
void commit();
/**
* Rollback all changes made on this transaction.
*/
void rollback();
}

@ -900,13 +900,29 @@ public interface RedissonClient {
RRemoteService getRemoteService(String name, Codec codec);
/**
* Return batch object which executes group of
* command in pipeline.
*
* Creates transaction with <b>READ_COMMITTED</b> isolation level.
*
* @param options - transaction configuration
* @return Transaction object
*/
RTransaction createTransaction(TransactionOptions options);
/**
* Creates batch object which could be executed later
* with collected group of commands in pipeline mode.
* <p>
* See <a href="http://redis.io/topics/pipelining">http://redis.io/topics/pipelining</a>
*
* @param options - batch configuration
* @return Batch object
*/
RBatch createBatch(BatchOptions options);
/*
* Use #createBatch(BatchOptions)
*
*/
@Deprecated
RBatch createBatch();
/**

@ -38,6 +38,15 @@ public interface RedissonReactiveClient {
*/
RSemaphoreReactive getSemaphore(String name);
/**
* Returns semaphore instance by name.
* Supports lease time parameter for each acquired permit.
*
* @param name - name of object
* @return PermitExpirableSemaphore object
*/
RPermitExpirableSemaphoreReactive getPermitExpirableSemaphore(String name);
/**
* Returns readWriteLock instance by name.
*
@ -498,8 +507,15 @@ public interface RedissonReactiveClient {
*
* See <a href="http://redis.io/topics/pipelining">http://redis.io/topics/pipelining</a>
*
* @param options - batch configuration
* @return Batch object
*/
RBatchReactive createBatch(BatchOptions options);
/*
* Use createBatch(BatchOptions)
*/
@Deprecated
RBatchReactive createBatch();
/**

@ -0,0 +1,134 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api;
import java.util.concurrent.TimeUnit;
/**
* Configuration for Transaction.
*
* @author Nikita Koksharov
*
*/
public class TransactionOptions {
private long responseTimeout = 3000;
private int retryAttempts = 3;
private long retryInterval = 1500;
private long syncTimeout = 5000;
private long timeout = 5000;
private TransactionOptions() {
}
public static TransactionOptions defaults() {
return new TransactionOptions();
}
public long getResponseTimeout() {
return responseTimeout;
}
/**
* Defines timeout for Redis response.
* Starts to countdown when Redis command has been successfully sent.
* <p>
* Default is <code>3000 milliseconds</code>
*
* @param timeout value
* @param unit value
* @return self instance
*/
public TransactionOptions responseTimeout(long timeout, TimeUnit unit) {
this.responseTimeout = unit.toMillis(timeout);
return this;
}
public int getRetryAttempts() {
return retryAttempts;
}
/**
* Defines attempts amount to send Redis commands batch
* if it hasn't been sent already.
* <p>
* Default is <code>3 attempts</code>
*
* @param retryAttempts value
* @return self instance
*/
public TransactionOptions retryAttempts(int retryAttempts) {
this.retryAttempts = retryAttempts;
return this;
}
public long getRetryInterval() {
return retryInterval;
}
/**
* Defines time interval for each attempt to send Redis commands batch
* if it hasn't been sent already.
* <p>
* Default is <code>1500 milliseconds</code>
*
* @param retryInterval - time interval
* @param retryIntervalUnit - time interval unit
* @return self instance
*/
public TransactionOptions retryInterval(long retryInterval, TimeUnit retryIntervalUnit) {
this.retryInterval = retryIntervalUnit.toMillis(retryInterval);
return this;
}
/**
* Synchronization data timeout between Redis master participating in transaction and its slaves.
* <p>
* Default syncSlaves is <code>5000 milliseconds</code>
*
* @param syncTimeout - synchronization timeout
* @param syncUnit - synchronization timeout time unit
* @return self instance
*/
public TransactionOptions syncSlavesTimeout(long syncTimeout, TimeUnit syncUnit) {
this.syncTimeout = syncUnit.toMillis(syncTimeout);
return this;
}
public long getSyncTimeout() {
return syncTimeout;
}
public long getTimeout() {
return timeout;
}
/**
* If transaction hasn't committed within <code>timeout</code> it will rollback automatically.
* <p>
* Default is <code>5000 milliseconds</code>
*
* @param timeout in milliseconds
* @param timeoutUnit timeout time unit
* @return self instance
*/
public TransactionOptions timeout(long timeout, TimeUnit timeoutUnit) {
this.timeout = timeoutUnit.toMillis(timeout);
return this;
}
}

@ -0,0 +1,68 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
import java.io.Serializable;
import java.util.Arrays;
/**
*
* @author Nikita Koksharov
*
*/
public class CacheKey implements Serializable {
private static final long serialVersionUID = 5790732187795028243L;
private final byte[] keyHash;
public CacheKey(byte[] keyHash) {
super();
this.keyHash = keyHash;
}
public byte[] getKeyHash() {
return keyHash;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(keyHash);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CacheKey other = (CacheKey) obj;
if (!Arrays.equals(keyHash, other.keyHash))
return false;
return true;
}
@Override
public String toString() {
return "CacheKey [keyHash=" + Arrays.toString(keyHash) + "]";
}
}

@ -0,0 +1,293 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonListMultimapCache;
import org.redisson.RedissonObject;
import org.redisson.RedissonScoredSortedSet;
import org.redisson.RedissonTopic;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.LocalCachedMapOptions.ReconnectionStrategy;
import org.redisson.api.LocalCachedMapOptions.SyncStrategy;
import org.redisson.api.RFuture;
import org.redisson.api.RListMultimapCache;
import org.redisson.api.RObject;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RTopic;
import org.redisson.api.listener.BaseStatusListener;
import org.redisson.api.listener.MessageListener;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public abstract class LocalCacheListener {
public static final String TOPIC_SUFFIX = "topic";
public static final String DISABLED_KEYS_SUFFIX = "disabled-keys";
public static final String DISABLED_ACK_SUFFIX = ":topic";
private Map<CacheKey, String> disabledKeys = new ConcurrentHashMap<CacheKey, String>();
private static final Logger log = LoggerFactory.getLogger(LocalCacheListener.class);
private String name;
private CommandAsyncExecutor commandExecutor;
private Cache<?, ?> cache;
private RObject object;
private byte[] instanceId;
private Codec codec;
private LocalCachedMapOptions<?, ?> options;
private long cacheUpdateLogTime;
private volatile long lastInvalidate;
private RTopic<Object> invalidationTopic;
private int syncListenerId;
private int reconnectionListenerId;
public LocalCacheListener(String name, CommandAsyncExecutor commandExecutor, Cache<?, ?> cache,
RObject object, byte[] instanceId, Codec codec, LocalCachedMapOptions<?, ?> options, long cacheUpdateLogTime) {
super();
this.name = name;
this.commandExecutor = commandExecutor;
this.cache = cache;
this.object = object;
this.instanceId = instanceId;
this.codec = codec;
this.options = options;
this.cacheUpdateLogTime = cacheUpdateLogTime;
}
public boolean isDisabled(Object key) {
return disabledKeys.containsKey(key);
}
public void add() {
invalidationTopic = new RedissonTopic<Object>(LocalCachedMessageCodec.INSTANCE, commandExecutor, getInvalidationTopicName());
if (options.getReconnectionStrategy() != ReconnectionStrategy.NONE) {
reconnectionListenerId = invalidationTopic.addListener(new BaseStatusListener() {
@Override
public void onSubscribe(String channel) {
if (options.getReconnectionStrategy() == ReconnectionStrategy.CLEAR) {
cache.clear();
}
if (options.getReconnectionStrategy() == ReconnectionStrategy.LOAD
// check if instance has already been used
&& lastInvalidate > 0) {
if (System.currentTimeMillis() - lastInvalidate > cacheUpdateLogTime) {
cache.clear();
return;
}
object.isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't check existance", future.cause());
return;
}
if (!future.getNow()) {
cache.clear();
return;
}
RScoredSortedSet<byte[]> logs = new RedissonScoredSortedSet<byte[]>(ByteArrayCodec.INSTANCE, commandExecutor, getUpdatesLogName(), null);
logs.valueRangeAsync(lastInvalidate, true, Double.POSITIVE_INFINITY, true)
.addListener(new FutureListener<Collection<byte[]>>() {
@Override
public void operationComplete(Future<Collection<byte[]>> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't load update log", future.cause());
return;
}
for (byte[] entry : future.getNow()) {
byte[] keyHash = Arrays.copyOf(entry, 16);
CacheKey key = new CacheKey(keyHash);
cache.remove(key);
}
}
});
}
});
}
}
});
}
if (options.getSyncStrategy() != SyncStrategy.NONE) {
syncListenerId = invalidationTopic.addListener(new MessageListener<Object>() {
@Override
public void onMessage(String channel, Object msg) {
if (msg instanceof LocalCachedMapDisable) {
LocalCachedMapDisable m = (LocalCachedMapDisable) msg;
String requestId = m.getRequestId();
Set<CacheKey> keysToDisable = new HashSet<CacheKey>();
for (byte[] keyHash : ((LocalCachedMapDisable) msg).getKeyHashes()) {
CacheKey key = new CacheKey(keyHash);
keysToDisable.add(key);
}
disableKeys(requestId, keysToDisable, m.getTimeout());
RedissonTopic<Object> topic = new RedissonTopic<Object>(LocalCachedMessageCodec.INSTANCE,
commandExecutor, RedissonObject.suffixName(name, requestId + DISABLED_ACK_SUFFIX));
topic.publishAsync(new LocalCachedMapDisableAck());
}
if (msg instanceof LocalCachedMapEnable) {
LocalCachedMapEnable m = (LocalCachedMapEnable) msg;
for (byte[] keyHash : m.getKeyHashes()) {
CacheKey key = new CacheKey(keyHash);
disabledKeys.remove(key, m.getRequestId());
}
}
if (msg instanceof LocalCachedMapClear) {
cache.clear();
}
if (msg instanceof LocalCachedMapInvalidate) {
LocalCachedMapInvalidate invalidateMsg = (LocalCachedMapInvalidate)msg;
if (!Arrays.equals(invalidateMsg.getExcludedId(), instanceId)) {
for (byte[] keyHash : invalidateMsg.getKeyHashes()) {
CacheKey key = new CacheKey(keyHash);
cache.remove(key);
}
}
}
if (msg instanceof LocalCachedMapUpdate) {
LocalCachedMapUpdate updateMsg = (LocalCachedMapUpdate) msg;
for (LocalCachedMapUpdate.Entry entry : updateMsg.getEntries()) {
ByteBuf keyBuf = Unpooled.wrappedBuffer(entry.getKey());
ByteBuf valueBuf = Unpooled.wrappedBuffer(entry.getValue());
try {
updateCache(keyBuf, valueBuf);
} catch (IOException e) {
log.error("Can't decode map entry", e);
} finally {
keyBuf.release();
valueBuf.release();
}
}
}
if (options.getReconnectionStrategy() == ReconnectionStrategy.LOAD) {
lastInvalidate = System.currentTimeMillis();
}
}
});
String disabledKeysName = RedissonObject.suffixName(name, DISABLED_KEYS_SUFFIX);
RListMultimapCache<LocalCachedMapDisabledKey, String> multimap = new RedissonListMultimapCache<LocalCachedMapDisabledKey, String>(null, codec, commandExecutor, disabledKeysName);
for (LocalCachedMapDisabledKey key : multimap.readAllKeySet()) {
Set<CacheKey> keysToDisable = new HashSet<CacheKey>();
for (String hash : multimap.getAll(key)) {
CacheKey cacheKey = new CacheKey(ByteBufUtil.decodeHexDump(hash));
keysToDisable.add(cacheKey);
}
disableKeys(key.getRequestId(), keysToDisable, key.getTimeout());
}
}
}
public RFuture<Void> clearLocalCacheAsync() {
final RPromise<Void> result = new RedissonPromise<Void>();
RFuture<Long> future = invalidationTopic.publishAsync(new LocalCachedMapClear());
future.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
result.trySuccess(null);
}
});
return result;
}
public String getInvalidationTopicName() {
return RedissonObject.suffixName(name, TOPIC_SUFFIX);
}
protected abstract void updateCache(ByteBuf keyBuf, ByteBuf valueBuf) throws IOException;
private void disableKeys(final String requestId, final Set<CacheKey> keys, long timeout) {
for (CacheKey key : keys) {
disabledKeys.put(key, requestId);
cache.remove(key);
}
commandExecutor.getConnectionManager().getGroup().schedule(new Runnable() {
@Override
public void run() {
for (CacheKey cacheKey : keys) {
disabledKeys.remove(cacheKey, requestId);
}
}
}, timeout, TimeUnit.MILLISECONDS);
}
public void remove() {
if (syncListenerId != 0) {
invalidationTopic.removeListener(syncListenerId);
}
if (reconnectionListenerId != 0) {
invalidationTopic.removeListener(reconnectionListenerId);
}
}
public String getUpdatesLogName() {
return RedissonObject.prefixName("redisson__cache_updates_log", name);
}
}

@ -0,0 +1,48 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
/**
*
* @author Nikita Koksharov
*
*/
public class LocalCachedMapDisable {
private byte[][] keyHashes;
private long timeout;
private String requestId;
public LocalCachedMapDisable(String requestId, byte[][] keyHashes, long timeout) {
super();
this.requestId = requestId;
this.keyHashes = keyHashes;
this.timeout = timeout;
}
public String getRequestId() {
return requestId;
}
public long getTimeout() {
return timeout;
}
public byte[][] getKeyHashes() {
return keyHashes;
}
}

@ -0,0 +1,31 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
import java.io.Serializable;
/**
*
* @author Nikita Koksharov
*
*/
@SuppressWarnings("serial")
public class LocalCachedMapDisableAck implements Serializable {
public LocalCachedMapDisableAck() {
}
}

@ -0,0 +1,45 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
/**
*
* @author Nikita Koksharov
*
*/
public class LocalCachedMapDisabledKey {
private String requestId;
private long timeout;
public LocalCachedMapDisabledKey() {
}
public LocalCachedMapDisabledKey(String requestId, long timeout) {
super();
this.requestId = requestId;
this.timeout = timeout;
}
public String getRequestId() {
return requestId;
}
public long getTimeout() {
return timeout;
}
}

@ -0,0 +1,42 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
/**
*
* @author Nikita Koksharov
*
*/
public class LocalCachedMapEnable {
private byte[][] keyHashes;
private String requestId;
public LocalCachedMapEnable(String requestId, byte[][] keyHashes) {
super();
this.requestId = requestId;
this.keyHashes = keyHashes;
}
public String getRequestId() {
return requestId;
}
public byte[][] getKeyHashes() {
return keyHashes;
}
}

@ -19,21 +19,24 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.BaseCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.CharsetUtil;
/**
*
* @author Nikita Koksharov
*
*/
public class LocalCachedMessageCodec implements Codec {
public class LocalCachedMessageCodec extends BaseCodec {
public static final LocalCachedMessageCodec INSTANCE = new LocalCachedMessageCodec();
private final Decoder<Object> decoder = new Decoder<Object>() {
@Override
public Object decode(ByteBuf buf, State state) throws IOException {
@ -72,6 +75,37 @@ public class LocalCachedMessageCodec implements Codec {
}
return new LocalCachedMapUpdate(entries);
}
if (type == 0x3) {
byte len = buf.readByte();
CharSequence requestId = buf.readCharSequence(len, CharsetUtil.UTF_8);
long timeout = buf.readLong();
int hashesCount = buf.readInt();
byte[][] hashes = new byte[hashesCount][];
for (int i = 0; i < hashesCount; i++) {
byte[] keyHash = new byte[16];
buf.readBytes(keyHash);
hashes[i] = keyHash;
}
return new LocalCachedMapDisable(requestId.toString(), hashes, timeout);
}
if (type == 0x4) {
return new LocalCachedMapDisableAck();
}
if (type == 0x5) {
byte len = buf.readByte();
CharSequence requestId = buf.readCharSequence(len, CharsetUtil.UTF_8);
int hashesCount = buf.readInt();
byte[][] hashes = new byte[hashesCount][];
for (int i = 0; i < hashesCount; i++) {
byte[] keyHash = new byte[16];
buf.readBytes(keyHash);
hashes[i] = keyHash;
}
return new LocalCachedMapEnable(requestId.toString(), hashes);
}
throw new IllegalArgumentException("Can't parse packet");
}
@ -90,6 +124,7 @@ public class LocalCachedMessageCodec implements Codec {
LocalCachedMapInvalidate li = (LocalCachedMapInvalidate) in;
ByteBuf result = ByteBufAllocator.DEFAULT.buffer();
result.writeByte(0x1);
result.writeBytes(li.getExcludedId());
result.writeInt(li.getKeyHashes().length);
for (int i = 0; i < li.getKeyHashes().length; i++) {
@ -100,7 +135,7 @@ public class LocalCachedMessageCodec implements Codec {
if (in instanceof LocalCachedMapUpdate) {
LocalCachedMapUpdate li = (LocalCachedMapUpdate) in;
ByteBuf result = ByteBufAllocator.DEFAULT.buffer(256);
ByteBuf result = ByteBufAllocator.DEFAULT.buffer();
result.writeByte(0x2);
for (LocalCachedMapUpdate.Entry e : li.getEntries()) {
@ -111,6 +146,41 @@ public class LocalCachedMessageCodec implements Codec {
}
return result;
}
if (in instanceof LocalCachedMapDisable) {
LocalCachedMapDisable li = (LocalCachedMapDisable) in;
ByteBuf result = ByteBufAllocator.DEFAULT.buffer();
result.writeByte(0x3);
result.writeByte(li.getRequestId().length());
result.writeCharSequence(li.getRequestId(), CharsetUtil.UTF_8);
result.writeLong(li.getTimeout());
result.writeInt(li.getKeyHashes().length);
for (int i = 0; i < li.getKeyHashes().length; i++) {
result.writeBytes(li.getKeyHashes()[i]);
}
return result;
}
if (in instanceof LocalCachedMapDisableAck) {
ByteBuf result = ByteBufAllocator.DEFAULT.buffer(1);
result.writeByte(0x4);
return result;
}
if (in instanceof LocalCachedMapEnable) {
LocalCachedMapEnable li = (LocalCachedMapEnable) in;
ByteBuf result = ByteBufAllocator.DEFAULT.buffer();
result.writeByte(0x5);
result.writeByte(li.getRequestId().length());
result.writeCharSequence(li.getRequestId(), CharsetUtil.UTF_8);
result.writeInt(li.getKeyHashes().length);
for (int i = 0; i < li.getKeyHashes().length; i++) {
result.writeBytes(li.getKeyHashes()[i]);
}
return result;
}
throw new IllegalArgumentException("Can't encode packet " + in);
}
@ -120,26 +190,6 @@ public class LocalCachedMessageCodec implements Codec {
public LocalCachedMessageCodec() {
}
@Override
public Decoder<Object> getMapValueDecoder() {
throw new UnsupportedOperationException();
}
@Override
public Encoder getMapValueEncoder() {
throw new UnsupportedOperationException();
}
@Override
public Decoder<Object> getMapKeyDecoder() {
throw new UnsupportedOperationException();
}
@Override
public Encoder getMapKeyEncoder() {
throw new UnsupportedOperationException();
}
@Override
public Decoder<Object> getValueDecoder() {
return decoder;
@ -150,9 +200,4 @@ public class LocalCachedMessageCodec implements Codec {
return encoder;
}
@Override
public ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
}

@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonReference;
import org.redisson.RedissonShutdownException;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.RFuture;
import org.redisson.client.RedisAskException;
@ -43,7 +44,6 @@ import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
@ -140,18 +140,18 @@ public class CommandBatchService extends CommandAsyncService {
}
public BatchResult<?> execute() {
RFuture<BatchResult<?>> f = executeAsync(0, 0, false, 0, 0, 0, false);
RFuture<BatchResult<?>> f = executeAsync(BatchOptions.defaults());
return get(f);
}
public BatchResult<?> execute(int syncSlaves, long syncTimeout, boolean noResult, long responseTimeout, int retryAttempts, long retryInterval, boolean atomic) {
RFuture<BatchResult<?>> f = executeAsync(syncSlaves, syncTimeout, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
public BatchResult<?> execute(BatchOptions options) {
RFuture<BatchResult<?>> f = executeAsync(options);
return get(f);
}
public RFuture<Void> executeAsyncVoid() {
final RedissonPromise<Void> promise = new RedissonPromise<Void>();
RFuture<BatchResult<?>> res = executeAsync(0, 0, false, 0, 0, 0, false);
RFuture<BatchResult<?>> res = executeAsync(BatchOptions.defaults());
res.addListener(new FutureListener<BatchResult<?>>() {
@Override
public void operationComplete(Future<BatchResult<?>> future) throws Exception {
@ -166,10 +166,10 @@ public class CommandBatchService extends CommandAsyncService {
}
public RFuture<List<?>> executeAsync() {
return executeAsync(0, 0, false, 0, 0, 0, false);
return executeAsync(BatchOptions.defaults());
}
public <R> RFuture<R> executeAsync(int syncSlaves, long syncTimeout, boolean skipResult, long responseTimeout, int retryAttempts, long retryInterval, boolean atomic) {
public <R> RFuture<R> executeAsync(BatchOptions options) {
if (executed) {
throw new IllegalStateException("Batch already executed!");
}
@ -179,7 +179,7 @@ public class CommandBatchService extends CommandAsyncService {
}
executed = true;
if (atomic) {
if (options.isAtomic()) {
for (Entry entry : commands.values()) {
BatchCommandData<?, ?> multiCommand = new BatchCommandData(RedisCommands.MULTI, new Object[] {}, index.incrementAndGet());
entry.getCommands().addFirst(multiCommand);
@ -188,7 +188,7 @@ public class CommandBatchService extends CommandAsyncService {
}
}
if (skipResult) {
if (options.isSkipResult()) {
for (Entry entry : commands.values()) {
BatchCommandData<?, ?> offCommand = new BatchCommandData(RedisCommands.CLIENT_REPLY, new Object[] { "OFF" }, index.incrementAndGet());
entry.getCommands().addFirst(offCommand);
@ -197,16 +197,17 @@ public class CommandBatchService extends CommandAsyncService {
}
}
if (syncSlaves > 0) {
if (options.getSyncSlaves() > 0) {
for (Entry entry : commands.values()) {
BatchCommandData<?, ?> waitCommand = new BatchCommandData(RedisCommands.WAIT, new Object[] { syncSlaves, syncTimeout }, index.incrementAndGet());
BatchCommandData<?, ?> waitCommand = new BatchCommandData(RedisCommands.WAIT,
new Object[] { options.getSyncSlaves(), options.getSyncTimeout() }, index.incrementAndGet());
entry.getCommands().add(waitCommand);
}
}
RPromise<R> resultPromise;
final RPromise<Void> voidPromise = new RedissonPromise<Void>();
if (skipResult) {
if (options.isSkipResult()) {
voidPromise.addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
@ -272,13 +273,13 @@ public class CommandBatchService extends CommandAsyncService {
}
for (java.util.Map.Entry<MasterSlaveEntry, Entry> e : commands.entrySet()) {
execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, skipResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, options);
}
return resultPromise;
}
private void execute(final Entry entry, final NodeSource source, final RPromise<Void> mainPromise, final AtomicInteger slots,
final int attempt, final boolean noResult, final long responseTimeout, final int retryAttempts, final long retryInterval, final boolean atomic) {
final int attempt, final BatchOptions options) {
if (mainPromise.isCancelled()) {
free(entry);
return;
@ -302,8 +303,8 @@ public class CommandBatchService extends CommandAsyncService {
}
final int attempts;
if (retryAttempts > 0) {
attempts = retryAttempts;
if (options.getRetryAttempts() > 0) {
attempts = options.getRetryAttempts();
} else {
attempts = connectionManager.getConfig().getRetryAttempts();
}
@ -374,13 +375,13 @@ public class CommandBatchService extends CommandAsyncService {
int count = attempt + 1;
mainPromise.removeListener(mainPromiseListener);
execute(entry, source, mainPromise, slots, count, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(entry, source, mainPromise, slots, count, options);
}
};
long interval = connectionManager.getConfig().getRetryInterval();
if (retryInterval > 0) {
interval = retryInterval;
if (options.getRetryInterval() > 0) {
interval = options.getRetryInterval();
}
Timeout timeout = connectionManager.newTimeout(retryTimerTask, interval, TimeUnit.MILLISECONDS);
@ -390,7 +391,7 @@ public class CommandBatchService extends CommandAsyncService {
connectionFuture.addListener(new FutureListener<RedisConnection>() {
@Override
public void operationComplete(Future<RedisConnection> connFuture) throws Exception {
checkConnectionFuture(entry, source, mainPromise, attemptPromise, details, connectionFuture, noResult, responseTimeout, attempts, atomic);
checkConnectionFuture(entry, source, mainPromise, attemptPromise, details, connectionFuture, options.isSkipResult(), options.getResponseTimeout(), attempts, options.isAtomic());
}
});
@ -408,19 +409,19 @@ public class CommandBatchService extends CommandAsyncService {
RedisMovedException ex = (RedisMovedException)future.cause();
entry.clearErrors();
NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.MOVED);
execute(entry, nodeSource, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(entry, nodeSource, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisAskException) {
RedisAskException ex = (RedisAskException)future.cause();
entry.clearErrors();
NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.ASK);
execute(entry, nodeSource, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(entry, nodeSource, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisLoadingException) {
entry.clearErrors();
execute(entry, source, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(entry, source, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisTryAgainException) {
@ -428,7 +429,7 @@ public class CommandBatchService extends CommandAsyncService {
connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
execute(entry, source, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval, atomic);
execute(entry, source, mainPromise, slots, attempt, options);
}
}, 1, TimeUnit.SECONDS);
return;

@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.RFuture;
import org.redisson.api.RedissonReactiveClient;
@ -59,12 +60,12 @@ public class CommandReactiveBatchService extends CommandReactiveService {
batchService.async(readOnlyMode, nodeSource, codec, command, params, mainPromise, attempt, ignoreRedirect);
}
public RFuture<BatchResult<?>> executeAsync(int syncSlaves, long syncTimeout, boolean skipResult, long responseTimeout, int retryAttempts, long retryInterval, boolean atomic) {
public RFuture<BatchResult<?>> executeAsync(BatchOptions options) {
for (Publisher<?> publisher : publishers) {
Flux.from(publisher).subscribe();
}
return batchService.executeAsync(syncSlaves, syncTimeout, skipResult, responseTimeout, retryAttempts, retryInterval, atomic);
return batchService.executeAsync(options);
}
@Override

@ -30,6 +30,7 @@ import org.redisson.connection.ConnectionManager;
import org.redisson.connection.DnsAddressResolverGroupFactory;
import org.redisson.connection.AddressResolverGroupFactory;
import org.redisson.connection.ReplicatedConnectionManager;
import org.redisson.misc.URIBuilder;
import io.netty.channel.EventLoopGroup;
@ -95,6 +96,10 @@ public class Config {
public Config() {
}
static {
URIBuilder.patchUriObject();
}
public Config(Config oldConf) {
setUseLinuxNativeEpoll(oldConf.isUseLinuxNativeEpoll());

@ -19,9 +19,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.util.List;
@ -124,102 +121,63 @@ public class ConfigSupport {
private ObjectMapper jsonMapper = createMapper(null, null);
private ObjectMapper yamlMapper = createMapper(new YAMLFactory(), null);
private void patchUriObject() throws IOException {
patchUriField("lowMask", "L_DASH");
patchUriField("highMask", "H_DASH");
}
private void patchUriField(String methodName, String fieldName)
throws IOException {
try {
Method lowMask = URI.class.getDeclaredMethod(methodName, String.class);
lowMask.setAccessible(true);
Long lowMaskValue = (Long) lowMask.invoke(null, "-_");
Field lowDash = URI.class.getDeclaredField(fieldName);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(lowDash, lowDash.getModifiers() & ~Modifier.FINAL);
lowDash.setAccessible(true);
lowDash.setLong(null, lowMaskValue);
} catch (Exception e) {
throw new IOException(e);
}
}
public <T> T fromJSON(String content, Class<T> configType) throws IOException {
patchUriObject();
return jsonMapper.readValue(content, configType);
}
public <T> T fromJSON(File file, Class<T> configType) throws IOException {
patchUriObject();
return fromJSON(file, configType, null);
}
public <T> T fromJSON(File file, Class<T> configType, ClassLoader classLoader) throws IOException {
patchUriObject();
jsonMapper = createMapper(null, classLoader);
return jsonMapper.readValue(file, configType);
}
public <T> T fromJSON(URL url, Class<T> configType) throws IOException {
patchUriObject();
return jsonMapper.readValue(url, configType);
}
public <T> T fromJSON(Reader reader, Class<T> configType) throws IOException {
patchUriObject();
return jsonMapper.readValue(reader, configType);
}
public <T> T fromJSON(InputStream inputStream, Class<T> configType) throws IOException {
patchUriObject();
return jsonMapper.readValue(inputStream, configType);
}
public String toJSON(Config config) throws IOException {
patchUriObject();
return jsonMapper.writeValueAsString(config);
}
public <T> T fromYAML(String content, Class<T> configType) throws IOException {
patchUriObject();
return yamlMapper.readValue(content, configType);
}
public <T> T fromYAML(File file, Class<T> configType) throws IOException {
patchUriObject();
return yamlMapper.readValue(file, configType);
}
public <T> T fromYAML(File file, Class<T> configType, ClassLoader classLoader) throws IOException {
patchUriObject();
yamlMapper = createMapper(new YAMLFactory(), classLoader);
return yamlMapper.readValue(file, configType);
}
public <T> T fromYAML(URL url, Class<T> configType) throws IOException {
patchUriObject();
return yamlMapper.readValue(url, configType);
}
public <T> T fromYAML(Reader reader, Class<T> configType) throws IOException {
patchUriObject();
return yamlMapper.readValue(reader, configType);
}
public <T> T fromYAML(InputStream inputStream, Class<T> configType) throws IOException {
patchUriObject();
return yamlMapper.readValue(inputStream, configType);
}
public String toYAML(Config config) throws IOException {
patchUriObject();
return yamlMapper.writeValueAsString(config);
}

@ -15,6 +15,9 @@
*/
package org.redisson.misc;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.regex.Pattern;
@ -40,6 +43,31 @@ public class URIBuilder {
return URI.create(uri.replace(s, "[" + s + "]"));
}
public static void patchUriObject() {
try {
patchUriField(35184372088832L, "L_DASH");
patchUriField(2147483648L, "H_DASH");
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private static void patchUriField(Long maskValue, String fieldName)
throws IOException {
try {
Field field = URI.class.getDeclaredField(fieldName);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
field.setLong(null, maskValue);
} catch (Exception e) {
throw new IOException(e);
}
}
public static boolean isValidIP(String host) {
if (ipv4Pattern.matcher(host).matches()) {
return true;

@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.RAtomicLongReactive;
import org.redisson.api.RBatchReactive;
@ -55,21 +56,14 @@ public class RedissonBatchReactive implements RBatchReactive {
private final EvictionScheduler evictionScheduler;
private final CommandReactiveBatchService executorService;
private final BatchOptions options;
private final CommandReactiveService commandExecutor;
private long timeout;
private int retryAttempts;
private long retryInterval;
private int syncSlaves;
private long syncTimeout;
private boolean skipResult;
private boolean atomic;
public RedissonBatchReactive(EvictionScheduler evictionScheduler, ConnectionManager connectionManager, CommandReactiveService commandExecutor) {
public RedissonBatchReactive(EvictionScheduler evictionScheduler, ConnectionManager connectionManager, CommandReactiveService commandExecutor, BatchOptions options) {
this.evictionScheduler = evictionScheduler;
this.executorService = new CommandReactiveBatchService(connectionManager);
this.commandExecutor = commandExecutor;
this.options = options;
}
@Override
@ -222,44 +216,43 @@ public class RedissonBatchReactive implements RBatchReactive {
return commandExecutor.reactive(new Supplier<RFuture<BatchResult<?>>>() {
@Override
public RFuture<BatchResult<?>> get() {
return executorService.executeAsync(syncSlaves, syncTimeout, skipResult, timeout, retryAttempts, retryInterval, atomic);
return executorService.executeAsync(options);
}
});
}
public RBatchReactive atomic() {
this.atomic = true;
options.atomic();
return this;
}
@Override
public RBatchReactive syncSlaves(int slaves, long timeout, TimeUnit unit) {
this.syncSlaves = slaves;
this.syncTimeout = unit.toMillis(timeout);
options.syncSlaves(slaves, timeout, unit);
return this;
}
@Override
public RBatchReactive skipResult() {
this.skipResult = true;
options.skipResult();
return this;
}
@Override
public RBatchReactive retryAttempts(int retryAttempts) {
this.retryAttempts = retryAttempts;
options.retryAttempts(retryAttempts);
return this;
}
@Override
public RBatchReactive retryInterval(long retryInterval, TimeUnit unit) {
this.retryInterval = unit.toMillis(retryInterval);
options.retryInterval(retryInterval, unit);
return this;
}
@Override
public RBatchReactive timeout(long timeout, TimeUnit unit) {
this.timeout = unit.toMillis(timeout);
options.responseTimeout(timeout, unit);
return this;
}

@ -0,0 +1,150 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.reactive;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.redisson.RedissonLock;
import org.redisson.RedissonPermitExpirableSemaphore;
import org.redisson.api.RFuture;
import org.redisson.api.RLockAsync;
import org.redisson.api.RPermitExpirableSemaphoreAsync;
import org.redisson.api.RPermitExpirableSemaphoreReactive;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandReactiveExecutor;
import org.redisson.pubsub.SemaphorePubSub;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonPermitExpirableSemaphoreReactive extends RedissonExpirableReactive implements RPermitExpirableSemaphoreReactive {
private final RPermitExpirableSemaphoreAsync instance;
public RedissonPermitExpirableSemaphoreReactive(CommandReactiveExecutor connectionManager, String name, SemaphorePubSub semaphorePubSub) {
super(connectionManager, name);
instance = new RedissonPermitExpirableSemaphore(commandExecutor, name, semaphorePubSub);
}
protected RLockAsync createLock(CommandAsyncExecutor connectionManager, String name) {
return new RedissonLock(commandExecutor, name);
}
@Override
public Publisher<String> acquire() {
return reactive(new Supplier<RFuture<String>>() {
@Override
public RFuture<String> get() {
return instance.acquireAsync();
}
});
}
@Override
public Publisher<String> acquire(final long leaseTime, final TimeUnit unit) {
return reactive(new Supplier<RFuture<String>>() {
@Override
public RFuture<String> get() {
return instance.acquireAsync(leaseTime, unit);
}
});
}
@Override
public Publisher<String> tryAcquire() {
return reactive(new Supplier<RFuture<String>>() {
@Override
public RFuture<String> get() {
return instance.tryAcquireAsync();
}
});
}
@Override
public Publisher<String> tryAcquire(final long waitTime, final TimeUnit unit) {
return reactive(new Supplier<RFuture<String>>() {
@Override
public RFuture<String> get() {
return instance.tryAcquireAsync(waitTime, unit);
}
});
}
@Override
public Publisher<String> tryAcquire(final long waitTime, final long leaseTime, final TimeUnit unit) {
return reactive(new Supplier<RFuture<String>>() {
@Override
public RFuture<String> get() {
return instance.tryAcquireAsync(waitTime, leaseTime, unit);
}
});
}
@Override
public Publisher<Boolean> tryRelease(final String permitId) {
return reactive(new Supplier<RFuture<Boolean>>() {
@Override
public RFuture<Boolean> get() {
return instance.tryReleaseAsync(permitId);
}
});
}
@Override
public Publisher<Void> release(final String permitId) {
return reactive(new Supplier<RFuture<Void>>() {
@Override
public RFuture<Void> get() {
return instance.releaseAsync(permitId);
}
});
}
@Override
public Publisher<Integer> availablePermits() {
return reactive(new Supplier<RFuture<Integer>>() {
@Override
public RFuture<Integer> get() {
return instance.availablePermitsAsync();
}
});
}
@Override
public Publisher<Boolean> trySetPermits(final int permits) {
return reactive(new Supplier<RFuture<Boolean>>() {
@Override
public RFuture<Boolean> get() {
return instance.trySetPermitsAsync(permits);
}
});
}
@Override
public Publisher<Void> addPermits(final int permits) {
return reactive(new Supplier<RFuture<Void>>() {
@Override
public RFuture<Void> get() {
return instance.addPermitsAsync(permits);
}
});
}
}

@ -15,18 +15,14 @@
*/
package org.redisson.reactive;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.redisson.RedissonLock;
import org.redisson.RedissonSemaphore;
import org.redisson.api.RFuture;
import org.redisson.api.RLockAsync;
import org.redisson.api.RSemaphoreAsync;
import org.redisson.api.RSemaphoreReactive;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandReactiveExecutor;
import org.redisson.pubsub.SemaphorePubSub;
@ -44,10 +40,6 @@ public class RedissonSemaphoreReactive extends RedissonExpirableReactive impleme
instance = new RedissonSemaphore(commandExecutor, name, semaphorePubSub);
}
protected RLockAsync createLock(CommandAsyncExecutor connectionManager, String name) {
return new RedissonLock(commandExecutor, name);
}
@Override
public Publisher<Boolean> tryAcquire() {
return reactive(new Supplier<RFuture<Boolean>>() {

@ -0,0 +1,37 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.spring.transaction;
import org.redisson.api.RTransaction;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonTransactionHolder {
private RTransaction transaction;
public RTransaction getTransaction() {
return transaction;
}
public void setTransaction(RTransaction transaction) {
this.transaction = transaction;
}
}

@ -0,0 +1,129 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.spring.transaction;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RTransaction;
import org.redisson.api.RedissonClient;
import org.redisson.api.TransactionOptions;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager {
private static final long serialVersionUID = -6151310954082124041L;
private RedissonClient redisson;
public RedissonTransactionManager(RedissonClient redisson) {
this.redisson = redisson;
}
public RTransaction getCurrentTransaction() {
RedissonTransactionHolder to = (RedissonTransactionHolder) TransactionSynchronizationManager.getResource(redisson);
if (to == null) {
throw new NoTransactionException("No transaction is available for the current thread");
}
return to.getTransaction();
}
@Override
protected Object doGetTransaction() throws TransactionException {
RedissonTransactionObject transactionObject = new RedissonTransactionObject();
RedissonTransactionHolder holder = (RedissonTransactionHolder) TransactionSynchronizationManager.getResource(redisson);
if (holder != null) {
transactionObject.setTransactionHolder(holder);
}
return transactionObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
RedissonTransactionObject transactionObject = (RedissonTransactionObject) transaction;
return transactionObject.getTransactionHolder() != null;
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
RedissonTransactionObject tObject = (RedissonTransactionObject) transaction;
if (tObject.getTransactionHolder() == null) {
int timeout = determineTimeout(definition);
TransactionOptions options = TransactionOptions.defaults();
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
options.timeout(timeout, TimeUnit.SECONDS);
}
RTransaction trans = redisson.createTransaction(options);
RedissonTransactionHolder holder = new RedissonTransactionHolder();
holder.setTransaction(trans);
tObject.setTransactionHolder(holder);
TransactionSynchronizationManager.bindResource(redisson, holder);
}
}
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction();
try {
to.getTransactionHolder().getTransaction().commit();
} catch (TransactionException e) {
throw new TransactionSystemException("Unable to commit transaction", e);
}
}
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction();
try {
to.getTransactionHolder().getTransaction().rollback();
} catch (TransactionException e) {
throw new TransactionSystemException("Unable to commit transaction", e);
}
}
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction();
to.setRollbackOnly(true);
}
@Override
protected void doCleanupAfterCompletion(Object transaction) {
TransactionSynchronizationManager.unbindResourceIfPossible(redisson);
RedissonTransactionObject to = (RedissonTransactionObject) transaction;
to.getTransactionHolder().setTransaction(null);
}
@Override
public Object getResourceFactory() {
return redisson;
}
}

@ -0,0 +1,52 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.spring.transaction;
import org.springframework.transaction.support.SmartTransactionObject;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonTransactionObject implements SmartTransactionObject {
private boolean isRollbackOnly;
private RedissonTransactionHolder transactionHolder;
public RedissonTransactionHolder getTransactionHolder() {
return transactionHolder;
}
public void setTransactionHolder(RedissonTransactionHolder transaction) {
this.transactionHolder = transaction;
}
public void setRollbackOnly(boolean isRollbackOnly) {
this.isRollbackOnly = isRollbackOnly;
}
@Override
public boolean isRollbackOnly() {
return isRollbackOnly;
}
@Override
public void flush() {
// skip
}
}

@ -0,0 +1,967 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.redisson.RedissonMap;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonObject;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.client.RedisClient;
import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.Hash;
import org.redisson.misc.HashValue;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.transaction.operation.DeleteOperation;
import org.redisson.transaction.operation.TouchOperation;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.UnlinkOperation;
import org.redisson.transaction.operation.map.MapAddAndGetOperation;
import org.redisson.transaction.operation.map.MapFastPutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapFastPutOperation;
import org.redisson.transaction.operation.map.MapFastRemoveOperation;
import org.redisson.transaction.operation.map.MapOperation;
import org.redisson.transaction.operation.map.MapPutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapPutOperation;
import org.redisson.transaction.operation.map.MapRemoveOperation;
import org.redisson.transaction.operation.map.MapReplaceOperation;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param <K> key type
* @param <V> value type
*/
public class BaseTransactionalMap<K, V> {
public static class MapEntry {
public static final MapEntry NULL = new MapEntry(null, null);
private final Object key;
private final Object value;
public MapEntry(Object key, Object value) {
super();
this.key = key;
this.value = value;
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
}
private final long timeout;
final List<TransactionalOperation> operations;
final Map<HashValue, MapEntry> state = new HashMap<HashValue, MapEntry>();
final RMap<K, V> map;
Boolean deleted;
public BaseTransactionalMap(long timeout, List<TransactionalOperation> operations, RMap<K, V> map) {
super();
this.timeout = timeout;
this.operations = operations;
this.map = map;
}
HashValue toKeyHash(Object key) {
ByteBuf keyState = ((RedissonObject)map).encodeMapKey(key);
try {
return new HashValue(Hash.hash128(keyState));
} finally {
keyState.release();
}
}
private HashValue toValueHash(Object value) {
ByteBuf keyState = ((RedissonObject)map).encodeMapValue(value);
try {
return new HashValue(Hash.hash128(keyState));
} finally {
keyState.release();
}
}
public RFuture<Boolean> isExistsAsync() {
if (deleted != null) {
return RedissonPromise.newSucceededFuture(!deleted);
}
return map.isExistsAsync();
}
public RFuture<Boolean> unlinkAsync(CommandAsyncExecutor commandExecutor) {
return deleteAsync(commandExecutor, new UnlinkOperation(map.getName(), null));
}
public RFuture<Boolean> touchAsync(CommandAsyncExecutor commandExecutor) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
if (deleted != null && deleted) {
operations.add(new TouchOperation(map.getName()));
result.trySuccess(false);
return result;
}
map.isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new TouchOperation(map.getName()));
boolean exists = future.getNow();
if (!exists) {
for (MapEntry entry : state.values()) {
if (entry != MapEntry.NULL) {
exists = true;
break;
}
}
}
result.trySuccess(exists);
}
});
result.trySuccess(null);
return result;
}
public RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor) {
return deleteAsync(commandExecutor, new DeleteOperation(map.getName()));
}
protected RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor, final TransactionalOperation operation) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
if (deleted != null) {
operations.add(operation);
result.trySuccess(!deleted);
deleted = true;
return result;
}
map.isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
for (HashValue key : state.keySet()) {
state.put(key, MapEntry.NULL);
}
deleted = true;
result.trySuccess(future.getNow());
}
});
return result;
}
protected MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
long startPos, String pattern) {
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = ((RedissonMap<?, ?>)map).scanIterator(name, client, startPos, pattern);
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
for (Iterator<ScanObjectEntry> iterator = res.getMap().keySet().iterator(); iterator.hasNext();) {
ScanObjectEntry entry = iterator.next();
MapEntry mapEntry = newstate.remove(entry.getHash());
if (mapEntry != null) {
if (mapEntry == MapEntry.NULL) {
iterator.remove();
continue;
}
HashValue valueHash = toValueHash(mapEntry.getValue());
res.getMap().put(entry, new ScanObjectEntry(valueHash, mapEntry.getValue()));
}
}
if (startPos == 0) {
for (Entry<HashValue, MapEntry> entry : newstate.entrySet()) {
if (entry.getValue() == MapEntry.NULL) {
continue;
}
ScanObjectEntry key = new ScanObjectEntry(entry.getKey(), entry.getValue().getKey());
ScanObjectEntry value = new ScanObjectEntry(toValueHash(entry.getValue().getValue()), entry.getValue().getValue());
res.getMap().put(key, value);
}
}
return res;
}
public RFuture<Boolean> containsKeyAsync(Object key) {
HashValue keyHash = toKeyHash(key);
MapEntry currentValue = state.get(keyHash);
if (currentValue != null) {
if (currentValue == MapEntry.NULL) {
return RedissonPromise.newSucceededFuture(false);
} else {
return RedissonPromise.newSucceededFuture(true);
}
}
return map.containsKeyAsync(key);
}
public RFuture<Boolean> containsValueAsync(Object value) {
for (MapEntry entry : state.values()) {
if (entry != MapEntry.NULL && isEqual(entry.getValue(), value)) {
return RedissonPromise.newSucceededFuture(true);
}
}
return map.containsValueAsync(value);
}
protected RFuture<V> addAndGetOperationAsync(final K key, final Number value) {
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
BigDecimal currentValue = BigDecimal.ZERO;
if (entry != MapEntry.NULL) {
currentValue = (BigDecimal) entry.getValue();
}
BigDecimal res = currentValue.add(new BigDecimal(value.toString()));
operations.add(new MapAddAndGetOperation(map, key, value));
state.put(keyHash, new MapEntry(key, res));
if (deleted != null) {
deleted = false;
}
NumberConvertor convertor = new NumberConvertor(value.getClass());
result.trySuccess((V) convertor.convert(res.toPlainString()));
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
BigDecimal currentValue = new BigDecimal(future.getNow().toString());
BigDecimal res = currentValue.add(new BigDecimal(value.toString()));
operations.add(new MapAddAndGetOperation(map, key, value));
state.put(keyHash, new MapEntry(key, res));
if (deleted != null) {
deleted = false;
}
NumberConvertor convertor = new NumberConvertor(value.getClass());
result.trySuccess((V) convertor.convert(res.toPlainString()));
}
});
}
});
return result;
}
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
return putIfAbsentOperationAsync(key, value, new MapPutIfAbsentOperation(map, key, value));
}
protected RFuture<V> putIfAbsentOperationAsync(final K key, final V value, final MapOperation mapOperation) {
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
operations.add(mapOperation);
if (entry == MapEntry.NULL) {
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
result.trySuccess(null);
} else {
result.trySuccess((V) entry.getValue());
}
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(mapOperation);
if (future.getNow() == null) {
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected final RFuture<V> putOperationAsync(K key, V value) {
return putOperationAsync(key, value, new MapPutOperation(map, key, value));
}
protected RFuture<V> putOperationAsync(final K key, final V value, final MapOperation operation) {
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
operations.add(operation);
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
if (entry == MapEntry.NULL) {
result.trySuccess(null);
} else {
result.trySuccess((V) entry.getValue());
}
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
return fastPutIfAbsentOperationAsync(key, value, new MapFastPutIfAbsentOperation(map, key, value));
}
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(final K key, final V value, final MapOperation mapOperation) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
operations.add(mapOperation);
if (entry == MapEntry.NULL) {
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
result.trySuccess(true);
} else {
result.trySuccess(false);
}
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(mapOperation);
boolean isUpdated = future.getNow() == null;
if (isUpdated) {
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
}
result.trySuccess(isUpdated);
}
});
}
});
return result;
}
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
return fastPutOperationAsync(key, value, new MapFastPutOperation(map, key, value));
}
protected RFuture<Boolean> fastPutOperationAsync(final K key, final V value, final MapOperation operation) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
operations.add(operation);
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
if (entry == MapEntry.NULL) {
result.trySuccess(true);
} else {
result.trySuccess(false);
}
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
state.put(keyHash, new MapEntry(key, value));
if (deleted != null) {
deleted = false;
}
boolean isNew = future.getNow() == null;
result.trySuccess(isNew);
}
});
}
});
return result;
}
@SuppressWarnings("unchecked")
protected RFuture<Long> fastRemoveOperationAsync(final K... keys) {
final RPromise<Long> result = new RedissonPromise<Long>();
executeLocked(result, new Runnable() {
@Override
public void run() {
AtomicLong counter = new AtomicLong();
List<K> keyList = Arrays.asList(keys);
for (Iterator<K> iterator = keyList.iterator(); iterator.hasNext();) {
K key = iterator.next();
HashValue keyHash = toKeyHash(key);
MapEntry currentValue = state.get(keyHash);
if (currentValue != null && currentValue != MapEntry.NULL) {
operations.add(new MapFastRemoveOperation(map, key));
state.put(keyHash, MapEntry.NULL);
counter.incrementAndGet();
iterator.remove();
}
}
// TODO optimize
map.getAllAsync(new HashSet<K>(keyList)).addListener(new FutureListener<Map<K, V>>() {
@Override
public void operationComplete(Future<Map<K, V>> future) throws Exception {
if (future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
for (K key : keys) {
HashValue keyHash = toKeyHash(key);
operations.add(new MapFastRemoveOperation(map, key));
state.put(keyHash, MapEntry.NULL);
}
result.trySuccess(null);
}
});
}
}, Arrays.asList(keys));
return result;
}
public RFuture<Integer> valueSizeAsync(K key) {
HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
if (entry == MapEntry.NULL) {
return RedissonPromise.newSucceededFuture(null);
} else {
ByteBuf valueState = ((RedissonObject)map).encodeMapValue(entry.getValue());
try {
return RedissonPromise.newSucceededFuture(valueState.readableBytes());
} finally {
valueState.release();
}
}
}
return map.valueSizeAsync(key);
}
protected RFuture<V> getOperationAsync(K key) {
HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
if (entry == MapEntry.NULL) {
return RedissonPromise.newSucceededFuture(null);
} else {
return RedissonPromise.newSucceededFuture((V)entry.getValue());
}
}
return ((RedissonMap<K, V>)map).getOperationAsync(key);
}
public RFuture<Set<K>> readAllKeySetAsync() {
final RPromise<Set<K>> result = new RedissonPromise<Set<K>>();
RFuture<Set<K>> future = map.readAllKeySetAsync();
future.addListener(new FutureListener<Set<K>>() {
@Override
public void operationComplete(Future<Set<K>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
Set<K> set = future.getNow();
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
for (Iterator<K> iterator = set.iterator(); iterator.hasNext();) {
K key = iterator.next();
MapEntry value = newstate.remove(toKeyHash(key));
if (value == MapEntry.NULL) {
iterator.remove();
}
}
for (MapEntry entry : newstate.values()) {
if (entry == MapEntry.NULL) {
continue;
}
set.add((K) entry.getKey());
}
result.trySuccess(set);
}
});
return result;
}
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
final RPromise<Set<Entry<K, V>>> result = new RedissonPromise<Set<Entry<K, V>>>();
RFuture<Map<K, V>> future = readAllMapAsync();
future.addListener(new FutureListener<Map<K, V>>() {
@Override
public void operationComplete(Future<Map<K, V>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
result.trySuccess(future.getNow().entrySet());
}
});
return result;
}
public RFuture<Collection<V>> readAllValuesAsync() {
final RPromise<Collection<V>> result = new RedissonPromise<Collection<V>>();
RFuture<Map<K, V>> future = readAllMapAsync();
future.addListener(new FutureListener<Map<K, V>>() {
@Override
public void operationComplete(Future<Map<K, V>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
result.trySuccess(future.getNow().values());
}
});
return result;
}
public RFuture<Map<K, V>> readAllMapAsync() {
final RPromise<Map<K, V>> result = new RedissonPromise<Map<K, V>>();
RFuture<Map<K, V>> future = map.readAllMapAsync();
future.addListener(new FutureListener<Map<K, V>>() {
@Override
public void operationComplete(Future<Map<K, V>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
Map<K, V> map = future.getNow();
for (Iterator<K> iterator = map.keySet().iterator(); iterator.hasNext();) {
K key = iterator.next();
MapEntry entry = newstate.remove(toKeyHash(key));
if (entry == MapEntry.NULL) {
iterator.remove();
} else if (entry != null) {
map.put(key, (V) entry.getValue());
}
}
for (MapEntry entry : newstate.values()) {
if (entry == MapEntry.NULL) {
continue;
}
map.put((K)entry.getKey(), (V)entry.getValue());
}
result.trySuccess(map);
}
});
return result;
}
protected RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
final RPromise<Map<K, V>> result = new RedissonPromise<Map<K, V>>();
Set<K> keysToLoad = new HashSet<K>(keys);
final Map<K, V> map = new HashMap<K, V>();
for (K key : keys) {
HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
if (entry != MapEntry.NULL) {
map.put(key, (V)entry.getValue());
}
} else {
keysToLoad.add(key);
}
}
RFuture<Map<K, V>> future = ((RedissonMap<K, V>)map).getAllOperationAsync(keysToLoad);
future.addListener(new FutureListener<Map<K, V>>() {
@Override
public void operationComplete(Future<Map<K, V>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
map.putAll(future.getNow());
result.trySuccess(map);
}
});
return result;
}
protected RFuture<V> removeOperationAsync(final K key) {
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
operations.add(new MapRemoveOperation(map, key));
if (entry == MapEntry.NULL) {
result.trySuccess(null);
} else {
state.put(keyHash, MapEntry.NULL);
result.trySuccess((V) entry.getValue());
}
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new MapRemoveOperation(map, key));
if (future.getNow() != null) {
state.put(keyHash, MapEntry.NULL);
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected RFuture<Boolean> removeOperationAsync(final Object key, final Object value) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, (K)key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
if (entry == MapEntry.NULL) {
result.trySuccess(false);
return;
}
operations.add(new MapRemoveOperation(map, key, value));
if (isEqual(entry.getValue(), value)) {
state.put(keyHash, MapEntry.NULL);
result.trySuccess(true);
return;
}
result.trySuccess(false);
return;
}
map.getAsync((K)key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new MapRemoveOperation(map, key, value));
boolean res = isEqual(future.getNow(), value);
if (res) {
state.put(keyHash, MapEntry.NULL);
}
result.trySuccess(res);
}
});
}
});
return result;
}
private boolean isEqual(Object value, Object oldValue) {
ByteBuf valueBuf = ((RedissonObject)map).encodeMapValue(value);
ByteBuf oldValueBuf = ((RedissonObject)map).encodeMapValue(oldValue);
try {
return valueBuf.equals(oldValueBuf);
} finally {
valueBuf.readableBytes();
oldValueBuf.readableBytes();
}
}
protected RFuture<Void> putAllOperationAsync(final Map<? extends K, ? extends V> entries) {
final RPromise<Void> result = new RedissonPromise<Void>();
executeLocked(result, new Runnable() {
@Override
public void run() {
for (Entry<? extends K, ? extends V> entry : entries.entrySet()) {
operations.add(new MapPutOperation(map, entry.getKey(), entry.getValue()));
HashValue keyHash = toKeyHash(entry.getKey());
state.put(keyHash, new MapEntry(entry.getKey(), entry.getValue()));
}
if (deleted != null) {
deleted = false;
}
result.trySuccess(null);
}
}, (Collection<K>)entries.keySet());
return result;
}
protected RFuture<Boolean> replaceOperationAsync(final K key, final V oldValue, final V newValue) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
if (entry != null) {
if (entry == MapEntry.NULL) {
result.trySuccess(false);
return;
}
operations.add(new MapReplaceOperation(map, key, newValue, oldValue));
if (isEqual(entry.getValue(), oldValue)) {
state.put(keyHash, new MapEntry(key, newValue));
result.trySuccess(true);
return;
}
result.trySuccess(false);
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new MapReplaceOperation(map, key, newValue, oldValue));
boolean res = isEqual(future.getNow(), oldValue);
if (res) {
state.put(keyHash, new MapEntry(key, newValue));
}
result.trySuccess(res);
}
});
}
});
return result;
}
protected RFuture<V> replaceOperationAsync(final K key, final V value) {
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, key, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toKeyHash(key);
MapEntry entry = state.get(keyHash);
operations.add(new MapReplaceOperation(map, key, value));
if (entry != null) {
if (entry == MapEntry.NULL) {
result.trySuccess(null);
return;
}
state.put(keyHash, new MapEntry(key, value));
result.trySuccess((V) entry.getValue());
return;
}
map.getAsync(key).addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new MapReplaceOperation(map, key, value));
if (future.getNow() != null) {
state.put(keyHash, new MapEntry(key, value));
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected <R> void executeLocked(final RPromise<R> promise, K key, final Runnable runnable) {
RLock lock = map.getLock(key);
executeLocked(promise, runnable, lock);
}
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, RLock lock) {
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
runnable.run();
} else {
promise.tryFailure(future.cause());
}
}
});
}
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, Collection<K> keys) {
List<RLock> locks = new ArrayList<RLock>(keys.size());
for (K key : keys) {
RLock lock = map.getLock(key);
locks.add(lock);
}
final RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
final long threadId = Thread.currentThread().getId();
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
runnable.run();
} else {
multiLock.unlockAsync(threadId);
promise.tryFailure(future.cause());
}
}
});
}
}

@ -0,0 +1,60 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RMap;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.map.MapCacheFastPutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapCacheFastPutOperation;
import org.redisson.transaction.operation.map.MapCachePutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapCachePutOperation;
/**
*
* @author Nikita Koksharov
*
* @param <K> key type
* @param <V> value type
*/
public class BaseTransactionalMapCache<K, V> extends BaseTransactionalMap<K, V> {
public BaseTransactionalMapCache(long timeout, List<TransactionalOperation> operations, RMap<K, V> map) {
super(timeout, operations, map);
}
public RFuture<V> putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return putIfAbsentOperationAsync(key, value, new MapCachePutIfAbsentOperation(map, key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit));
}
public RFuture<Boolean> fastPutOperationAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return fastPutOperationAsync(key, value, new MapCacheFastPutOperation(map, key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit));
}
public RFuture<V> putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout, long maxIdleDelta) {
return putOperationAsync(key, value, new MapCachePutOperation(map, key, value,
ttlTimeout, TimeUnit.MILLISECONDS, maxIdleTimeout, TimeUnit.MILLISECONDS));
}
public RFuture<Boolean> fastPutIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return fastPutIfAbsentOperationAsync(key, value, new MapCacheFastPutIfAbsentOperation(map, key, value,
ttl, ttlUnit, maxIdleTime, maxIdleUnit));
}
}

@ -0,0 +1,55 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
/**
*
* @author Nikita Koksharov
*
*/
public class BaseTransactionalObject {
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("move method is not supported in transaction");
}
public RFuture<Void> migrateAsync(String host, int port, int database) {
throw new UnsupportedOperationException("migrate method is not supported in transaction");
}
}

@ -0,0 +1,570 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonObject;
import org.redisson.RedissonSet;
import org.redisson.api.RCollectionAsync;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RObject;
import org.redisson.api.RSet;
import org.redisson.api.SortOrder;
import org.redisson.client.RedisClient;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.Hash;
import org.redisson.misc.HashValue;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.transaction.operation.DeleteOperation;
import org.redisson.transaction.operation.TouchOperation;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.UnlinkOperation;
import org.redisson.transaction.operation.set.MoveOperation;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public abstract class BaseTransactionalSet<V> extends BaseTransactionalObject {
static final Object NULL = new Object();
private final long timeout;
final Map<HashValue, Object> state = new HashMap<HashValue, Object>();
final List<TransactionalOperation> operations;
final RCollectionAsync<V> set;
final RObject object;
final String name;
final CommandAsyncExecutor commandExecutor;
Boolean deleted;
public BaseTransactionalSet(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations, RCollectionAsync<V> set) {
this.commandExecutor = commandExecutor;
this.timeout = timeout;
this.operations = operations;
this.set = set;
this.object = (RObject) set;
this.name = object.getName();
}
private HashValue toHash(Object value) {
ByteBuf state = ((RedissonObject)set).encode(value);
try {
return new HashValue(Hash.hash128(state));
} finally {
state.release();
}
}
public RFuture<Boolean> isExistsAsync() {
if (deleted != null) {
return RedissonPromise.newSucceededFuture(!deleted);
}
return set.isExistsAsync();
}
public RFuture<Boolean> unlinkAsync(CommandAsyncExecutor commandExecutor) {
return deleteAsync(commandExecutor, new UnlinkOperation(name));
}
public RFuture<Boolean> touchAsync(CommandAsyncExecutor commandExecutor) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
if (deleted != null && deleted) {
operations.add(new TouchOperation(name));
result.trySuccess(false);
return result;
}
set.isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new TouchOperation(name));
boolean exists = future.getNow();
if (!exists) {
for (Object value : state.values()) {
if (value != NULL) {
exists = true;
break;
}
}
}
result.trySuccess(exists);
}
});
return result;
}
public RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor) {
return deleteAsync(commandExecutor, new DeleteOperation(name));
}
protected RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor, final TransactionalOperation operation) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
if (deleted != null) {
operations.add(operation);
result.trySuccess(!deleted);
deleted = true;
return result;
}
set.isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
for (HashValue key : state.keySet()) {
state.put(key, NULL);
}
deleted = true;
result.trySuccess(future.getNow());
}
});
return result;
}
public RFuture<Boolean> containsAsync(Object value) {
for (Object val : state.values()) {
if (val != NULL && isEqual(val, value)) {
return RedissonPromise.newSucceededFuture(true);
}
}
return set.containsAsync(value);
}
protected abstract ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client,
long startPos, String pattern);
protected ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client,
long startPos, String pattern) {
ListScanResult<ScanObjectEntry> res = scanIteratorSource(name, client, startPos, pattern);
Map<HashValue, Object> newstate = new HashMap<HashValue, Object>(state);
for (Iterator<ScanObjectEntry> iterator = res.getValues().iterator(); iterator.hasNext();) {
ScanObjectEntry entry = iterator.next();
Object value = newstate.remove(entry.getHash());
if (value == NULL) {
iterator.remove();
}
}
if (startPos == 0) {
for (Entry<HashValue, Object> entry : newstate.entrySet()) {
if (entry.getValue() == NULL) {
continue;
}
res.getValues().add(new ScanObjectEntry(entry.getKey(), entry.getValue()));
}
}
return res;
}
protected abstract RFuture<Set<V>> readAllAsyncSource();
public RFuture<Set<V>> readAllAsync() {
final RPromise<Set<V>> result = new RedissonPromise<Set<V>>();
RFuture<Set<V>> future = readAllAsyncSource();
future.addListener(new FutureListener<Set<V>>() {
@Override
public void operationComplete(Future<Set<V>> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
Set<V> set = future.getNow();
Map<HashValue, Object> newstate = new HashMap<HashValue, Object>(state);
for (Iterator<V> iterator = set.iterator(); iterator.hasNext();) {
V key = iterator.next();
Object value = newstate.remove(toHash(key));
if (value == NULL) {
iterator.remove();
}
}
for (Object value : newstate.values()) {
if (value == NULL) {
continue;
}
set.add((V) value);
}
result.trySuccess(set);
}
});
return result;
}
public RFuture<Boolean> addAsync(V value) {
final TransactionalOperation operation = createAddOperation(value);
return addAsync(value, operation);
}
public RFuture<Boolean> addAsync(final V value, final TransactionalOperation operation) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, value, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toHash(value);
Object entry = state.get(keyHash);
if (entry != null) {
operations.add(operation);
state.put(keyHash, value);
if (deleted != null) {
deleted = false;
}
result.trySuccess(entry == NULL);
return;
}
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
state.put(keyHash, value);
if (deleted != null) {
deleted = false;
}
result.trySuccess(!future.getNow());
}
});
}
});
return result;
}
protected abstract TransactionalOperation createAddOperation(V value);
public RFuture<V> removeRandomAsync() {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> removeRandomAsync(int amount) {
throw new UnsupportedOperationException();
}
public RFuture<Boolean> moveAsync(final String destination, final V value) {
RSet<V> destinationSet = new RedissonSet<V>(object.getCodec(), commandExecutor, destination, null);
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
RLock destinationLock = destinationSet.getLock(value);
RLock lock = getLock(value);
final RedissonMultiLock multiLock = new RedissonMultiLock(destinationLock, lock);
final long threadId = Thread.currentThread().getId();
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (!future.isSuccess()) {
multiLock.unlockAsync(threadId);
result.tryFailure(future.cause());
return;
}
final HashValue keyHash = toHash(value);
Object currentValue = state.get(keyHash);
if (currentValue != null) {
operations.add(createMoveOperation(destination, value, threadId));
if (currentValue == NULL) {
result.trySuccess(false);
} else {
state.put(keyHash, NULL);
result.trySuccess(true);
}
return;
}
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(createMoveOperation(destination, value, threadId));
if (future.getNow()) {
state.put(keyHash, NULL);
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected abstract MoveOperation createMoveOperation(String destination, V value, long threadId);
protected abstract RLock getLock(V value);
public RFuture<Boolean> removeAsync(final Object value) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, (V)value, new Runnable() {
@Override
public void run() {
final HashValue keyHash = toHash(value);
Object currentValue = state.get(keyHash);
if (currentValue != null) {
operations.add(createRemoveOperation(value));
if (currentValue == NULL) {
result.trySuccess(false);
} else {
state.put(keyHash, NULL);
result.trySuccess(true);
}
return;
}
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(createRemoveOperation(value));
if (future.getNow()) {
state.put(keyHash, NULL);
}
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
protected abstract TransactionalOperation createRemoveOperation(Object value);
public RFuture<Boolean> containsAllAsync(Collection<?> c) {
List<Object> coll = new ArrayList<Object>(c);
for (Iterator<Object> iterator = coll.iterator(); iterator.hasNext();) {
Object value = iterator.next();
for (Object val : state.values()) {
if (val != NULL && isEqual(val, value)) {
iterator.remove();
break;
}
}
}
return set.containsAllAsync(coll);
}
public RFuture<Boolean> addAllAsync(final Collection<? extends V> c) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
containsAllAsync(c).addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
for (V value : c) {
operations.add(createAddOperation(value));
HashValue keyHash = toHash(value);
state.put(keyHash, value);
}
if (deleted != null) {
deleted = false;
}
result.trySuccess(!future.getNow());
}
});
}
}, c);
return result;
}
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
throw new UnsupportedOperationException();
}
public RFuture<Boolean> removeAllAsync(final Collection<?> c) {
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
containsAllAsync(c).addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
for (Object value : c) {
operations.add(createRemoveOperation(value));
HashValue keyHash = toHash(value);
state.put(keyHash, NULL);
}
result.trySuccess(!future.getNow());
}
});
}
}, c);
return result;
}
public RFuture<Integer> unionAsync(String... names) {
throw new UnsupportedOperationException();
}
public RFuture<Integer> diffAsync(String... names) {
throw new UnsupportedOperationException();
}
public RFuture<Integer> intersectionAsync(String... names) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readSortAsync(SortOrder order) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
throw new UnsupportedOperationException();
}
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
throw new UnsupportedOperationException();
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readUnionAsync(String... names) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readDiffAsync(String... names) {
throw new UnsupportedOperationException();
}
public RFuture<Set<V>> readIntersectionAsync(String... names) {
throw new UnsupportedOperationException();
}
private boolean isEqual(Object value, Object oldValue) {
ByteBuf valueBuf = ((RedissonObject)set).encode(value);
ByteBuf oldValueBuf = ((RedissonObject)set).encode(oldValue);
try {
return valueBuf.equals(oldValueBuf);
} finally {
valueBuf.readableBytes();
oldValueBuf.readableBytes();
}
}
protected <R> void executeLocked(final RPromise<R> promise, Object value, final Runnable runnable) {
RLock lock = getLock((V) value);
executeLocked(promise, runnable, lock);
}
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, RLock lock) {
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
runnable.run();
} else {
promise.tryFailure(future.cause());
}
}
});
}
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, Collection<?> values) {
List<RLock> locks = new ArrayList<RLock>(values.size());
for (Object value : values) {
RLock lock = getLock((V) value);
locks.add(lock);
}
final RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
final long threadId = Thread.currentThread().getId();
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
runnable.run();
} else {
multiLock.unlockAsync(threadId);
promise.tryFailure(future.cause());
}
}
});
}
}

@ -0,0 +1,367 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonBatch;
import org.redisson.RedissonLocalCachedMap;
import org.redisson.RedissonObject;
import org.redisson.RedissonTopic;
import org.redisson.api.BatchOptions;
import org.redisson.api.RBucket;
import org.redisson.api.RFuture;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.RMultimapCacheAsync;
import org.redisson.api.RSet;
import org.redisson.api.RSetCache;
import org.redisson.api.RTopic;
import org.redisson.api.RTopicAsync;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
import org.redisson.api.listener.MessageListener;
import org.redisson.cache.LocalCachedMapDisable;
import org.redisson.cache.LocalCachedMapDisabledKey;
import org.redisson.cache.LocalCachedMapEnable;
import org.redisson.cache.LocalCachedMessageCodec;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandBatchService;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.map.MapOperation;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonTransaction implements RTransaction {
private final CommandAsyncExecutor commandExecutor;
private final AtomicBoolean executed = new AtomicBoolean();
private final TransactionOptions options;
private final List<TransactionalOperation> operations = new ArrayList<TransactionalOperation>();
private final Set<String> localCaches = new HashSet<String>();
private final long startTime = System.currentTimeMillis();
public RedissonTransaction(CommandAsyncExecutor commandExecutor, TransactionOptions options) {
super();
this.options = options;
this.commandExecutor = commandExecutor;
}
@Override
public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(RLocalCachedMap<K, V> fromInstance) {
checkState();
localCaches.add(fromInstance.getName());
return new RedissonTransactionalLocalCachedMap<K, V>(commandExecutor,
operations, options.getTimeout(), executed, fromInstance);
}
@Override
public <V> RBucket<V> getBucket(String name) {
checkState();
return new RedissonTransactionalBucket<V>(commandExecutor, name, operations, executed);
}
@Override
public <V> RBucket<V> getBucket(String name, Codec codec) {
checkState();
return new RedissonTransactionalBucket<V>(codec, commandExecutor, name, operations, executed);
}
@Override
public <V> RSet<V> getSet(String name) {
checkState();
return new RedissonTransactionalSet<V>(commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <V> RSet<V> getSet(String name, Codec codec) {
checkState();
return new RedissonTransactionalSet<V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <V> RSetCache<V> getSetCache(String name) {
checkState();
return new RedissonTransactionalSetCache<V>(commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <V> RSetCache<V> getSetCache(String name, Codec codec) {
checkState();
return new RedissonTransactionalSetCache<V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <K, V> RMap<K, V> getMap(String name) {
checkState();
return new RedissonTransactionalMap<K, V>(commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <K, V> RMap<K, V> getMap(String name, Codec codec) {
checkState();
return new RedissonTransactionalMap<K, V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <K, V> RMapCache<K, V> getMapCache(String name) {
checkState();
return new RedissonTransactionalMapCache<K, V>(commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public <K, V> RMapCache<K, V> getMapCache(String name, Codec codec) {
checkState();
return new RedissonTransactionalMapCache<K, V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
}
@Override
public void commit() {
checkState();
checkTimeout();
CommandBatchService transactionExecutor = new CommandBatchService(commandExecutor.getConnectionManager());
for (TransactionalOperation transactionalOperation : operations) {
transactionalOperation.commit(transactionExecutor);
}
String id = generateId();
Map<TransactionalOperation, List<byte[]>> hashes = disableLocalCache(id);
try {
checkTimeout();
} catch (TransactionTimeoutException e) {
enableLocalCache(id, hashes);
throw e;
}
int syncSlaves = 0;
if (!commandExecutor.getConnectionManager().isClusterMode()) {
MasterSlaveEntry entry = commandExecutor.getConnectionManager().getEntrySet().iterator().next();
syncSlaves = entry.getAvailableClients() - 1;
}
try {
BatchOptions batchOptions = BatchOptions.defaults()
.syncSlaves(syncSlaves, options.getSyncTimeout(), TimeUnit.MILLISECONDS)
.responseTimeout(options.getResponseTimeout(), TimeUnit.MILLISECONDS)
.retryAttempts(options.getRetryAttempts())
.retryInterval(options.getRetryInterval(), TimeUnit.MILLISECONDS)
.atomic();
transactionExecutor.execute(batchOptions);
} catch (Exception e) {
throw new TransactionException("Unable to execute transaction", e);
}
enableLocalCache(id, hashes);
executed.set(true);
}
private void checkTimeout() {
if (System.currentTimeMillis() - startTime > options.getTimeout()) {
throw new TransactionTimeoutException("Transaction was discarded due to timeout " + options.getTimeout() + " milliseconds");
}
}
private void enableLocalCache(String requestId, Map<TransactionalOperation, List<byte[]>> hashes) {
if (hashes.isEmpty()) {
return;
}
RedissonBatch publishBatch = new RedissonBatch(null, commandExecutor.getConnectionManager(), BatchOptions.defaults());
for (Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
String name = RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.TOPIC_SUFFIX);
RTopicAsync<Object> topic = publishBatch.getTopic(name, LocalCachedMessageCodec.INSTANCE);
LocalCachedMapEnable msg = new LocalCachedMapEnable(requestId, entry.getValue().toArray(new byte[entry.getValue().size()][]));
topic.publishAsync(msg);
}
try {
publishBatch.execute();
} catch (Exception e) {
// skip it. Disabled local cache entries are enabled once reach timeout.
}
}
private Map<TransactionalOperation, List<byte[]>> disableLocalCache(String requestId) {
if (localCaches.isEmpty()) {
return Collections.emptyMap();
}
Map<TransactionalOperation, List<byte[]>> hashes = new HashMap<TransactionalOperation, List<byte[]>>(localCaches.size());
RedissonBatch batch = new RedissonBatch(null, commandExecutor.getConnectionManager(), BatchOptions.defaults());
for (TransactionalOperation transactionalOperation : operations) {
if (localCaches.contains(transactionalOperation.getName())) {
MapOperation mapOperation = (MapOperation) transactionalOperation;
RedissonLocalCachedMap<?, ?> map = (RedissonLocalCachedMap<?, ?>)mapOperation.getMap();
byte[] key = map.toCacheKey(mapOperation.getKey()).getKeyHash();
List<byte[]> list = hashes.get(transactionalOperation);
if (list == null) {
list = new ArrayList<byte[]>();
hashes.put(transactionalOperation, list);
}
list.add(key);
String disabledKeysName = RedissonObject.suffixName(transactionalOperation.getName(), RedissonLocalCachedMap.DISABLED_KEYS_SUFFIX);
RMultimapCacheAsync<LocalCachedMapDisabledKey, String> multimap = batch.getListMultimapCache(disabledKeysName, transactionalOperation.getCodec());
LocalCachedMapDisabledKey localCacheKey = new LocalCachedMapDisabledKey(requestId, options.getResponseTimeout());
multimap.putAsync(localCacheKey, ByteBufUtil.hexDump(key));
multimap.expireKeyAsync(localCacheKey, options.getResponseTimeout(), TimeUnit.MILLISECONDS);
}
}
try {
batch.execute();
} catch (Exception e) {
throw new TransactionException("Unable to execute transaction over local cached map objects: " + localCaches, e);
}
final Map<String, AtomicInteger> map = new HashMap<String, AtomicInteger>();
final CountDownLatch latch = new CountDownLatch(hashes.size());
List<RTopic<Object>> topics = new ArrayList<RTopic<Object>>();
for (final Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
RTopic<Object> topic = new RedissonTopic<Object>(LocalCachedMessageCodec.INSTANCE,
commandExecutor, RedissonObject.suffixName(entry.getKey().getName(), requestId + RedissonLocalCachedMap.DISABLED_ACK_SUFFIX));
topics.add(topic);
map.put(entry.getKey().getName(), new AtomicInteger());
topic.addListener(new MessageListener<Object>() {
@Override
public void onMessage(String channel, Object msg) {
AtomicInteger counter = map.get(entry.getKey().getName());
if (counter.decrementAndGet() == 0) {
latch.countDown();
}
}
});
}
RedissonBatch publishBatch = new RedissonBatch(null, commandExecutor.getConnectionManager(), BatchOptions.defaults());
for (final Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
String disabledKeysName = RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.DISABLED_KEYS_SUFFIX);
RMultimapCacheAsync<LocalCachedMapDisabledKey, String> multimap = publishBatch.getListMultimapCache(disabledKeysName, entry.getKey().getCodec());
LocalCachedMapDisabledKey localCacheKey = new LocalCachedMapDisabledKey(requestId, options.getResponseTimeout());
multimap.removeAllAsync(localCacheKey);
RTopicAsync<Object> topic = publishBatch.getTopic(RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.TOPIC_SUFFIX), LocalCachedMessageCodec.INSTANCE);
RFuture<Long> future = topic.publishAsync(new LocalCachedMapDisable(requestId,
entry.getValue().toArray(new byte[entry.getValue().size()][]), options.getResponseTimeout()));
future.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
return;
}
int receivers = future.getNow().intValue();
AtomicInteger counter = map.get(entry.getKey().getName());
if (counter.addAndGet(receivers) == 0) {
latch.countDown();
}
}
});
}
try {
publishBatch.execute();
} catch (Exception e) {
throw new TransactionException("Unable to execute transaction over local cached map objects: " + localCaches, e);
}
for (RTopic<Object> topic : topics) {
topic.removeAllListeners();
}
try {
latch.await(options.getResponseTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return hashes;
}
protected static String generateId() {
byte[] id = new byte[16];
// TODO JDK UPGRADE replace to native ThreadLocalRandom
PlatformDependent.threadLocalRandom().nextBytes(id);
return ByteBufUtil.hexDump(id);
}
@Override
public void rollback() {
checkState();
CommandBatchService executorService = new CommandBatchService(commandExecutor.getConnectionManager());
for (TransactionalOperation transactionalOperation : operations) {
transactionalOperation.rollback(executorService);
}
try {
executorService.execute(BatchOptions.defaults());
} catch (Exception e) {
throw new TransactionException("Unable to execute transaction", e);
}
operations.clear();
executed.set(true);
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction was finished!");
}
}
}

@ -0,0 +1,518 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.transaction.operation.DeleteOperation;
import org.redisson.transaction.operation.TouchOperation;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.UnlinkOperation;
import org.redisson.transaction.operation.bucket.BucketCompareAndSetOperation;
import org.redisson.transaction.operation.bucket.BucketGetAndDeleteOperation;
import org.redisson.transaction.operation.bucket.BucketGetAndSetOperation;
import org.redisson.transaction.operation.bucket.BucketSetOperation;
import org.redisson.transaction.operation.bucket.BucketTrySetOperation;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonTransactionalBucket<V> extends RedissonBucket<V> {
static final Object NULL = new Object();
private long timeout;
private final AtomicBoolean executed;
private final List<TransactionalOperation> operations;
private Object state;
public RedissonTransactionalBucket(CommandAsyncExecutor commandExecutor, String name, List<TransactionalOperation> operations, AtomicBoolean executed) {
super(commandExecutor, name);
this.operations = operations;
this.executed = executed;
}
public RedissonTransactionalBucket(Codec codec, CommandAsyncExecutor commandExecutor, String name, List<TransactionalOperation> operations, AtomicBoolean executed) {
super(codec, commandExecutor, name);
this.operations = operations;
this.executed = executed;
}
@Override
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
@Override
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("moveAsync method is not supported in transaction");
}
@Override
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrateAsync method is not supported in transaction");
}
@Override
public RFuture<Long> sizeAsync() {
checkState();
if (state != null) {
if (state == NULL) {
return RedissonPromise.newSucceededFuture(0L);
} else {
ByteBuf buf = encode(state);
long size = buf.readableBytes();
buf.release();
return RedissonPromise.newSucceededFuture(size);
}
}
return super.sizeAsync();
}
@Override
public RFuture<Boolean> isExistsAsync() {
checkState();
if (state != null) {
if (state == NULL) {
return RedissonPromise.newSucceededFuture(null);
} else {
return RedissonPromise.newSucceededFuture(true);
}
}
return super.isExistsAsync();
}
@Override
public RFuture<Boolean> touchAsync() {
checkState();
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
operations.add(new TouchOperation(getName(), getLockName()));
result.trySuccess(state != NULL);
return;
}
isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new TouchOperation(getName(), getLockName()));
result.trySuccess(future.getNow());
}
});
result.trySuccess(null);
}
});
return result;
}
@Override
public RFuture<Boolean> unlinkAsync() {
checkState();
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
operations.add(new UnlinkOperation(getName(), getLockName()));
if (state == NULL) {
result.trySuccess(false);
} else {
state = NULL;
result.trySuccess(true);
}
return;
}
isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new UnlinkOperation(getName(), getLockName()));
state = NULL;
result.trySuccess(future.getNow());
}
});
result.trySuccess(null);
}
});
return result;
}
@Override
public RFuture<Boolean> deleteAsync() {
checkState();
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
operations.add(new DeleteOperation(getName(), getLockName()));
if (state == NULL) {
result.trySuccess(false);
} else {
state = NULL;
result.trySuccess(true);
}
return;
}
isExistsAsync().addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new DeleteOperation(getName(), getLockName()));
state = NULL;
result.trySuccess(future.getNow());
}
});
result.trySuccess(null);
}
});
return result;
}
@Override
@SuppressWarnings("unchecked")
public RFuture<V> getAsync() {
checkState();
if (state != null) {
if (state == NULL) {
return RedissonPromise.newSucceededFuture(null);
} else {
return RedissonPromise.newSucceededFuture((V)state);
}
}
return super.getAsync();
}
@Override
public RFuture<Boolean> compareAndSetAsync(final V expect, final V update) {
checkState();
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
operations.add(new BucketCompareAndSetOperation<V>(getName(), getLockName(), getCodec(), expect, update));
if ((state == NULL && expect == null)
|| isEquals(state, expect)) {
if (update == null) {
state = NULL;
} else {
state = update;
}
result.trySuccess(true);
} else {
result.trySuccess(false);
}
return;
}
getAsync().addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(new BucketCompareAndSetOperation<V>(getName(), getLockName(), getCodec(), expect, update));
if ((future.getNow() == null && expect == null)
|| isEquals(future.getNow(), expect)) {
if (update == null) {
state = NULL;
} else {
state = update;
}
result.trySuccess(true);
} else {
result.trySuccess(false);
}
}
});
}
});
return result;
}
@Override
@SuppressWarnings("unchecked")
public RFuture<V> getAndSetAsync(final V newValue) {
checkState();
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
Object prevValue;
if (state == NULL) {
prevValue = null;
} else {
prevValue = state;
}
operations.add(new BucketGetAndSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
if (newValue == null) {
state = NULL;
} else {
state = newValue;
}
result.trySuccess((V) prevValue);
return;
}
getAsync().addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
if (newValue == null) {
state = NULL;
} else {
state = newValue;
}
operations.add(new BucketGetAndSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
@Override
@SuppressWarnings("unchecked")
public RFuture<V> getAndDeleteAsync() {
checkState();
final RPromise<V> result = new RedissonPromise<V>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
Object prevValue;
if (state == NULL) {
prevValue = null;
} else {
prevValue = state;
}
operations.add(new BucketGetAndDeleteOperation<V>(getName(), getLockName(), getCodec()));
state = NULL;
result.trySuccess((V) prevValue);
return;
}
getAsync().addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
state = NULL;
operations.add(new BucketGetAndDeleteOperation<V>(getName(), getLockName(), getCodec()));
result.trySuccess(future.getNow());
}
});
}
});
return result;
}
@Override
public RFuture<Void> setAsync(V newValue) {
return setAsync(newValue, new BucketSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
}
private RFuture<Void> setAsync(final V newValue, final TransactionalOperation operation) {
checkState();
final RPromise<Void> result = new RedissonPromise<Void>();
executeLocked(result, new Runnable() {
@Override
public void run() {
operations.add(operation);
if (newValue == null) {
state = NULL;
} else {
state = newValue;
}
result.trySuccess(null);
}
});
return result;
}
@Override
public RFuture<Void> setAsync(V value, long timeToLive, TimeUnit timeUnit) {
return setAsync(value, new BucketSetOperation<V>(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit));
}
@Override
public RFuture<Boolean> trySetAsync(V newValue) {
return trySet(newValue, new BucketTrySetOperation<V>(getName(), getLockName(), getCodec(), newValue));
}
@Override
public RFuture<Boolean> trySetAsync(V value, long timeToLive, TimeUnit timeUnit) {
return trySet(value, new BucketTrySetOperation<V>(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit));
}
private RFuture<Boolean> trySet(final V newValue, final TransactionalOperation operation) {
checkState();
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
executeLocked(result, new Runnable() {
@Override
public void run() {
if (state != null) {
operations.add(operation);
if (state == NULL) {
if (newValue == null) {
state = NULL;
} else {
state = newValue;
}
result.trySuccess(true);
} else {
result.trySuccess(false);
}
return;
}
getAsync().addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
operations.add(operation);
if (future.getNow() == null) {
if (newValue == null) {
state = NULL;
} else {
state = newValue;
}
result.trySuccess(true);
} else {
result.trySuccess(false);
}
}
});
}
});
return result;
}
private boolean isEquals(Object value, Object oldValue) {
ByteBuf valueBuf = encode(value);
ByteBuf oldValueBuf = encode(oldValue);
try {
return valueBuf.equals(oldValueBuf);
} finally {
valueBuf.readableBytes();
oldValueBuf.readableBytes();
}
}
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable) {
RLock lock = getLock();
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
runnable.run();
} else {
promise.tryFailure(future.cause());
}
}
});
}
private RLock getLock() {
return new RedissonLock(commandExecutor, getLockName());
}
private String getLockName() {
return getName() + ":transaction_lock";
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
}

@ -0,0 +1,60 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.api.RFuture;
import org.redisson.api.RLocalCachedMap;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <K> key type
* @param <V> value type
*/
public class RedissonTransactionalLocalCachedMap<K, V> extends RedissonTransactionalMap<K, V> implements RLocalCachedMap<K, V> {
public RedissonTransactionalLocalCachedMap(CommandAsyncExecutor commandExecutor,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed, RLocalCachedMap<K, V> innerMap) {
super(commandExecutor, operations, timeout, executed, innerMap);
}
@Override
public void destroy() {
throw new UnsupportedOperationException("destroy method is not supported in transaction");
}
@Override
public void preloadCache() {
throw new UnsupportedOperationException("preloadCache method is not supported in transaction");
}
@Override
public RFuture<Void> clearLocalCacheAsync() {
throw new UnsupportedOperationException("clearLocalCache method is not supported in transaction");
}
@Override
public void clearLocalCache() {
throw new UnsupportedOperationException("clearLocalCache method is not supported in transaction");
}
}

@ -0,0 +1,286 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.RedissonMap;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.mapreduce.RMapReduce;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <K> key type
* @param <V> value type
*/
public class RedissonTransactionalMap<K, V> extends RedissonMap<K, V> {
private final BaseTransactionalMap<K, V> transactionalMap;
private final AtomicBoolean executed;
public RedissonTransactionalMap(CommandAsyncExecutor commandExecutor,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed, RMap<K, V> innerMap) {
super(innerMap.getCodec(), commandExecutor, innerMap.getName(), null, null);
this.executed = executed;
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
}
public RedissonTransactionalMap(CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(commandExecutor, name, null, null);
this.executed = executed;
RedissonMap<K, V> innerMap = new RedissonMap<K, V>(commandExecutor, name, null, null);
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
}
public RedissonTransactionalMap(Codec codec, CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(codec, commandExecutor, name, null, null);
this.executed = executed;
RedissonMap<K, V> innerMap = new RedissonMap<K, V>(codec, commandExecutor, name, null, null);
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
}
@Override
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
@Override
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("move method is not supported in transaction");
}
@Override
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrate method is not supported in transaction");
}
@Override
public <KOut, VOut> RMapReduce<K, V, KOut, VOut> mapReduce() {
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
}
@Override
public MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
long startPos, String pattern) {
checkState();
return transactionalMap.scanIterator(name, client, startPos, pattern);
}
@Override
public RFuture<Boolean> containsKeyAsync(Object key) {
checkState();
return transactionalMap.containsKeyAsync(key);
}
@Override
public RFuture<Boolean> containsValueAsync(Object value) {
checkState();
return transactionalMap.containsValueAsync(value);
}
@Override
protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
checkState();
return transactionalMap.addAndGetOperationAsync(key, value);
}
@Override
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
checkState();
return transactionalMap.putIfAbsentOperationAsync(key, value);
}
@Override
protected RFuture<V> putOperationAsync(K key, V value) {
checkState();
return transactionalMap.putOperationAsync(key, value);
}
@Override
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
checkState();
return transactionalMap.fastPutIfAbsentOperationAsync(key, value);
}
@Override
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
checkState();
return transactionalMap.fastPutOperationAsync(key, value);
}
@Override
@SuppressWarnings("unchecked")
protected RFuture<Long> fastRemoveOperationAsync(K... keys) {
checkState();
return transactionalMap.fastRemoveOperationAsync(keys);
}
@Override
public RFuture<Integer> valueSizeAsync(K key) {
checkState();
return transactionalMap.valueSizeAsync(key);
}
@Override
public RFuture<V> getOperationAsync(K key) {
checkState();
return transactionalMap.getOperationAsync(key);
}
@Override
public RFuture<Set<K>> readAllKeySetAsync() {
checkState();
return transactionalMap.readAllKeySetAsync();
}
@Override
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
checkState();
return transactionalMap.readAllEntrySetAsync();
}
@Override
public RFuture<Collection<V>> readAllValuesAsync() {
checkState();
return transactionalMap.readAllValuesAsync();
}
@Override
public RFuture<Map<K, V>> readAllMapAsync() {
checkState();
return transactionalMap.readAllMapAsync();
}
@Override
public RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
checkState();
return transactionalMap.getAllOperationAsync(keys);
}
@Override
protected RFuture<V> removeOperationAsync(K key) {
checkState();
return transactionalMap.removeOperationAsync(key);
}
@Override
protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
checkState();
return transactionalMap.removeOperationAsync(key, value);
}
@Override
protected RFuture<Void> putAllOperationAsync(Map<? extends K, ? extends V> entries) {
checkState();
return transactionalMap.putAllOperationAsync(entries);
}
@Override
protected RFuture<Boolean> replaceOperationAsync(final K key, final V oldValue, final V newValue) {
checkState();
return transactionalMap.replaceOperationAsync(key, oldValue, newValue);
}
@Override
public RFuture<Boolean> touchAsync() {
checkState();
return transactionalMap.touchAsync(commandExecutor);
}
@Override
public RFuture<Boolean> isExistsAsync() {
checkState();
return transactionalMap.isExistsAsync();
}
@Override
public RFuture<Boolean> unlinkAsync() {
return transactionalMap.unlinkAsync(commandExecutor);
}
@Override
public RFuture<Boolean> deleteAsync() {
checkState();
return transactionalMap.deleteAsync(commandExecutor);
}
@Override
protected RFuture<V> replaceOperationAsync(final K key, final V value) {
checkState();
return transactionalMap.replaceOperationAsync(key, value);
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
@Override
public RFuture<Void> loadAllAsync(boolean replaceExistingValues, int parallelism) {
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
}
@Override
public RFuture<Void> loadAllAsync(Set<? extends K> keys, boolean replaceExistingValues, int parallelism) {
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
}
@Override
public RLock getLock(K key) {
throw new UnsupportedOperationException("getLock method is not supported in transaction");
}
@Override
public RReadWriteLock getReadWriteLock(K key) {
throw new UnsupportedOperationException("getReadWriteLock method is not supported in transaction");
}
}

@ -0,0 +1,312 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.RedissonMapCache;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.mapreduce.RMapReduce;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <K> key type
* @param <V> value type
*/
public class RedissonTransactionalMapCache<K, V> extends RedissonMapCache<K, V> {
private final BaseTransactionalMapCache<K, V> transactionalMap;
private final AtomicBoolean executed;
public RedissonTransactionalMapCache(CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(null, commandExecutor, name, null, null);
this.executed = executed;
RedissonMapCache<K, V> innerMap = new RedissonMapCache<K, V>(null, commandExecutor, name, null, null);
this.transactionalMap = new BaseTransactionalMapCache<K, V>(timeout, operations, innerMap);
}
public RedissonTransactionalMapCache(Codec codec, CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(codec, null, commandExecutor, name, null, null);
this.executed = executed;
RedissonMapCache<K, V> innerMap = new RedissonMapCache<K, V>(codec, null, commandExecutor, name, null, null);
this.transactionalMap = new BaseTransactionalMapCache<K, V>(timeout, operations, innerMap);
}
@Override
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
@Override
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("moveAsync method is not supported in transaction");
}
@Override
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrateAsync method is not supported in transaction");
}
@Override
public RFuture<Boolean> touchAsync() {
checkState();
return transactionalMap.touchAsync(commandExecutor);
}
@Override
public RFuture<Boolean> isExistsAsync() {
checkState();
return transactionalMap.isExistsAsync();
}
@Override
public RFuture<Boolean> unlinkAsync() {
return transactionalMap.unlinkAsync(commandExecutor);
}
@Override
public RFuture<Boolean> deleteAsync() {
checkState();
return transactionalMap.deleteAsync(commandExecutor);
}
@Override
public RFuture<V> putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return transactionalMap.putIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
}
@Override
public RFuture<Boolean> fastPutOperationAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return transactionalMap.fastPutOperationAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
}
@Override
public RFuture<V> putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout, long maxIdleDelta) {
return transactionalMap.putOperationAsync(key, value, ttlTimeout, maxIdleTimeout, maxIdleDelta);
}
public RFuture<Boolean> fastPutIfAbsentAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
return transactionalMap.fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
}
@Override
public RFuture<Long> remainTimeToLiveAsync() {
throw new UnsupportedOperationException("remainTimeToLiveAsync method is not supported in transaction");
}
@Override
public RFuture<Void> setMaxSizeAsync(int maxSize) {
throw new UnsupportedOperationException("setMaxSize method is not supported in transaction");
}
@Override
public RFuture<Boolean> trySetMaxSizeAsync(int maxSize) {
throw new UnsupportedOperationException("trySetMaxSize method is not supported in transaction");
}
@Override
public <KOut, VOut> RMapReduce<K, V, KOut, VOut> mapReduce() {
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
}
@Override
public MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
long startPos, String pattern) {
checkState();
return transactionalMap.scanIterator(name, client, startPos, pattern);
}
@Override
public RFuture<Boolean> containsKeyAsync(Object key) {
checkState();
return transactionalMap.containsKeyAsync(key);
}
@Override
public RFuture<Boolean> containsValueAsync(Object value) {
checkState();
return transactionalMap.containsValueAsync(value);
}
@Override
protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
checkState();
return transactionalMap.addAndGetOperationAsync(key, value);
}
@Override
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
checkState();
return transactionalMap.putIfAbsentOperationAsync(key, value);
}
@Override
protected RFuture<V> putOperationAsync(K key, V value) {
checkState();
return transactionalMap.putOperationAsync(key, value);
}
@Override
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
checkState();
return transactionalMap.fastPutIfAbsentOperationAsync(key, value);
}
@Override
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
checkState();
return transactionalMap.fastPutOperationAsync(key, value);
}
@Override
@SuppressWarnings("unchecked")
protected RFuture<Long> fastRemoveOperationAsync(K... keys) {
checkState();
return transactionalMap.fastRemoveOperationAsync(keys);
}
@Override
public RFuture<Integer> valueSizeAsync(K key) {
checkState();
return transactionalMap.valueSizeAsync(key);
}
@Override
public RFuture<V> getOperationAsync(K key) {
checkState();
return transactionalMap.getOperationAsync(key);
}
@Override
public RFuture<Set<K>> readAllKeySetAsync() {
checkState();
return transactionalMap.readAllKeySetAsync();
}
@Override
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
checkState();
return transactionalMap.readAllEntrySetAsync();
}
@Override
public RFuture<Collection<V>> readAllValuesAsync() {
checkState();
return transactionalMap.readAllValuesAsync();
}
@Override
public RFuture<Map<K, V>> readAllMapAsync() {
checkState();
return transactionalMap.readAllMapAsync();
}
@Override
public RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
checkState();
return transactionalMap.getAllOperationAsync(keys);
}
@Override
protected RFuture<V> removeOperationAsync(K key) {
checkState();
return transactionalMap.removeOperationAsync(key);
}
@Override
protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
checkState();
return transactionalMap.removeOperationAsync(key, value);
}
@Override
protected RFuture<Void> putAllOperationAsync(Map<? extends K, ? extends V> entries) {
checkState();
return transactionalMap.putAllOperationAsync(entries);
}
@Override
protected RFuture<Boolean> replaceOperationAsync(K key, V oldValue, V newValue) {
checkState();
return transactionalMap.replaceOperationAsync(key, oldValue, newValue);
}
@Override
protected RFuture<V> replaceOperationAsync(K key, V value) {
checkState();
return transactionalMap.replaceOperationAsync(key, value);
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
@Override
public RFuture<Void> loadAllAsync(boolean replaceExistingValues, int parallelism) {
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
}
@Override
public RFuture<Void> loadAllAsync(Set<? extends K> keys, boolean replaceExistingValues, int parallelism) {
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
}
@Override
public RLock getLock(K key) {
throw new UnsupportedOperationException("getLock method is not supported in transaction");
}
@Override
public RReadWriteLock getReadWriteLock(K key) {
throw new UnsupportedOperationException("getReadWriteLock method is not supported in transaction");
}
}

@ -0,0 +1,237 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.RedissonSet;
import org.redisson.api.RFuture;
import org.redisson.api.SortOrder;
import org.redisson.api.mapreduce.RCollectionMapReduce;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonTransactionalSet<V> extends RedissonSet<V> {
private final TransactionalSet<V> transactionalSet;
private final AtomicBoolean executed;
public RedissonTransactionalSet(CommandAsyncExecutor commandExecutor,
String name, List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(commandExecutor, name, null);
this.executed = executed;
RedissonSet<V> innerSet = new RedissonSet<V>(commandExecutor, name, null);
this.transactionalSet = new TransactionalSet<V>(commandExecutor, timeout, operations, innerSet);
}
public RedissonTransactionalSet(Codec codec, CommandAsyncExecutor commandExecutor,
String name, List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(codec, commandExecutor, name, null);
this.executed = executed;
RedissonSet<V> innerSet = new RedissonSet<V>(codec, commandExecutor, name, null);
this.transactionalSet = new TransactionalSet<V>(commandExecutor, timeout, operations, innerSet);
}
@Override
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
@Override
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("move method is not supported in transaction");
}
@Override
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrate method is not supported in transaction");
}
@Override
public <KOut, VOut> RCollectionMapReduce<V, KOut, VOut> mapReduce() {
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
}
@Override
public ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client, long startPos, String pattern) {
checkState();
return transactionalSet.scanIterator(name, client, startPos, pattern);
}
@Override
public RFuture<Boolean> containsAsync(Object o) {
checkState();
return transactionalSet.containsAsync(o);
}
@Override
public RFuture<Set<V>> readAllAsync() {
checkState();
return transactionalSet.readAllAsync();
}
@Override
public RFuture<Boolean> addAsync(V e) {
checkState();
return transactionalSet.addAsync(e);
}
@Override
public RFuture<V> removeRandomAsync() {
checkState();
return transactionalSet.removeRandomAsync();
}
@Override
public RFuture<Set<V>> removeRandomAsync(int amount) {
checkState();
return transactionalSet.removeRandomAsync(amount);
}
@Override
public RFuture<Boolean> removeAsync(Object o) {
checkState();
return transactionalSet.removeAsync(o);
}
@Override
public RFuture<Boolean> moveAsync(String destination, V member) {
checkState();
return transactionalSet.moveAsync(destination, member);
}
@Override
public RFuture<Boolean> addAllAsync(Collection<? extends V> c) {
checkState();
return transactionalSet.addAllAsync(c);
}
@Override
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
checkState();
return transactionalSet.retainAllAsync(c);
}
@Override
public RFuture<Boolean> removeAllAsync(Collection<?> c) {
checkState();
return transactionalSet.removeAllAsync(c);
}
@Override
public RFuture<Integer> unionAsync(String... names) {
checkState();
return transactionalSet.unionAsync(names);
}
@Override
public RFuture<Integer> diffAsync(String... names) {
checkState();
return transactionalSet.diffAsync(names);
}
@Override
public RFuture<Integer> intersectionAsync(String... names) {
checkState();
return transactionalSet.intersectionAsync(names);
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order) {
checkState();
return transactionalSet.readSortAsync(order);
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
checkState();
return transactionalSet.readSortAsync(order, offset, count);
}
@Override
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
checkState();
return transactionalSet.readSortAsync(byPattern, order);
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order,
int offset, int count) {
checkState();
return transactionalSet.readSortAsync(byPattern, getPatterns, order, offset, count);
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
checkState();
return transactionalSet.sortToAsync(destName, byPattern, getPatterns, order, offset, count);
}
@Override
public RFuture<Set<V>> readUnionAsync(String... names) {
checkState();
return transactionalSet.readUnionAsync(names);
}
@Override
public RFuture<Set<V>> readDiffAsync(String... names) {
checkState();
return transactionalSet.readDiffAsync(names);
}
@Override
public RFuture<Set<V>> readIntersectionAsync(String... names) {
checkState();
return transactionalSet.readIntersectionAsync(names);
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
}

@ -0,0 +1,158 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.redisson.RedissonSetCache;
import org.redisson.api.RFuture;
import org.redisson.api.mapreduce.RCollectionMapReduce;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonTransactionalSetCache<V> extends RedissonSetCache<V> {
private final TransactionalSetCache<V> transactionalSet;
private final AtomicBoolean executed;
public RedissonTransactionalSetCache(CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(null, commandExecutor, name, null);
this.executed = executed;
RedissonSetCache<V> innerSet = new RedissonSetCache<V>(null, commandExecutor, name, null);
this.transactionalSet = new TransactionalSetCache<V>(commandExecutor, timeout, operations, innerSet);
}
public RedissonTransactionalSetCache(Codec codec, CommandAsyncExecutor commandExecutor, String name,
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
super(null, commandExecutor, name, null);
this.executed = executed;
RedissonSetCache<V> innerSet = new RedissonSetCache<V>(codec, null, commandExecutor, name, null);
this.transactionalSet = new TransactionalSetCache<V>(commandExecutor, timeout, operations, innerSet);
}
@Override
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
throw new UnsupportedOperationException("expire method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(Date timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> expireAtAsync(long timestamp) {
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
}
@Override
public RFuture<Boolean> clearExpireAsync() {
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
}
@Override
public RFuture<Boolean> moveAsync(int database) {
throw new UnsupportedOperationException("move method is not supported in transaction");
}
@Override
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrate method is not supported in transaction");
}
@Override
public <KOut, VOut> RCollectionMapReduce<V, KOut, VOut> mapReduce() {
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
}
@Override
public ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client, long startPos, String pattern) {
checkState();
return transactionalSet.scanIterator(name, client, startPos, pattern);
}
@Override
public RFuture<Boolean> containsAsync(Object o) {
checkState();
return transactionalSet.containsAsync(o);
}
@Override
public RFuture<Set<V>> readAllAsync() {
checkState();
return transactionalSet.readAllAsync();
}
@Override
public RFuture<Boolean> addAsync(V e) {
checkState();
return transactionalSet.addAsync(e);
}
@Override
public RFuture<Boolean> addAsync(V value, long ttl, TimeUnit unit) {
checkState();
return transactionalSet.addAsync(value, ttl, unit);
}
@Override
public RFuture<Boolean> removeAsync(Object o) {
checkState();
return transactionalSet.removeAsync(o);
}
@Override
public RFuture<Boolean> addAllAsync(Collection<? extends V> c) {
checkState();
return transactionalSet.addAllAsync(c);
}
@Override
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
checkState();
return transactionalSet.retainAllAsync(c);
}
@Override
public RFuture<Boolean> removeAllAsync(Collection<?> c) {
checkState();
return transactionalSet.removeAllAsync(c);
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
}

@ -0,0 +1,39 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import org.redisson.client.RedisException;
/**
* This exception used to report an error during Transaction execution.
*
* @author Nikita Koksharov
*
*/
public class TransactionException extends RedisException {
private static final long serialVersionUID = 7126673140273327142L;
public TransactionException(String message) {
super(message);
}
public TransactionException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,32 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
/**
* This exception used to report an error during Transaction execution.
*
* @author Nikita Koksharov
*
*/
public class TransactionTimeoutException extends TransactionException {
private static final long serialVersionUID = 7126673140273327142L;
public TransactionTimeoutException(String message) {
super(message);
}
}

@ -0,0 +1,81 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.List;
import java.util.Set;
import org.redisson.RedissonSet;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RSet;
import org.redisson.client.RedisClient;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.set.AddOperation;
import org.redisson.transaction.operation.set.MoveOperation;
import org.redisson.transaction.operation.set.RemoveOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class TransactionalSet<V> extends BaseTransactionalSet<V> {
private final RSet<V> set;
public TransactionalSet(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations,
RSet<V> set) {
super(commandExecutor, timeout, operations, set);
this.set = set;
}
@Override
protected ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client, long startPos,
String pattern) {
return ((RedissonSet<?>)set).scanIterator(name, client, startPos, pattern);
}
@Override
protected RFuture<Set<V>> readAllAsyncSource() {
return set.readAllAsync();
}
@Override
protected TransactionalOperation createAddOperation(final V value) {
return new AddOperation(set, value);
}
@Override
protected MoveOperation createMoveOperation(final String destination, final V value, final long threadId) {
return new MoveOperation(set, destination, threadId, value);
}
@Override
protected RLock getLock(V value) {
return set.getLock(value);
}
@Override
protected TransactionalOperation createRemoveOperation(final Object value) {
return new RemoveOperation(set, value);
}
}

@ -0,0 +1,86 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonSetCache;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RSetCache;
import org.redisson.client.RedisClient;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.set.AddCacheOperation;
import org.redisson.transaction.operation.set.MoveOperation;
import org.redisson.transaction.operation.set.RemoveCacheOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class TransactionalSetCache<V> extends BaseTransactionalSet<V> {
private final RSetCache<V> set;
public TransactionalSetCache(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations,
RSetCache<V> set) {
super(commandExecutor, timeout, operations, set);
this.set = set;
}
@Override
protected ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client, long startPos,
String pattern) {
return ((RedissonSetCache<?>)set).scanIterator(name, client, startPos, pattern);
}
@Override
protected RFuture<Set<V>> readAllAsyncSource() {
return set.readAllAsync();
}
public RFuture<Boolean> addAsync(V value, long ttl, TimeUnit ttlUnit) {
return addAsync(value, new AddCacheOperation(set, value, ttl, ttlUnit));
}
@Override
protected TransactionalOperation createAddOperation(final V value) {
return new AddCacheOperation(set, value);
}
@Override
protected MoveOperation createMoveOperation(final String destination, final V value, final long threadId) {
throw new UnsupportedOperationException();
}
@Override
protected RLock getLock(V value) {
return set.getLock(value);
}
@Override
protected TransactionalOperation createRemoveOperation(final Object value) {
return new RemoveCacheOperation(set, value);
}
}

@ -0,0 +1,59 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation;
import org.redisson.RedissonKeys;
import org.redisson.RedissonLock;
import org.redisson.api.RKeys;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
*/
public class DeleteOperation extends TransactionalOperation {
private final String lockName;
public DeleteOperation(String name) {
this(name, null);
}
public DeleteOperation(String name, String lockName) {
super(name, null);
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RKeys keys = new RedissonKeys(commandExecutor);
keys.deleteAsync(getName());
if (lockName != null) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
if (lockName != null) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}
}

@ -0,0 +1,55 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation;
import org.redisson.RedissonKeys;
import org.redisson.RedissonLock;
import org.redisson.api.RKeys;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
*/
public class TouchOperation extends TransactionalOperation {
private final String lockName;
public TouchOperation(String name) {
this(name, null);
}
public TouchOperation(String name, String lockName) {
super(name, null);
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RKeys keys = new RedissonKeys(commandExecutor);
keys.touchAsync(getName());
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,48 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
*/
public abstract class TransactionalOperation {
protected final Codec codec;
protected final String name;
public TransactionalOperation(String name, Codec codec) {
this.name = name;
this.codec = codec;
}
public Codec getCodec() {
return codec;
}
public String getName() {
return name;
}
public abstract void commit(CommandAsyncExecutor commandExecutor);
public abstract void rollback(CommandAsyncExecutor commandExecutor);
}

@ -0,0 +1,59 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation;
import org.redisson.RedissonKeys;
import org.redisson.RedissonLock;
import org.redisson.api.RKeys;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
*/
public class UnlinkOperation extends TransactionalOperation {
private final String lockName;
public UnlinkOperation(String name) {
this(name, null);
}
public UnlinkOperation(String name, String lockName) {
super(name, null);
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RKeys keys = new RedissonKeys(commandExecutor);
keys.unlinkAsync(getName());
if (lockName != null) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
if (lockName != null) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}
}

@ -0,0 +1,57 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.bucket;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class BucketCompareAndSetOperation<V> extends TransactionalOperation {
private final V expected;
private final V value;
private final String lockName;
public BucketCompareAndSetOperation(String name, String lockName, Codec codec, V expected, V value) {
super(name, codec);
this.expected = expected;
this.value = value;
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
bucket.compareAndSetAsync(expected, value);
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,53 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.bucket;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class BucketGetAndDeleteOperation<V> extends TransactionalOperation {
private final String lockName;
public BucketGetAndDeleteOperation(String name, String lockName, Codec codec) {
super(name, codec);
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
bucket.getAndDeleteAsync();
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,55 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.bucket;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class BucketGetAndSetOperation<V> extends TransactionalOperation {
private final Object value;
private final String lockName;
public BucketGetAndSetOperation(String name, String lockName, Codec codec, Object value) {
super(name, codec);
this.value = value;
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
bucket.getAndSetAsync((V) value);
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,69 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.bucket;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class BucketSetOperation<V> extends TransactionalOperation {
private final Object value;
private final String lockName;
private long timeToLive;
private TimeUnit timeUnit;
public BucketSetOperation(String name, String lockName, Codec codec, Object value, long timeToLive, TimeUnit timeUnit) {
this(name, lockName, codec, value);
this.timeToLive = timeToLive;
this.timeUnit = timeUnit;
}
public BucketSetOperation(String name, String lockName, Codec codec, Object value) {
super(name, codec);
this.value = value;
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
if (timeToLive != 0) {
bucket.setAsync((V) value, timeToLive, timeUnit);
} else {
bucket.setAsync((V) value);
}
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,69 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.bucket;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonBucket;
import org.redisson.RedissonLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class BucketTrySetOperation<V> extends TransactionalOperation {
private final Object value;
private final String lockName;
private long timeToLive;
private TimeUnit timeUnit;
public BucketTrySetOperation(String name, String lockName, Codec codec, Object value, long timeToLive, TimeUnit timeUnit) {
this(name, lockName, codec, value);
this.timeToLive = timeToLive;
this.timeUnit = timeUnit;
}
public BucketTrySetOperation(String name, String lockName, Codec codec, Object value) {
super(name, codec);
this.value = value;
this.lockName = lockName;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
if (timeToLive != 0) {
bucket.trySetAsync((V) value, timeToLive, timeUnit);
} else {
bucket.trySetAsync((V) value);
}
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
lock.unlockAsync();
}
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapAddAndGetOperation extends MapOperation {
public MapAddAndGetOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
map.addAndGetAsync(key, (Number) value);
}
}

@ -0,0 +1,49 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
/**
*
* @author Nikita Koksharov
*
*/
public class MapCacheFastPutIfAbsentOperation extends MapOperation {
private long ttl;
private TimeUnit ttlUnit;
private long maxIdleTime;
private TimeUnit maxIdleUnit;
public MapCacheFastPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value, long ttl, TimeUnit ttlUnit,
long maxIdleTime, TimeUnit maxIdleUnit) {
super(map, key, value);
this.ttl = ttl;
this.ttlUnit = ttlUnit;
this.maxIdleTime = maxIdleTime;
this.maxIdleUnit = maxIdleUnit;
}
@Override
public void commit(RMap<Object, Object> map) {
((RMapCache<Object, Object>)map).fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
}
}

@ -0,0 +1,48 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
/**
*
* @author Nikita Koksharov
*
*/
public class MapCacheFastPutOperation extends MapOperation {
private long ttl;
private TimeUnit ttlUnit;
private long maxIdleTime;
private TimeUnit maxIdleUnit;
public MapCacheFastPutOperation(RMap<?, ?> map, Object key, Object value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
super(map, key, value);
this.ttl = ttl;
this.ttlUnit = ttlUnit;
this.maxIdleTime = maxIdleTime;
this.maxIdleUnit = maxIdleUnit;
}
@Override
public void commit(RMap<Object, Object> map) {
((RMapCache<Object, Object>)map).fastPutAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
}
}

@ -0,0 +1,53 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
/**
*
* @author Nikita Koksharov
*
*/
public class MapCachePutIfAbsentOperation extends MapOperation {
private long ttl;
private TimeUnit unit;
private long maxIdleTime;
private TimeUnit maxIdleUnit;
public MapCachePutIfAbsentOperation(RMap<?, ?> map, Object key, Object value,
long ttl, TimeUnit unit, long maxIdleTime, TimeUnit maxIdleUnit) {
this(map, key, value);
this.ttl = ttl;
this.unit = unit;
this.maxIdleTime = maxIdleTime;
this.maxIdleUnit = maxIdleUnit;
}
public MapCachePutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
((RMapCache<Object, Object>)map).putIfAbsentAsync(key, value, ttl, unit, maxIdleTime, maxIdleUnit);
}
}

@ -0,0 +1,48 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
/**
*
* @author Nikita Koksharov
*
*/
public class MapCachePutOperation extends MapOperation {
private long ttlTimeout;
private TimeUnit ttlUnit;
private long maxIdleTimeout;
private TimeUnit maxIdleUnit;
public MapCachePutOperation(RMap<?, ?> map, Object key, Object value, long ttlTimeout, TimeUnit ttlUnit, long maxIdleTimeout, TimeUnit maxIdleUnit) {
super(map, key, value);
this.ttlTimeout = ttlTimeout;
this.ttlUnit = ttlUnit;
this.maxIdleTimeout = maxIdleTimeout;
this.maxIdleUnit = maxIdleUnit;
}
@Override
public void commit(RMap<Object, Object> map) {
((RMapCache<Object, Object>)map).putAsync(key, value, ttlTimeout, ttlUnit, maxIdleTimeout, maxIdleUnit);
}
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapFastPutIfAbsentOperation extends MapOperation {
public MapFastPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
map.fastPutIfAbsentAsync(key, value);
}
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapFastPutOperation extends MapOperation {
public MapFastPutOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
map.fastPutAsync(key, value);
}
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapFastRemoveOperation extends MapOperation {
public MapFastRemoveOperation(RMap<?, ?> map, Object key) {
super(map, key, null);
}
@Override
public void commit(RMap<Object, Object> map) {
map.fastRemoveAsync(key);
}
}

@ -0,0 +1,80 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.RedissonMap;
import org.redisson.RedissonMapCache;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public abstract class MapOperation extends TransactionalOperation {
final Object key;
final Object value;
final Object oldValue;
final RMap<?, ?> map;
public MapOperation(RMap<?, ?> map, Object key, Object value) {
this(map, key, value, null);
}
public MapOperation(RMap<?, ?> map, Object key, Object value, Object oldValue) {
super(map.getName(), map.getCodec());
this.map = map;
this.key = key;
this.value = value;
this.oldValue = oldValue;
}
public Object getKey() {
return key;
}
public RMap<?, ?> getMap() {
return map;
}
@Override
public final void commit(CommandAsyncExecutor commandExecutor) {
RMap<Object, Object> map = getMap(commandExecutor);
commit(map);
map.getLock(key).unlockAsync();
}
protected RMap<Object, Object> getMap(CommandAsyncExecutor commandExecutor) {
if (map instanceof RMapCache) {
return new RedissonMapCache<Object, Object>(codec, null, commandExecutor, name, null, null);
}
return new RedissonMap<Object, Object>(codec, commandExecutor, name, null, null);
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RMap<Object, Object> map = getMap(commandExecutor);
map.getLock(key).unlockAsync();
}
protected abstract void commit(RMap<Object, Object> map);
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapPutIfAbsentOperation extends MapOperation {
public MapPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
map.putIfAbsentAsync(key, value);
}
}

@ -0,0 +1,36 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapPutOperation extends MapOperation {
public MapPutOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
map.putAsync(key, value);
}
}

@ -0,0 +1,44 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapRemoveOperation extends MapOperation {
public MapRemoveOperation(RMap<?, ?> map, Object key) {
super(map, key, null);
}
public MapRemoveOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
if (value != null) {
map.removeAsync(key, value);
} else {
map.removeAsync(key);
}
}
}

@ -0,0 +1,44 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.map;
import org.redisson.api.RMap;
/**
*
* @author Nikita Koksharov
*
*/
public class MapReplaceOperation extends MapOperation {
public MapReplaceOperation(RMap<?, ?> map, Object key, Object value, Object oldValue) {
super(map, key, value, oldValue);
}
public MapReplaceOperation(RMap<?, ?> map, Object key, Object value) {
super(map, key, value);
}
@Override
public void commit(RMap<Object, Object> map) {
if (oldValue != null) {
map.replaceAsync(key, oldValue, value);
} else {
map.replaceAsync(key, value);
}
}
}

@ -0,0 +1,66 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.set;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonSetCache;
import org.redisson.api.RObject;
import org.redisson.api.RSetCache;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public class AddCacheOperation extends TransactionalOperation {
final Object value;
final long ttl;
final TimeUnit timeUnit;
public AddCacheOperation(RObject set, Object value) {
this(set, value, 0, null);
}
public AddCacheOperation(RObject set, Object value, long ttl, TimeUnit timeUnit) {
super(set.getName(), set.getCodec());
this.value = value;
this.timeUnit = timeUnit;
this.ttl = ttl;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
if (timeUnit != null) {
set.addAsync(value, ttl, timeUnit);
} else {
set.addAsync(value);
}
set.getLock(value).unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
set.getLock(value).unlockAsync();
}
}

@ -0,0 +1,51 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.set;
import org.redisson.RedissonSet;
import org.redisson.api.RObject;
import org.redisson.api.RSet;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public class AddOperation extends TransactionalOperation {
final Object value;
public AddOperation(RObject set, Object value) {
super(set.getName(), set.getCodec());
this.value = value;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
set.addAsync(value);
set.getLock(value).unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
set.getLock(value).unlockAsync();
}
}

@ -0,0 +1,59 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.set;
import org.redisson.RedissonSet;
import org.redisson.api.RObject;
import org.redisson.api.RSet;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public class MoveOperation extends TransactionalOperation {
final String destinationName;
final Object value;
final long threadId;
public MoveOperation(RObject set, String destinationName, long threadId, Object value) {
super(set.getName(), set.getCodec());
this.destinationName = destinationName;
this.value = value;
this.threadId = threadId;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
RSet<Object> destinationSet = new RedissonSet<Object>(codec, commandExecutor, destinationName, null);
set.moveAsync(destinationSet.getName(), value);
destinationSet.getLock(value).unlockAsync(threadId);
set.getLock(value).unlockAsync(threadId);
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
RSet<Object> destinationSet = new RedissonSet<Object>(codec, commandExecutor, destinationName, null);
destinationSet.getLock(value).unlockAsync(threadId);
set.getLock(value).unlockAsync(threadId);
}
}

@ -0,0 +1,51 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.set;
import org.redisson.RedissonSetCache;
import org.redisson.api.RObject;
import org.redisson.api.RSetCache;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public class RemoveCacheOperation extends TransactionalOperation {
final Object value;
public RemoveCacheOperation(RObject set, Object value) {
super(set.getName(), set.getCodec());
this.value = value;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
set.removeAsync(value);
set.getLock(value).unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
set.getLock(value).unlockAsync();
}
}

@ -0,0 +1,51 @@
/**
* Copyright 2018 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.transaction.operation.set;
import org.redisson.RedissonSet;
import org.redisson.api.RObject;
import org.redisson.api.RSet;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.transaction.operation.TransactionalOperation;
/**
*
* @author Nikita Koksharov
*
*/
public class RemoveOperation extends TransactionalOperation {
final Object value;
public RemoveOperation(RObject set, Object value) {
super(set.getName(), set.getCodec());
this.value = value;
}
@Override
public void commit(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
set.removeAsync(value);
set.getLock(value).unlockAsync();
}
@Override
public void rollback(CommandAsyncExecutor commandExecutor) {
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
set.getLock(value).unlockAsync();
}
}

@ -587,6 +587,18 @@ public abstract class BaseMapTest extends BaseTest {
assertThat(map.get(1)).isEqualTo(3);
Assert.assertEquals(1, map.size());
}
@Test
public void testFastReplace() throws Exception {
RMap<Integer, Integer> map = getMap("simple");
map.put(1, 2);
assertThat(map.fastReplace(1, 3)).isTrue();
assertThat(map.fastReplace(2, 0)).isFalse();
Assert.assertEquals(1, map.size());
assertThat(map.get(1)).isEqualTo(3);
}
@Test
public void testEquals() {

@ -17,6 +17,7 @@ import org.junit.Assume;
import org.junit.Test;
import org.redisson.ClusterRunner.ClusterProcesses;
import org.redisson.RedisRunner.FailedToStartRedisException;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.RBatch;
import org.redisson.api.RFuture;
@ -24,8 +25,8 @@ import org.redisson.api.RListAsync;
import org.redisson.api.RMapAsync;
import org.redisson.api.RMapCacheAsync;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.api.RScript.Mode;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisException;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
@ -34,13 +35,13 @@ public class RedissonBatchTest extends BaseTest {
// @Test
public void testBatchRedirect() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
for (int i = 0; i < 5; i++) {
batch.getMap("" + i).fastPutAsync("" + i, i);
}
batch.execute();
batch = redisson.createBatch();
batch = redisson.createBatch(BatchOptions.defaults());
for (int i = 0; i < 1; i++) {
batch.getMap("" + i).sizeAsync();
batch.getMap("" + i).containsValueAsync("" + i);
@ -52,11 +53,13 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testBigRequestAtomic() {
RBatch batch = redisson.createBatch();
batch.atomic();
batch.timeout(15, TimeUnit.SECONDS);
batch.retryInterval(1, TimeUnit.SECONDS);
batch.retryAttempts(5);
BatchOptions options = BatchOptions.defaults()
.atomic()
.responseTimeout(15, TimeUnit.SECONDS)
.retryInterval(1, TimeUnit.SECONDS)
.retryAttempts(5);
RBatch batch = redisson.createBatch(options);
for (int i = 0; i < 100; i++) {
batch.getBucket("" + i).setAsync(i);
batch.getBucket("" + i).getAsync();
@ -87,13 +90,15 @@ public class RedissonBatchTest extends BaseTest {
.addNodeAddress(process.getNodes().stream().findAny().get().getRedisServerAddressAndPort());
RedissonClient redisson = Redisson.create(config);
RBatch batch = redisson.createBatch();
BatchOptions options = BatchOptions.defaults()
.syncSlaves(1, 1, TimeUnit.SECONDS);
RBatch batch = redisson.createBatch(options);
for (int i = 0; i < 100; i++) {
RMapAsync<String, String> map = batch.getMap("test");
map.putAsync("" + i, "" + i);
}
batch.syncSlaves(1, 1, TimeUnit.SECONDS);
BatchResult<?> result = batch.execute();
assertThat(result.getSyncedSlaves()).isEqualTo(1);
@ -102,7 +107,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testWriteTimeout() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
for (int i = 0; i < 200000; i++) {
RMapCacheAsync<String, String> map = batch.getMapCache("test");
map.putAsync("" + i, "" + i, 10, TimeUnit.SECONDS);
@ -113,13 +118,16 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testSkipResult() {
Assume.assumeTrue(RedisRunner.getDefaultRedisServerInstance().getRedisVersion().compareTo("3.2.0") > 0);
RBatch batch = redisson.createBatch();
BatchOptions options = BatchOptions.defaults()
.skipResult();
RBatch batch = redisson.createBatch(options);
batch.getBucket("A1").setAsync("001");
batch.getBucket("A2").setAsync("001");
batch.getBucket("A3").setAsync("001");
batch.getKeys().deleteAsync("A1");
batch.getKeys().deleteAsync("A2");
batch.skipResult();
batch.execute();
assertThat(redisson.getBucket("A1").isExists()).isFalse();
@ -128,7 +136,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testBatchNPE() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getBucket("A1").setAsync("001");
batch.getBucket("A2").setAsync("001");
batch.getBucket("A3").setAsync("001");
@ -139,8 +147,10 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testAtomic() {
RBatch batch = redisson.createBatch();
batch.atomic();
BatchOptions options = BatchOptions.defaults()
.atomic();
RBatch batch = redisson.createBatch(options);
RFuture<Long> f1 = batch.getAtomicLong("A1").addAndGetAsync(1);
RFuture<Long> f2 = batch.getAtomicLong("A2").addAndGetAsync(2);
RFuture<Long> f3 = batch.getAtomicLong("A3").addAndGetAsync(3);
@ -176,13 +186,15 @@ public class RedissonBatchTest extends BaseTest {
.addNodeAddress(process.getNodes().stream().findAny().get().getRedisServerAddressAndPort());
RedissonClient redisson = Redisson.create(config);
RBatch batch = redisson.createBatch();
BatchOptions options = BatchOptions.defaults()
.atomic()
.syncSlaves(1, 1, TimeUnit.SECONDS);
RBatch batch = redisson.createBatch(options);
for (int i = 0; i < 10; i++) {
batch.getAtomicLong("{test}" + i).addAndGetAsync(i);
}
batch.atomic();
batch.syncSlaves(1, 1, TimeUnit.SECONDS);
BatchResult<?> result = batch.execute();
assertThat(result.getSyncedSlaves()).isEqualTo(1);
int i = 0;
@ -196,7 +208,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testDifferentCodecs() {
RBatch b = redisson.createBatch();
RBatch b = redisson.createBatch(BatchOptions.defaults());
b.getMap("test1").putAsync("1", "2");
b.getMap("test2", StringCodec.INSTANCE).putAsync("21", "3");
RFuture<Object> val1 = b.getMap("test1").getAsync("1");
@ -209,7 +221,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testBatchList() {
RBatch b = redisson.createBatch();
RBatch b = redisson.createBatch(BatchOptions.defaults());
RListAsync<Integer> listAsync = b.getList("list");
for (int i = 1; i < 540; i++) {
listAsync.addAsync(i);
@ -220,7 +232,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testBatchBigRequest() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
for (int i = 0; i < 210; i++) {
batch.getMap("test").fastPutAsync("1", "2");
batch.getMap("test").fastPutAsync("2", "3");
@ -234,7 +246,7 @@ public class RedissonBatchTest extends BaseTest {
@Test(expected=RedisException.class)
public void testExceptionHandling() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test").putAsync("1", "2");
batch.getScript().evalAsync(Mode.READ_WRITE, "wrong_code", RScript.ReturnType.VALUE);
batch.execute();
@ -242,7 +254,7 @@ public class RedissonBatchTest extends BaseTest {
@Test(expected=IllegalStateException.class)
public void testTwice() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test").putAsync("1", "2");
batch.execute();
batch.execute();
@ -251,14 +263,14 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void testEmpty() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.execute();
}
@Test
public void testOrdering() throws InterruptedException {
ExecutorService e = Executors.newFixedThreadPool(16);
final RBatch batch = redisson.createBatch();
final RBatch batch = redisson.createBatch(BatchOptions.defaults());
final AtomicLong index = new AtomicLong(-1);
final List<RFuture<Long>> futures = new CopyOnWriteArrayList<>();
for (int i = 0; i < 500; i++) {
@ -292,7 +304,7 @@ public class RedissonBatchTest extends BaseTest {
@Test
public void test() {
RBatch batch = redisson.createBatch();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test").fastPutAsync("1", "2");
batch.getMap("test").fastPutAsync("2", "3");
batch.getMap("test").putAsync("2", "5");

@ -11,7 +11,6 @@ import java.util.concurrent.ExecutionException;
import org.junit.Assert;
import org.junit.Test;
import org.redisson.RedissonLocalCachedMap.CacheKey;
import org.redisson.RedissonLocalCachedMap.CacheValue;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.LocalCachedMapOptions.EvictionPolicy;
@ -20,6 +19,7 @@ import org.redisson.api.LocalCachedMapOptions.SyncStrategy;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RMap;
import org.redisson.cache.Cache;
import org.redisson.cache.CacheKey;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;

@ -12,20 +12,18 @@ import org.junit.Test;
import org.redisson.api.MapOptions;
import org.redisson.api.RMap;
import org.redisson.client.codec.Codec;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
public class RedissonMapTest extends BaseMapTest {
@Override
protected <K, V> RMap<K, V> getMap(String name) {
return redisson.getMap(name);
}
}
@Override
protected <K, V> RMap<K, V> getMap(String name, Codec codec) {
return redisson.getMap(name, codec);
}
}
@Override
protected <K, V> RMap<K, V> getLoaderTestMap(String name, Map<K, V> map) {

@ -3,6 +3,7 @@ package org.redisson;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Test;
import org.redisson.api.BatchOptions;
import org.redisson.api.RBatch;
import org.redisson.api.RBatchReactive;
import org.redisson.api.RBucket;
@ -35,7 +36,7 @@ public class RedissonReferenceReactiveTest extends BaseReactiveTest {
@Test
public void testBatch() throws InterruptedException {
RBatchReactive batch = redisson.createBatch();
RBatchReactive batch = redisson.createBatch(BatchOptions.defaults());
RBucketReactive<Object> b1 = batch.getBucket("b1");
RBucketReactive<Object> b2 = batch.getBucket("b2");
RBucketReactive<Object> b3 = batch.getBucket("b3");
@ -44,7 +45,7 @@ public class RedissonReferenceReactiveTest extends BaseReactiveTest {
b3.set(b1);
sync(batch.execute());
batch = redisson.createBatch();
batch = redisson.createBatch(BatchOptions.defaults());
batch.getBucket("b1").get();
batch.getBucket("b2").get();
batch.getBucket("b3").get();
@ -56,7 +57,7 @@ public class RedissonReferenceReactiveTest extends BaseReactiveTest {
@Test
public void testReactiveToNormal() throws InterruptedException {
RBatchReactive batch = redisson.createBatch();
RBatchReactive batch = redisson.createBatch(BatchOptions.defaults());
RBucketReactive<Object> b1 = batch.getBucket("b1");
RBucketReactive<Object> b2 = batch.getBucket("b2");
RBucketReactive<Object> b3 = batch.getBucket("b3");
@ -66,7 +67,7 @@ public class RedissonReferenceReactiveTest extends BaseReactiveTest {
sync(batch.execute());
RedissonClient lredisson = Redisson.create(redisson.getConfig());
RBatch b = lredisson.createBatch();
RBatch b = lredisson.createBatch(BatchOptions.defaults());
b.getBucket("b1").getAsync();
b.getBucket("b2").getAsync();
b.getBucket("b3").getAsync();

@ -0,0 +1,39 @@
package org.redisson.spring.transaction;
import javax.annotation.PreDestroy;
import org.redisson.BaseTest;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class RedissonTransactionContextConfig {
@Bean
public TransactionalBean2 transactionBean2() {
return new TransactionalBean2();
}
@Bean
public TransactionalBean transactionBean() {
return new TransactionalBean();
}
@Bean
public RedissonTransactionManager transactionManager(RedissonClient redisson) {
return new RedissonTransactionManager(redisson);
}
@Bean
public RedissonClient redisson() {
return BaseTest.createInstance();
}
@PreDestroy
public void destroy() {
redisson().shutdown();
}
}

@ -0,0 +1,90 @@
package org.redisson.spring.transaction;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.RedisRunner;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.TransactionSuspensionNotSupportedException;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RedissonTransactionContextConfig.class)
public class RedissonTransactionManagerTest {
@Autowired
private RedissonClient redisson;
@Autowired
private TransactionalBean transactionalBean;
@BeforeClass
public static void beforeClass() throws IOException, InterruptedException {
RedisRunner.startDefaultRedisServerInstance();
}
@AfterClass
public static void afterClass() throws IOException, InterruptedException {
RedisRunner.shutDownDefaultRedisServerInstance();
}
@Test
public void test() {
transactionalBean.testTransactionIsNotNull();
transactionalBean.testNoTransaction();
transactionalBean.testCommit();
RMap<String, String> map1 = redisson.getMap("test1");
assertThat(map1.get("1")).isEqualTo("2");
try {
transactionalBean.testRollback();
Assert.fail();
} catch (IllegalStateException e) {
// skip
}
RMap<String, String> map2 = redisson.getMap("test2");
assertThat(map2.get("1")).isNull();
transactionalBean.testCommitAfterRollback();
assertThat(map2.get("1")).isEqualTo("2");
try {
transactionalBean.testNestedNewTransaction();
Assert.fail();
} catch (TransactionSuspensionNotSupportedException e) {
// skip
}
RMap<String, String> mapTr1 = redisson.getMap("tr1");
assertThat(mapTr1.get("1")).isNull();
RMap<String, String> mapTr2 = redisson.getMap("tr2");
assertThat(mapTr2.get("2")).isNull();
transactionalBean.testPropagationRequired();
RMap<String, String> mapTr3 = redisson.getMap("tr3");
assertThat(mapTr3.get("2")).isEqualTo("4");
try {
transactionalBean.testPropagationRequiredWithException();
Assert.fail();
} catch (IllegalStateException e) {
// skip
}
RMap<String, String> mapTr4 = redisson.getMap("tr4");
assertThat(mapTr4.get("1")).isNull();
RMap<String, String> mapTr5 = redisson.getMap("tr5");
assertThat(mapTr5.get("2")).isNull();
}
}

@ -0,0 +1,74 @@
package org.redisson.spring.transaction;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Assert;
import org.redisson.api.RTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.annotation.Transactional;
public class TransactionalBean {
@Autowired
private RedissonTransactionManager transactionManager;
@Autowired
private TransactionalBean2 transactionalBean2;
@Transactional
public void testTransactionIsNotNull() {
RTransaction transaction = transactionManager.getCurrentTransaction();
assertThat(transaction).isNotNull();
}
public void testNoTransaction() {
try {
RTransaction transaction = transactionManager.getCurrentTransaction();
Assert.fail();
} catch (NoTransactionException e) {
// skip
}
}
@Transactional
public void testCommit() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("test1").put("1", "2");
}
@Transactional
public void testRollback() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("test2").put("1", "2");
throw new IllegalStateException();
}
@Transactional
public void testCommitAfterRollback() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("test2").put("1", "2");
}
@Transactional
public void testNestedNewTransaction() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("tr1").put("1", "0");
transactionalBean2.testInNewTransaction();
}
@Transactional
public void testPropagationRequired() {
transactionalBean2.testPropagationRequired();
}
@Transactional
public void testPropagationRequiredWithException() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("tr4").put("1", "0");
transactionalBean2.testPropagationRequiredWithException();
}
}

@ -0,0 +1,32 @@
package org.redisson.spring.transaction;
import org.redisson.api.RTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class TransactionalBean2 {
@Autowired
private RedissonTransactionManager transactionManager;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testInNewTransaction() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("tr2").put("2", "4");
}
@Transactional
public void testPropagationRequired() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("tr3").put("2", "4");
}
@Transactional
public void testPropagationRequiredWithException() {
RTransaction transaction = transactionManager.getCurrentTransaction();
transaction.getMap("tr5").put("2", "4");
throw new IllegalStateException();
}
}

@ -0,0 +1,239 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.redisson.BaseTest;
import org.redisson.api.RMap;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public abstract class RedissonBaseTransactionalMapTest extends BaseTest {
protected abstract RMap<String, String> getMap();
protected abstract RMap<String, String> getTransactionalMap(RTransaction transaction);
@Test
public void testPutAll() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(t);
Map<String, String> putMap = new HashMap<String, String>();
putMap.put("4", "5");
putMap.put("6", "7");
map.putAll(putMap);
assertThat(m.keySet()).containsOnly("1", "3");
t.commit();
assertThat(m.keySet()).containsOnly("1", "3", "4", "6");
}
@Test
public void testKeySet() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(t);
map.remove("3");
assertThat(map.keySet()).containsOnly("1");
assertThat(m.keySet()).containsOnly("1", "3");
}
@Test
public void testReplace2() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.replace("3", "4", "10")).isTrue();
assertThat(map.replace("1", "1", "3")).isFalse();
assertThat(map.replace("3", "10", "11")).isTrue();
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.size()).isEqualTo(2);
transaction.commit();
assertThat(m.size()).isEqualTo(2);
assertThat(m.get("3")).isEqualTo("11");
assertThat(m.get("1")).isEqualTo("2");
}
@Test
public void testReplace() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.replace("3", "10")).isEqualTo("4");
assertThat(map.replace("5", "0")).isNull();
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.size()).isEqualTo(2);
transaction.commit();
assertThat(m.size()).isEqualTo(2);
assertThat(m.get("3")).isEqualTo("10");
assertThat(m.get("5")).isNull();
RTransaction transaction2 = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map2 = getTransactionalMap(transaction2);
assertThat(map2.replace("3", "20")).isEqualTo("10");
assertThat(map2.replace("3", "30")).isEqualTo("20");
assertThat(m.get("3")).isEqualTo("10");
assertThat(m.size()).isEqualTo(2);
transaction2.commit();
assertThat(m.get("3")).isEqualTo("30");
}
@Test
public void testPutIfAbsent() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.putIfAbsent("3", "2")).isEqualTo("4");
assertThat(map.putIfAbsent("5", "6")).isNull();
assertThat(map.putIfAbsent("5", "7")).isEqualTo("6");
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.size()).isEqualTo(2);
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("5")).isEqualTo("6");
}
@Test
public void testPutIfAbsentRemove() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.putIfAbsent("3", "2")).isEqualTo("4");
assertThat(map.putIfAbsent("5", "6")).isNull();
assertThat(map.putIfAbsent("5", "7")).isEqualTo("6");
assertThat(map.remove("5")).isEqualTo("6");
assertThat(map.putIfAbsent("5", "8")).isNull();
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.size()).isEqualTo(2);
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("5")).isEqualTo("8");
}
@Test
public void testRemove() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.get("1")).isEqualTo("2");
assertThat(map.remove("3")).isEqualTo("4");
assertThat(map.remove("3")).isNull();
assertThat(map.remove("3")).isNull();
assertThat(m.get("3")).isEqualTo("4");
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isNull();
}
@Test
public void testPut() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.put("3", "5")).isEqualTo("4");
assertThat(map.get("3")).isEqualTo("5");
assertThat(m.get("3")).isEqualTo("4");
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("5");
}
@Test
public void testPutRemove() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.get("1")).isEqualTo("2");
assertThat(map.remove("3")).isEqualTo("4");
assertThat(map.put("3", "5")).isNull();
assertThat(map.get("3")).isEqualTo("5");
assertThat(m.get("3")).isEqualTo("4");
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("5");
}
@Test
public void testRollback() {
RMap<String, String> m = getMap();
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = getTransactionalMap(transaction);
assertThat(map.get("1")).isEqualTo("2");
assertThat(map.remove("3")).isEqualTo("4");
assertThat(m.get("3")).isEqualTo("4");
transaction.rollback();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("4");
}
}

@ -0,0 +1,148 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
import org.redisson.BaseTest;
import org.redisson.api.RBucket;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public class RedissonTransactionalBucketTest extends BaseTest {
@Test
public void testTimeout() throws InterruptedException {
RBucket<String> b = redisson.getBucket("test");
b.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults().timeout(3, TimeUnit.SECONDS));
RBucket<String> bucket = transaction.getBucket("test");
bucket.set("234");
Thread.sleep(3000);
try {
transaction.commit();
Assert.fail();
} catch (TransactionException e) {
// skip
}
Thread.sleep(1000);
assertThat(b.get()).isEqualTo("123");
}
@Test
public void testSet() {
RBucket<String> b = redisson.getBucket("test");
b.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<String> bucket = transaction.getBucket("test");
bucket.set("234");
assertThat(bucket.get()).isEqualTo("234");
transaction.commit();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(b.get()).isEqualTo("234");
}
@Test
public void testGetAndSet() {
RBucket<String> b = redisson.getBucket("test");
b.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<String> bucket = transaction.getBucket("test");
assertThat(bucket.getAndSet("0")).isEqualTo("123");
assertThat(bucket.get()).isEqualTo("0");
assertThat(bucket.getAndSet("324")).isEqualTo("0");
transaction.commit();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(b.get()).isEqualTo("324");
}
@Test
public void testCompareAndSet() {
RBucket<String> b = redisson.getBucket("test");
b.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<String> bucket = transaction.getBucket("test");
assertThat(bucket.compareAndSet("0", "434")).isFalse();
assertThat(bucket.get()).isEqualTo("123");
assertThat(bucket.compareAndSet("123", "232")).isTrue();
assertThat(bucket.get()).isEqualTo("232");
transaction.commit();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(b.get()).isEqualTo("232");
}
@Test
public void testTrySet() {
RBucket<String> b = redisson.getBucket("test");
b.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<String> bucket = transaction.getBucket("test");
assertThat(bucket.trySet("0")).isFalse();
assertThat(bucket.delete()).isTrue();
assertThat(bucket.trySet("324")).isTrue();
assertThat(bucket.trySet("43")).isFalse();
transaction.commit();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(b.get()).isEqualTo("324");
}
@Test
public void testGetAndRemove() {
RBucket<String> m = redisson.getBucket("test");
m.set("123");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<String> set = transaction.getBucket("test");
assertThat(set.get()).isEqualTo("123");
assertThat(set.size()).isEqualTo(5);
assertThat(set.getAndDelete()).isEqualTo("123");
assertThat(set.size()).isEqualTo(0);
assertThat(set.get()).isNull();
assertThat(set.getAndDelete()).isNull();
transaction.commit();
assertThat(redisson.getKeys().count()).isEqualTo(0);
assertThat(m.get()).isNull();
}
@Test
public void testRollback() {
RBucket<Object> b = redisson.getBucket("test");
b.set("1234");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RBucket<Object> bucket = transaction.getBucket("test");
assertThat(bucket.get()).isEqualTo("1234");
assertThat(bucket.getAndDelete()).isEqualTo("1234");
assertThat(b.get()).isEqualTo("1234");
transaction.rollback();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(b.get()).isEqualTo("1234");
}
}

@ -0,0 +1,94 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.redisson.BaseTest;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RMap;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public class RedissonTransactionalLocalCachedMapTest extends BaseTest {
@Test
public void testPut() throws InterruptedException {
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m1.put("1", "2");
m1.put("3", "4");
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m2.get("1");
m2.get("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = transaction.getLocalCachedMap(m1);
assertThat(map.put("3", "5")).isEqualTo("4");
assertThat(map.get("3")).isEqualTo("5");
assertThat(m1.get("3")).isEqualTo("4");
assertThat(m2.get("3")).isEqualTo("4");
transaction.commit();
assertThat(m1.get("3")).isEqualTo("5");
assertThat(m2.get("3")).isEqualTo("5");
}
@Test
public void testPutRemove() {
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m1.put("1", "2");
m1.put("3", "4");
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m2.get("1");
m2.get("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = transaction.getLocalCachedMap(m1);
assertThat(map.get("1")).isEqualTo("2");
assertThat(map.remove("3")).isEqualTo("4");
assertThat(map.put("3", "5")).isNull();
assertThat(map.get("3")).isEqualTo("5");
assertThat(m1.get("3")).isEqualTo("4");
assertThat(m2.get("3")).isEqualTo("4");
transaction.commit();
assertThat(m1.get("1")).isEqualTo("2");
assertThat(m1.get("3")).isEqualTo("5");
assertThat(m2.get("1")).isEqualTo("2");
assertThat(m2.get("3")).isEqualTo("5");
}
@Test
public void testRollback() {
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m1.put("1", "2");
m1.put("3", "4");
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
m2.get("1");
m2.get("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMap<String, String> map = transaction.getLocalCachedMap(m1);
assertThat(map.get("1")).isEqualTo("2");
assertThat(map.remove("3")).isEqualTo("4");
assertThat(m1.get("3")).isEqualTo("4");
transaction.rollback();
assertThat(redisson.getKeys().count()).isEqualTo(1);
assertThat(m1.get("1")).isEqualTo("2");
assertThat(m1.get("3")).isEqualTo("4");
assertThat(m2.get("1")).isEqualTo("2");
assertThat(m2.get("3")).isEqualTo("4");
}
}

@ -0,0 +1,58 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public class RedissonTransactionalMapCacheTest extends RedissonBaseTransactionalMapTest {
@Test
public void testPutIfAbsentTTL() throws InterruptedException {
RMapCache<Object, Object> m = redisson.getMapCache("test");
m.put("1", "2");
m.put("3", "4");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RMapCache<Object, Object> map = transaction.getMapCache("test");
assertThat(map.putIfAbsent("3", "2", 1, TimeUnit.SECONDS)).isEqualTo("4");
assertThat(map.putIfAbsent("5", "6", 3, TimeUnit.SECONDS)).isNull();
assertThat(map.putIfAbsent("5", "7", 1, TimeUnit.SECONDS)).isEqualTo("6");
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.size()).isEqualTo(2);
transaction.commit();
assertThat(m.get("1")).isEqualTo("2");
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("5")).isEqualTo("6");
Thread.sleep(1500);
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("5")).isEqualTo("6");
Thread.sleep(1500);
assertThat(m.get("3")).isEqualTo("4");
assertThat(m.get("5")).isNull();
}
@Override
protected RMap<String, String> getMap() {
return redisson.getMapCache("test");
}
@Override
protected RMap<String, String> getTransactionalMap(RTransaction transaction) {
return transaction.getMapCache("test");
}
}

@ -0,0 +1,19 @@
package org.redisson.transaction;
import org.redisson.api.RMap;
import org.redisson.api.RTransaction;
public class RedissonTransactionalMapTest extends RedissonBaseTransactionalMapTest {
@Override
protected RMap<String, String> getMap() {
return redisson.getMap("test");
}
@Override
protected RMap<String, String> getTransactionalMap(RTransaction transaction) {
return transaction.getMap("test");
}
}

@ -0,0 +1,143 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.redisson.BaseTest;
import org.redisson.api.RSetCache;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public class RedissonTransactionalSetCacheTest extends BaseTest {
@Test
public void testRemoveAll() {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = t.getSetCache("test");
Set<String> putSet = new HashSet<String>();
putSet.add("4");
putSet.add("3");
set.removeAll(putSet);
assertThat(s).containsOnly("1", "3");
assertThat(set).containsOnly("1");
t.commit();
assertThat(s).containsOnly("1");
}
@Test
public void testPutAll() {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = t.getSetCache("test");
Set<String> putSet = new HashSet<String>();
putSet.add("4");
putSet.add("6");
set.addAll(putSet);
assertThat(s).containsOnly("1", "3");
assertThat(set).containsOnly("1", "3", "4", "6");
t.commit();
assertThat(s).containsOnly("1", "3", "4", "6");
}
@Test
public void testKeySet() {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = t.getSetCache("test");
set.remove("3");
assertThat(set).containsOnly("1");
assertThat(s).containsOnly("1", "3");
}
@Test
public void testAdd() {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = transaction.getSetCache("test");
assertThat(set.add("4")).isTrue();
assertThat(set.add("3")).isFalse();
assertThat(set.contains("4")).isTrue();
assertThat(s.contains("4")).isFalse();
transaction.commit();
assertThat(s.size()).isEqualTo(3);
assertThat(s.contains("1")).isTrue();
assertThat(s.contains("3")).isTrue();
assertThat(s.contains("4")).isTrue();
}
@Test
public void testAddTTL() throws InterruptedException {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = transaction.getSetCache("test");
assertThat(set.add("4", 2, TimeUnit.SECONDS)).isTrue();
assertThat(set.add("3")).isFalse();
assertThat(set.contains("4")).isTrue();
assertThat(s.contains("4")).isFalse();
transaction.commit();
assertThat(s.size()).isEqualTo(3);
assertThat(s.contains("1")).isTrue();
assertThat(s.contains("3")).isTrue();
assertThat(s.contains("4")).isTrue();
Thread.sleep(2000);
assertThat(s.contains("4")).isFalse();
}
@Test
public void testRemove() {
RSetCache<String> s = redisson.getSetCache("test");
s.add("1");
s.add("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RSetCache<String> set = transaction.getSetCache("test");
assertThat(set.contains("1")).isTrue();
assertThat(set.remove("3")).isTrue();
assertThat(set.remove("3")).isFalse();
assertThat(set.remove("3")).isFalse();
assertThat(s.contains("3")).isTrue();
transaction.commit();
assertThat(s.size()).isEqualTo(1);
assertThat(s.contains("1")).isTrue();
assertThat(s.contains("3")).isFalse();
}
}

@ -0,0 +1,115 @@
package org.redisson.transaction;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.redisson.BaseTest;
import org.redisson.api.RSet;
import org.redisson.api.RTransaction;
import org.redisson.api.TransactionOptions;
public class RedissonTransactionalSetTest extends BaseTest {
@Test
public void testRemoveAll() {
RSet<String> s = redisson.getSet("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSet<String> set = t.getSet("test");
Set<String> putSet = new HashSet<String>();
putSet.add("4");
putSet.add("3");
set.removeAll(putSet);
assertThat(s).containsOnly("1", "3");
assertThat(set).containsOnly("1");
t.commit();
assertThat(s).containsOnly("1");
}
@Test
public void testPutAll() {
RSet<String> s = redisson.getSet("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSet<String> set = t.getSet("test");
Set<String> putSet = new HashSet<String>();
putSet.add("4");
putSet.add("6");
set.addAll(putSet);
assertThat(s).containsOnly("1", "3");
assertThat(set).containsOnly("1", "3", "4", "6");
t.commit();
assertThat(s).containsOnly("1", "3", "4", "6");
}
@Test
public void testKeySet() {
RSet<String> s = redisson.getSet("test");
s.add("1");
s.add("3");
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
RSet<String> set = t.getSet("test");
set.remove("3");
assertThat(set).containsOnly("1");
assertThat(s).containsOnly("1", "3");
}
@Test
public void testAdd() {
RSet<String> s = redisson.getSet("test");
s.add("1");
s.add("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RSet<String> set = transaction.getSet("test");
assertThat(set.add("4")).isTrue();
assertThat(set.add("3")).isFalse();
assertThat(set.contains("4")).isTrue();
assertThat(s.contains("4")).isFalse();
transaction.commit();
assertThat(s.size()).isEqualTo(3);
assertThat(s.contains("1")).isTrue();
assertThat(s.contains("3")).isTrue();
assertThat(s.contains("4")).isTrue();
}
@Test
public void testRemove() {
RSet<String> s = redisson.getSet("test");
s.add("1");
s.add("3");
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
RSet<String> set = transaction.getSet("test");
assertThat(set.contains("1")).isTrue();
assertThat(set.remove("3")).isTrue();
assertThat(set.remove("3")).isFalse();
assertThat(set.remove("3")).isFalse();
assertThat(s.contains("3")).isTrue();
transaction.commit();
assertThat(s.size()).isEqualTo(1);
assertThat(s.contains("1")).isTrue();
assertThat(s.contains("3")).isFalse();
}
}
Loading…
Cancel
Save