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

com.artipie.asto.redis.RedisStorage Maven / Gradle / Ivy

There is a newer version: v1.17.16
Show newest version
/*
 * The MIT License (MIT) Copyright (c) 2020-2023 artipie.com
 * https://github.com/artipie/artipie/blob/master/LICENSE.txt
 */
package com.artipie.asto.redis;

import com.artipie.asto.ArtipieIOException;
import com.artipie.asto.Concatenation;
import com.artipie.asto.Content;
import com.artipie.asto.Key;
import com.artipie.asto.Meta;
import com.artipie.asto.OneTimePublisher;
import com.artipie.asto.Remaining;
import com.artipie.asto.Storage;
import com.artipie.asto.UnderLockOperation;
import com.artipie.asto.ValueNotFoundException;
import com.artipie.asto.ext.CompletableFutureSupport;
import com.artipie.asto.lock.storage.StorageLock;
import hu.akarnokd.rxjava2.interop.SingleInterop;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import org.redisson.api.RMapAsync;

/**
 * Redis implementation of Storage.
 *
 * @since 0.1
 */
public final class RedisStorage implements Storage {
    /**
     * Async interface for Redis based implementation
     * of {@link java.util.concurrent.ConcurrentMap} and {@link java.util.Map}.
     */
    private final RMapAsync data;

    /**
     * Storage identifier is redisson instance id, example: b0d9b09f-7c45-4a22-a8b7-c4979b65476a.
     */
    private final String id;

    /**
     * Ctor.
     *
     * @param data Async interface for Redis.
     * @param id Redisson instance id
     */
    public RedisStorage(final RMapAsync data, final String id) {
        this.data = data;
        this.id = String.format("Radis: id=%s", id);
    }

    @Override
    public CompletableFuture exists(final Key key) {
        return this.data.containsKeyAsync(key.string()).toCompletableFuture();
    }

    @Override
    public CompletableFuture> list(final Key root) {
        return this.data.readAllKeySetAsync()
            .thenApply(
                keys -> {
                    final Collection res = new LinkedList<>();
                    final String prefix = root.string();
                    for (final String string : new TreeSet<>(keys)) {
                        if (string.startsWith(prefix)) {
                            res.add(new Key.From(string));
                        }
                    }
                    return res;
                }
            ).toCompletableFuture();
    }

    @Override
    public CompletableFuture save(final Key key, final Content content) {
        final CompletableFuture res;
        if (Key.ROOT.equals(key)) {
            res = new CompletableFutureSupport.Failed(
                new ArtipieIOException("Unable to save to root")
            ).get();
        } else {
            res = new Concatenation(new OneTimePublisher<>(content)).single()
                .to(SingleInterop.get())
                .thenApply(Remaining::new)
                .thenApply(Remaining::bytes)
                .thenCompose(bytes -> this.data.fastPutAsync(key.string(), bytes))
                .thenRun(
                    () -> {
                    }
                )
                .toCompletableFuture();
        }
        return res;
    }

    @Override
    public CompletableFuture move(final Key source, final Key destination) {
        final String src = source.string();
        return this.data.containsKeyAsync(src)
            .thenCompose(
                exists -> {
                    final CompletionStage res;
                    if (exists) {
                        res = this.data.getAsync(src)
                            .thenCompose(
                                bytes -> this.data.fastPutAsync(destination.string(), bytes)
                            ).thenCompose(
                                unused -> this.data.fastRemoveAsync(src)
                                    .thenRun(() -> { })
                            );
                    } else {
                        res = new CompletableFutureSupport.Failed(
                            new ArtipieIOException(
                                String.format("No value for source key: %s", src)
                            )
                        ).get();
                    }
                    return res;
                }
            ).toCompletableFuture();
    }

    @Override
    public CompletableFuture value(final Key key) {
        final CompletableFuture res;
        if (Key.ROOT.equals(key)) {
            res = new CompletableFutureSupport.Failed(
                new ArtipieIOException("Unable to load from root")
            ).get();
        } else {
            res = this.data.getAsync(key.string())
                .thenApply(
                    bytes -> {
                        if (bytes != null) {
                            return (Content) new Content.OneTime(new Content.From(bytes));
                        }
                        throw new ValueNotFoundException(key);
                    }
                ).toCompletableFuture();
        }
        return res;
    }

    @Override
    public CompletableFuture delete(final Key key) {
        final String str = key.string();
        return this.data.fastRemoveAsync(str)
            .thenAccept(
                num -> {
                    if (num != 1) {
                        throw new ArtipieIOException(
                            String.format("Key does not exist: %s", str)
                        );
                    }
                }
            ).toCompletableFuture();
    }

    @Override
    public  CompletionStage exclusively(
        final Key key,
        final Function> operation
    ) {
        return new UnderLockOperation<>(new StorageLock(this, key), operation)
            .perform(this);
    }

    @Override
    public CompletableFuture metadata(final Key key) {
        return this.data.getAsync(key.string())
            .thenApply(
                bytes -> {
                    if (bytes != null) {
                        return new RedisMeta(bytes.length);
                    }
                    throw new ValueNotFoundException(key);
                }
            ).toCompletableFuture();
    }

    @Override
    public String identifier() {
        return this.id;
    }

    /**
     * Metadata for redis storage.
     *
     * @since 1.9
     */
    private static final class RedisMeta implements Meta {

        /**
         * Byte-array length.
         */
        private final long length;

        /**
         * New metadata.
         *
         * @param length Array length
         */
        RedisMeta(final int length) {
            this.length = length;
        }

        @Override
        public  T read(final ReadOperator opr) {
            final Map raw = new HashMap<>();
            Meta.OP_SIZE.put(raw, this.length);
            return opr.take(Collections.unmodifiableMap(raw));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy