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.javapull/1821/head
commit
09c5a6baf3
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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…
Reference in New Issue