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

com.aerospike.mapper.tools.ReactiveAeroMapper Maven / Gradle / Ivy

package com.aerospike.mapper.tools;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;

import javax.validation.constraints.NotNull;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Operation;
import com.aerospike.client.Value;
import com.aerospike.client.policy.BatchPolicy;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.QueryPolicy;
import com.aerospike.client.policy.RecordExistsAction;
import com.aerospike.client.policy.ScanPolicy;
import com.aerospike.client.policy.WritePolicy;
import com.aerospike.client.query.Filter;
import com.aerospike.client.query.KeyRecord;
import com.aerospike.client.query.Statement;
import com.aerospike.client.reactor.IAerospikeReactorClient;
import com.aerospike.mapper.tools.converters.MappingConverter;
import com.aerospike.mapper.tools.utils.MapperUtils;
import com.aerospike.mapper.tools.virtuallist.ReactiveVirtualList;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ReactiveAeroMapper implements IReactiveAeroMapper {

    private final IAerospikeReactorClient reactorClient;
    private final IAeroMapper aeroMapper;
    private final MappingConverter mappingConverter;

    /**
     * Create a new Builder to instantiate the AeroMapper. 
     * @author tfaulkes
     *
     */
    public static class Builder extends AbstractBuilder {
        public Builder(IAerospikeReactorClient reactorClient) {
            super(new ReactiveAeroMapper(reactorClient));
            ClassCache.getInstance().setReactiveDefaultPolicies(reactorClient);
        }
    }
    
    private ReactiveAeroMapper(@NotNull IAerospikeReactorClient reactorClient) {
        this.reactorClient = reactorClient;
        this.aeroMapper = new AeroMapper.Builder(reactorClient.getAerospikeClient()).build();
        this.mappingConverter = new MappingConverter(this, reactorClient.getAerospikeClient());
    }

    @Override
    public  Flux save(@NotNull T... objects) {
        return Flux.fromStream(Arrays.stream(objects))
                .flatMap(this::save);
    }

    @Override
    public  Mono save(@NotNull T object, String... binNames) {
        return save(null, object, RecordExistsAction.REPLACE, binNames);
    }

    @Override
    public  Mono save(@NotNull WritePolicy writePolicy, @NotNull T object, String... binNames) {
        return save(writePolicy, object, null, binNames);
    }

    @SuppressWarnings("unchecked")
    private  Mono save(WritePolicy writePolicy, @NotNull T object, RecordExistsAction recordExistsAction, String[] binNames) {
        Class clazz = (Class) object.getClass();
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        if (writePolicy == null) {
            writePolicy = new WritePolicy(entry.getWritePolicy());
            if (recordExistsAction != null) {
                writePolicy.recordExistsAction = recordExistsAction;
            }
            
            // #132 -- Only override the TTL / send key if the policy was not passed in.
            Integer ttl = entry.getTtl();
            Boolean sendKey = entry.getSendKey();

            if (ttl != null) {
                writePolicy.expiration = ttl;
            }
            if (sendKey != null) {
                writePolicy.sendKey = sendKey;
            }
        }

        String set = entry.getSetName();
        if ("".equals(set)) {
            // Use the null set
            set = null;
        }
        Key key = new Key(entry.getNamespace(), set, Value.get(entry.getKey(object)));

        Bin[] bins = entry.getBins(object, writePolicy.recordExistsAction != RecordExistsAction.REPLACE, binNames);

        return reactorClient
                .put(writePolicy, key, bins)
                .map(docKey -> object)
                .onErrorMap(this::translateError);
    }

    @Override
    public  Mono update(@NotNull T object, String... binNames) {
        return save(null, object, RecordExistsAction.UPDATE, binNames);
    }

    @Override
    public  Mono readFromDigest(@NotNull Class clazz, @NotNull byte[] digest) {
        return this.readFromDigest(clazz, digest, true);
    }

    @Override
    public  Mono readFromDigest(@NotNull Class clazz, @NotNull byte[] digest, boolean resolveDependencies) throws AerospikeException {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        Key key = new Key(entry.getNamespace(), digest, entry.getSetName(), null);
        return this.read(null, clazz, key, entry, resolveDependencies);
    }

    @Override
    public  Mono readFromDigest(Policy readPolicy, @NotNull Class clazz, @NotNull byte[] digest) {
        return this.readFromDigest(readPolicy, clazz, digest, true);
    }

    @Override
    public  Mono readFromDigest(Policy readPolicy, @NotNull Class clazz, @NotNull byte[] digest, boolean resolveDependencies) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        Key key = new Key(entry.getNamespace(), digest, entry.getSetName(), null);
        return this.read(readPolicy, clazz, key, entry, resolveDependencies);
    }

    @Override
    public  Mono read(@NotNull Class clazz, @NotNull Object userKey) {
        return this.read(clazz, userKey, true);
    }

    @Override
    public  Mono read(@NotNull Class clazz, @NotNull Object userKey, boolean resolveDependencies) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        String set = entry.getSetName();
        Key key = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKey)));
        return read(null, clazz, key, entry, resolveDependencies);
    }

    @Override
    public  Mono read(Policy readPolicy, @NotNull Class clazz, @NotNull Object userKey) {
        return this.read(readPolicy, clazz, userKey, true);
    }

    @Override
    public  Mono read(Policy readPolicy, @NotNull Class clazz, @NotNull Object userKey, boolean resolveDependencies) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        String set = entry.getSetName();
        Key key = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKey)));
        return read(readPolicy, clazz, key, entry, resolveDependencies);
    }

    @Override
    public  Flux read(@NotNull Class clazz, @NotNull Object[] userKeys) {
        return read(null, clazz, userKeys);
    }

    @Override
    public  Flux read(BatchPolicy batchPolicy, @NotNull Class clazz, @NotNull Object[] userKeys) {
        return read(null, clazz, userKeys, (Operation[]) null);
    }

    @Override
    public  Flux read(@NotNull Class clazz, @NotNull Object[] userKeys, Operation... operations) {
        return read(null, clazz, userKeys, operations);
    }

    @Override
    public  Flux read(BatchPolicy batchPolicy, @NotNull Class clazz, @NotNull Object[] userKeys, Operation... operations) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        String set = entry.getSetName();
        Key[] keys = new Key[userKeys.length];
        for (int i = 0; i < userKeys.length; i++) {
            if (userKeys[i] == null) {
                throw new AerospikeException("Cannot pass null to object " + i + " in multi-read call");
            } else {
                keys[i] = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKeys[i])));
            }
        }

        return readBatch(batchPolicy, clazz, keys, entry, operations);
    }

    private  Mono read(Policy readPolicy, @NotNull Class clazz, @NotNull Key key, @NotNull ClassCacheEntry entry, boolean resolveDependencies) {
        if (readPolicy == null) {
            readPolicy = entry.getReadPolicy();
        }

        return reactorClient
                .get(readPolicy, key)
                .filter(keyRecord -> Objects.nonNull(keyRecord.record))
                .map(keyRecord -> {
                    try {
                        ThreadLocalKeySaver.save(key);
                        return mappingConverter.convertToObject(clazz, keyRecord.record, entry, resolveDependencies);
                    } catch (ReflectiveOperationException e) {
                        throw new AerospikeException(e);
                    } finally {
                        ThreadLocalKeySaver.clear();
                    }
                });
    }

    private  Flux readBatch(BatchPolicy batchPolicy, @NotNull Class clazz, @NotNull Key[] keys,
                                  @NotNull ClassCacheEntry entry, Operation... operations) {
        if (batchPolicy == null) {
            batchPolicy = entry.getBatchPolicy();
        }

        Flux keyRecordFlux;

        if (operations != null && operations.length > 0) {
            keyRecordFlux = reactorClient
                    .getFlux(batchPolicy, keys, operations);
        } else {
            keyRecordFlux = reactorClient
                    .getFlux(batchPolicy, keys);
        }

        return keyRecordFlux.filter(keyRecord -> Objects.nonNull(keyRecord.record))
                .map(keyRecord -> {
                    try {
                        ThreadLocalKeySaver.save(keyRecord.key);
                        return mappingConverter.convertToObject(clazz, keyRecord.record, entry, true);
                    } catch (ReflectiveOperationException e) {
                        throw new AerospikeException(e);
                    } finally {
                        ThreadLocalKeySaver.clear();
                    }
                });
    }

    @Override
    public  Mono delete(@NotNull Class clazz, @NotNull Object userKey) {
        return delete(null, clazz, userKey);
    }

    @Override
    public  Mono delete(WritePolicy writePolicy, @NotNull Class clazz, @NotNull Object userKey) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        Object asKey = entry.translateKeyToAerospikeKey(userKey);

        if (writePolicy == null) {
            writePolicy = entry.getWritePolicy();
            if (entry.getDurableDelete() != null) {
                // Clone the write policy so we're not changing the original one
                writePolicy = new WritePolicy(writePolicy);
                writePolicy.durableDelete = entry.getDurableDelete();
            }
        }
        Key key = new Key(entry.getNamespace(), entry.getSetName(), Value.get(asKey));

        return reactorClient
                .delete(writePolicy, key)
                .map(k -> true);
    }

    @Override
    public Mono delete(@NotNull Object object) {
        return this.delete((WritePolicy) null, object);
    }

    @Override
    public Mono delete(WritePolicy writePolicy, @NotNull Object object) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(object.getClass(), this);
        Key key = new Key(entry.getNamespace(), entry.getSetName(), Value.get(entry.getKey(object)));

        if (writePolicy == null) {
            writePolicy = entry.getWritePolicy();
            if (entry.getDurableDelete() != null) {
                writePolicy = new WritePolicy(writePolicy);
                writePolicy.durableDelete = entry.getDurableDelete();
            }
        }
        return reactorClient
                .delete(writePolicy, key)
                .map(k -> true);
    }

    @Override
    public  Mono find(@NotNull Class clazz, Function function) throws AerospikeException {
        return Mono.fromCallable(() -> {
            asMapper().find(clazz, function);
            return null;
        });
    }

    @Override
    public  Flux scan(@NotNull Class clazz) {
        return scan(null, clazz);
    }

    @Override
    public  Flux scan(ScanPolicy policy, @NotNull Class clazz) {
        return scan(policy, clazz, -1);
    }

    @Override
    public  Flux scan(@NotNull Class clazz, int recordsPerSecond) {
        return scan(null, clazz, recordsPerSecond);
    }

    @Override
    public  Flux scan(ScanPolicy policy, @NotNull Class clazz, int recordsPerSecond) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        if (policy == null) {
            policy = entry.getScanPolicy();
        }
        if (recordsPerSecond >= 0) {
            // Ensure the underlying rate on the policy does not change
            policy = new ScanPolicy(policy);
            policy.recordsPerSecond = recordsPerSecond;
        }
        String namespace = entry.getNamespace();
        String setName = entry.getSetName();

        return reactorClient.scanAll(policy, namespace, setName)
                .map(keyRecord -> getMappingConverter().convertToObject(clazz, keyRecord.record));
    }

    @Override
    public  Flux query(@NotNull Class clazz, Filter filter) {
        return query(null, clazz, filter);
    }

    @Override
    public  Flux query(QueryPolicy policy, @NotNull Class clazz, Filter filter) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        if (policy == null) {
            policy = entry.getQueryPolicy();
        }
        Statement statement = new Statement();
        statement.setFilter(filter);
        statement.setNamespace(entry.getNamespace());
        statement.setSetName(entry.getSetName());

        return reactorClient.query(policy, statement)
                .map(keyRecord -> getMappingConverter().convertToObject(clazz, keyRecord.record));
    }

    @Override
    public  ReactiveVirtualList asBackedList(@NotNull Object object, @NotNull String binName, Class elementClazz) {
        return new ReactiveVirtualList<>(this, object, binName, elementClazz);
    }

    @Override
    public  ReactiveVirtualList asBackedList(@NotNull Class owningClazz, @NotNull Object key, @NotNull String binName, Class elementClazz) {
        return new ReactiveVirtualList<>(this, owningClazz, key, binName, elementClazz);
    }

    @Override
    public IAerospikeReactorClient getReactorClient() {
        return reactorClient;
    }

    @Override
    public MappingConverter getMappingConverter() {
        return mappingConverter;
    }

    @Override
    public IAeroMapper asMapper() {
        return aeroMapper;
    }

    @Override
    public Policy getReadPolicy(Class clazz) {
        return getPolicyByClassAndType(clazz, ClassCache.PolicyType.READ);
    }

    @Override
    public WritePolicy getWritePolicy(Class clazz) {
        return (WritePolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.WRITE);
    }

    @Override
    public BatchPolicy getBatchPolicy(Class clazz) {
        return (BatchPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.BATCH);
    }

    @Override
    public ScanPolicy getScanPolicy(Class clazz) {
        return (ScanPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.SCAN);
    }

    @Override
    public QueryPolicy getQueryPolicy(Class clazz) {
        return (QueryPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.QUERY);
    }

    private Policy getPolicyByClassAndType(Class clazz, ClassCache.PolicyType policyType) {
        ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this);

        switch (policyType) {
            case READ:
                return entry == null ? reactorClient.getReadPolicyDefault() : entry.getReadPolicy();
            case WRITE:
                return entry == null ? reactorClient.getWritePolicyDefault() : entry.getWritePolicy();
            case BATCH:
                return entry == null ? reactorClient.getBatchPolicyDefault() : entry.getBatchPolicy();
            case SCAN:
                return entry == null ? reactorClient.getScanPolicyDefault() : entry.getScanPolicy();
            case QUERY:
                return entry == null ? reactorClient.getQueryPolicyDefault() : entry.getQueryPolicy();
            default:
                throw new UnsupportedOperationException("Provided unsupported policy.");
        }
    }

    private Throwable translateError(Throwable e) {
        if (e instanceof AerospikeException) {
            return translateError(e);
        }
        return e;
    }
    
    @Override
    public  Mono getNamespace(Class clazz) {
        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
        return entry == null ? null : Mono.just(entry.getNamespace());
    }

    @Override
    public  Mono getSet(Class clazz) {
        ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this);
        return entry == null ? null : Mono.just(entry.getSetName());
    }

    @Override
    public Mono getKey(Object obj) {
        ClassCacheEntry entry = ClassCache.getInstance().loadClass(obj.getClass(), this);
        return entry == null ? null : Mono.just(entry.getKey(obj));
    }

    @Override
    public Mono getRecordKey(Object obj) {
        ClassCacheEntry entry = ClassCache.getInstance().loadClass(obj.getClass(), this);
        return entry == null ? null : Mono.just(new Key(entry.getNamespace(), entry.getSetName(), Value.get(entry.getKey(obj))));
    }
}