All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.transaction.RedissonTransactionalBuckets Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2021 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.RedissonBuckets;
import org.redisson.RedissonKeys;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RFuture;
import org.redisson.api.RKeys;
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.TransactionalOperation;
import org.redisson.transaction.operation.bucket.BucketSetOperation;
import org.redisson.transaction.operation.bucket.BucketsTrySetOperation;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 
 * @author Nikita Koksharov
 *
 */
public class RedissonTransactionalBuckets extends RedissonBuckets {

    static final Object NULL = new Object();
    
    private long timeout;
    private final AtomicBoolean executed;
    private final List operations;
    private Map state = new HashMap<>();
    private final String transactionId;
    
    public RedissonTransactionalBuckets(CommandAsyncExecutor commandExecutor, 
            long timeout, List operations, AtomicBoolean executed, String transactionId) {
        super(commandExecutor);
        
        this.timeout = timeout;
        this.operations = operations;
        this.executed = executed;
        this.transactionId = transactionId;
    }

    public RedissonTransactionalBuckets(Codec codec, CommandAsyncExecutor commandExecutor, 
            long timeout, List operations, AtomicBoolean executed, String transactionId) {
        super(codec, commandExecutor);
        
        this.timeout = timeout;
        this.operations = operations;
        this.executed = executed;
        this.transactionId = transactionId;
    }

    @Override
    public  RFuture> getAsync(String... keys) {
        checkState();
        
        if (keys.length == 0) {
            return RedissonPromise.newSucceededFuture(Collections.emptyMap());
        }
        
        Set keysToLoad = new HashSet<>();
        Map map = new LinkedHashMap<>();
        for (String key : keys) {
            Object value = state.get(key);
            if (value != null) {
                if (value != NULL) {
                    map.put(key, (V) value);
                }
            } else {
                keysToLoad.add(key);
            }
        }
        
        if (keysToLoad.isEmpty()) {
            return RedissonPromise.newSucceededFuture(map);
        }
        
        RPromise> result = new RedissonPromise<>();
        super.getAsync(keysToLoad.toArray(new String[keysToLoad.size()])).onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }
            
            map.putAll((Map) res);
            result.trySuccess(map);
        });
        return result;
    }
    
    @Override
    public RFuture setAsync(Map buckets) {
        checkState();
        
        RPromise result = new RedissonPromise<>();
        long currentThreadId = Thread.currentThread().getId();
        executeLocked(result, () -> {
            for (Entry entry : buckets.entrySet()) {
                operations.add(new BucketSetOperation<>(entry.getKey(), getLockName(entry.getKey()), codec, entry.getValue(), transactionId, currentThreadId));
                if (entry.getValue() == null) {
                    state.put(entry.getKey(), NULL);
                } else {
                    state.put(entry.getKey(), entry.getValue());
                }
            }
            result.trySuccess(null);
        }, buckets.keySet());
        return result;
    }
    
//    Add RKeys.deleteAsync support
//
//    public RFuture deleteAsync(String... keys) {
//        checkState();
//        RPromise result = new RedissonPromise<>();
//        long threadId = Thread.currentThread().getId();
//        executeLocked(result, new Runnable() {
//            @Override
//            public void run() {
//                AtomicLong counter = new AtomicLong();
//                AtomicLong executions = new AtomicLong(keys.length);
//                for (String key : keys) {
//                    Object st = state.get(key);
//                    if (st != null) {
//                        operations.add(new DeleteOperation(key, getLockName(key), transactionId, threadId));
//                        if (st != NULL) {
//                            state.put(key, NULL);
//                            counter.incrementAndGet();
//                        }
//                        if (executions.decrementAndGet() == 0) {
//                            result.trySuccess(counter.get());
//                        }
//                        continue;
//                    }
//
//                    RedissonKeys ks = new RedissonKeys(commandExecutor);
//                    ks.countExistsAsync(key).onComplete((res, e) -> {
//                        if (e != null) {
//                            result.tryFailure(e);
//                            return;
//                        }
//
//                        if (res > 0) {
//                            operations.add(new DeleteOperation(key, getLockName(key), transactionId, threadId));
//                            state.put(key, NULL);
//                            counter.incrementAndGet();
//                        }
//
//                        if (executions.decrementAndGet() == 0) {
//                            result.trySuccess(counter.get());
//                        }
//                    });
//                }
//            }
//        }, Arrays.asList(keys));
//        return result;
//    }
    
    @Override
    public RFuture trySetAsync(Map buckets) {
        checkState();
        
        RPromise result = new RedissonPromise<>();
        executeLocked(result, () -> {
            Set keysToSet = new HashSet<>();
            for (String key : buckets.keySet()) {
                Object value = state.get(key);
                if (value != null) {
                    if (value != NULL) {
                        operations.add(new BucketsTrySetOperation(codec, (Map) buckets, transactionId));
                        result.trySuccess(false);
                        return;
                    }
                } else {
                    keysToSet.add(key);
                }
            }
            
            if (keysToSet.isEmpty()) {
                operations.add(new BucketsTrySetOperation(codec, (Map) buckets, transactionId));
                state.putAll(buckets);
                result.trySuccess(true);
                return;
            }
            
            RKeys keys = new RedissonKeys(commandExecutor);
            String[] ks = keysToSet.toArray(new String[keysToSet.size()]);
            keys.countExistsAsync(ks).onComplete((res, e) -> {
                if (e != null) {
                    result.tryFailure(e);
                    return;
                }
                
                operations.add(new BucketsTrySetOperation(codec, (Map) buckets, transactionId));
                if (res == 0) {
                    state.putAll(buckets);
                    result.trySuccess(true);
                } else {
                    result.trySuccess(false);
                }
            });
        }, buckets.keySet());
        return result;
    }
    
    protected  void executeLocked(RPromise promise, Runnable runnable, Collection keys) {
        List locks = new ArrayList<>(keys.size());
        for (String key : keys) {
            RLock lock = getLock(key);
            locks.add(lock);
        }
        RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
        long threadId = Thread.currentThread().getId();
        multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).onComplete((res, e) -> {
            if (e == null) {
                runnable.run();
            } else {
                multiLock.unlockAsync(threadId);
                promise.tryFailure(e);
            }
        });
    }
    
    private RLock getLock(String name) {
        return new RedissonTransactionalLock(commandExecutor, getLockName(name), transactionId);
    }

    private String getLockName(String name) {
        return name + ":transaction_lock";
    }

    protected void checkState() {
        if (executed.get()) {
            throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
        }
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy