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

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

package com.aerospike.mapper.tools;

import com.aerospike.client.*;
import com.aerospike.client.cdt.*;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.RecordExistsAction;
import com.aerospike.client.policy.WritePolicy;
import com.aerospike.mapper.annotations.AerospikeEmbed;
import com.aerospike.mapper.tools.mappers.ListMapper;
import reactor.core.publisher.Mono;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class ReactiveVirtualList {
    private final IReactiveAeroMapper reactiveAeroMapper;
    private final ValueType value;
    private final ClassCacheEntry owningEntry;
    private final ClassCacheEntry elementEntry;
    private final String binName;
    private final ListMapper listMapper;
    private Key key;
    private final AerospikeEmbed.EmbedType listType;
    private final AerospikeEmbed.EmbedType elementType;
    private final Function instanceMapper;

    // package level visibility
    ReactiveVirtualList(@NotNull IReactiveAeroMapper reactiveAeroMapper, @NotNull Class owningClazz, @NotNull Object key, @NotNull String binName, @NotNull Class clazz) {
        this(reactiveAeroMapper, null, owningClazz, key, binName, clazz);
    }

    // package level visibility
    ReactiveVirtualList(@NotNull IReactiveAeroMapper reactiveAeroMapper, @NotNull Object object, @NotNull String binName, @NotNull Class clazz) {
        this(reactiveAeroMapper, object, null, null, binName, clazz);
    }

    private ReactiveVirtualList(@NotNull IReactiveAeroMapper reactiveAeroMapper, Object object, Class owningClazz, Object key, @NotNull String binName, @NotNull Class clazz) {
        if (object != null) {
            owningClazz = object.getClass();
        }
        this.owningEntry = ClassCache.getInstance().loadClass(owningClazz, reactiveAeroMapper);
        Object aerospikeKey;
        if (key == null) {
            aerospikeKey = owningEntry.getKey(object);
        } else {
            aerospikeKey = owningEntry.translateKeyToAerospikeKey(key);
        }
        this.elementEntry = ClassCache.getInstance().loadClass(clazz, reactiveAeroMapper);
        this.reactiveAeroMapper = reactiveAeroMapper;
        this.binName = binName;
        this.value = owningEntry.getValueFromBinName(binName);
        if (value == null) {
            throw new AerospikeException(String.format("Class %s has no bin called %s", clazz.getSimpleName(), binName));
        }
        String set = owningEntry.getSetName();
        if ("".equals(set)) {
            // Use the null set
            set = null;
        }
        this.key = new Key(owningEntry.getNamespace(), set, Value.get(aerospikeKey));

        TypeUtils.AnnotatedType annotatedType = value.getAnnotatedType();
        AerospikeEmbed embed = annotatedType.getAnnotation(AerospikeEmbed.class);
        if (embed == null) {
            throw new AerospikeException(String.format("Bin %s on class %s is not specified as a embedded", binName, clazz.getSimpleName()));
        }
        listType = embed.type() == AerospikeEmbed.EmbedType.DEFAULT ? AerospikeEmbed.EmbedType.LIST : embed.type();
        elementType = embed.elementType() == AerospikeEmbed.EmbedType.DEFAULT ? AerospikeEmbed.EmbedType.MAP : embed.elementType();
        Class binClazz = value.getType();
        if (!(binClazz.isArray() || (Map.class.isAssignableFrom(binClazz)) || List.class.isAssignableFrom(binClazz))) {
            throw new AerospikeException(String.format("Bin %s on class %s is not a collection class", binName, clazz.getSimpleName()));
        }

        TypeMapper typeMapper = value.getTypeMapper();
        if (typeMapper instanceof ListMapper) {
            listMapper = ((ListMapper) typeMapper);
        } else {
            throw new AerospikeException(String.format("Bin %s on class %s is not mapped via a listMapper. This is unexpected", binName, clazz.getSimpleName()));
        }
        this.instanceMapper = listMapper::fromAerospikeInstanceFormat;
    }

    public ReactiveVirtualList changeKey(Object newKey) {
        String set = owningEntry.getSetName();
        if ("".equals(set)) {
            // Use the null set
            set = null;
        }
        this.key = new Key(owningEntry.getNamespace(), set, Value.get(owningEntry.translateKeyToAerospikeKey(key)));
        return this;
    }

    public class MultiOperation {
        private final ReactiveVirtualList reactiveVirtualList;
        private final List interactions;
        private int indexToReturn = -1;
        private final WritePolicy writePolicy;

        private MultiOperation(@NotNull ReactiveVirtualList virtualList, @NotNull WritePolicy writePolicy) {
            this.reactiveVirtualList = virtualList;
            this.interactions = new ArrayList<>();
            this.writePolicy = writePolicy;
        }

        public MultiOperation append(E item) {
            Object aerospikeItem = listMapper.toAerospikeInstanceFormat(item);
            this.interactions.add(new Interactor(reactiveVirtualList.getAppendOperation(aerospikeItem)));
            return this;
        }

        public MultiOperation removeByKey(Object key) {
            this.interactions.add(getRemoveKeyInteractor(key));
            return this;
        }

        public MultiOperation removeByKeyRange(Object startKey, Object endKey) {
            this.interactions.add(getRemoveKeyRangeInteractor(startKey, endKey));
            return this;
        }

        public MultiOperation removeByValueRange(Object startKey, Object endKey) {
            this.interactions.add(getRemoveValueRangeInteractor(startKey, endKey));
            return this;
        }

        public MultiOperation getByValueRange(Object startKey, Object endKey) {
            this.interactions.add(getGetByValueRangeInteractor(startKey, endKey));
            return this;
        }

        public MultiOperation getByKeyRange(Object startKey, Object endKey) {
            this.interactions.add(getGetByKeyRangeInteractor(startKey, endKey));
            return this;
        }

        public MultiOperation get(int index) {
            this.interactions.add(getIndexInteractor(index));
            return this;
        }

        public MultiOperation size() {
            this.interactions.add(getSizeInteractor());
            return this;
        }

        public MultiOperation asResult() {
            return this.asResultOfType(ReturnType.DEFAULT);
        }

        public MultiOperation asResultOfType(ReturnType type) {
            if (interactions.isEmpty()) {
                throw new AerospikeException("asResult() cannot mark an item as the function result if there are no items to process");
            }
            else if (this.indexToReturn >= 0) {
                throw new AerospikeException("asResult() can only be called once in a multi operation");
            }
            else {
                this.indexToReturn = this.interactions.size() - 1;
                this.interactions.get(indexToReturn).setNeedsResultOfType(type);
            }
            return this;
        }

        /**
         * Finish the multi operation and process it.
         * @return The multi operation result.
         */
        public  Mono end() {
            return end(null);
        }

        /**
         * Finish the multi operation and process it.
         * @param resultType The return type for the result.
         * @return The multi operation result with the given result type.
         */
        public  Mono end(Class resultType) {
            if (this.interactions.isEmpty()) {
                return null;
            }
            this.writePolicy.respondAllOps = true;
            Operation[] operations = new Operation[this.interactions.size()];

            int listSize = this.interactions.size();
            if (this.indexToReturn < 0) {
                // Mark the last get operation to return it's value, or the last value if there are no get operations
                for (int i = listSize-1; i >= 0; i--) {
                    if (!this.interactions.get(i).isWriteOperation()) {
                        this.indexToReturn = i;
                        this.interactions.get(indexToReturn).setNeedsResultOfType(ReturnType.DEFAULT);
                        break;
                    }
                }
            }
            int count = 0;
            for (Interactor thisInteractor : this.interactions) {
                operations[count++] = thisInteractor.getOperation();
            }

            int finalCount = count;
            return reactiveVirtualList.reactiveAeroMapper.getReactorClient()
                    .operate(writePolicy, key, operations)
                    .map(keyRecord -> {
                        T result;
                        if(finalCount == 1) {
                            result = (T)this.interactions.get(0).getResult(keyRecord.record.getValue(binName));
                        } else {
                            List resultList = keyRecord.record.getList(binName);
                            if (indexToReturn < 0) {
                                indexToReturn = listSize-1;
                                // Determine the last GET operation
                                for (int i = listSize-1; i >= 0; i--) {
                                    if (!this.interactions.get(i).isWriteOperation()) {
                                        indexToReturn = i;
                                        break;
                                    }
                                }
                            }
                            result = (T)this.interactions.get(indexToReturn).getResult(resultList.get(indexToReturn));
                        }
                        if (result != null) {
                            Object object = result;
                            if (result instanceof Collection) {
                                Collection collection = (Collection) result;
                                object = collection.isEmpty() ? null : collection.iterator().next();
                            }
                            reactiveAeroMapper.getMappingConverter().resolveDependencies(ClassCache.getInstance().loadClass(object.getClass(), reactiveAeroMapper));
                        }
                        return result;
                    });
        }
    }

    public MultiOperation beginMultiOperation() {
        return this.beginMulti(null);
    }

    public MultiOperation beginMulti(WritePolicy writePolicy) {
        if (writePolicy == null) {
            writePolicy = new WritePolicy(owningEntry.getWritePolicy());
            writePolicy.recordExistsAction = RecordExistsAction.UPDATE;
        }
        return new MultiOperation<>(this, writePolicy);
    }

    public Mono getByValueRange(Object startKey, Object endKey, ReturnType returnResultsOfType) {
        return this.getByValueRange(null, startKey, endKey, returnResultsOfType);
    }

    public Mono getByValueRange(WritePolicy writePolicy, Object startValue, Object endValue, ReturnType returnResultsOfType) {
        if (writePolicy == null) {
            writePolicy = new WritePolicy(owningEntry.getWritePolicy());
            writePolicy.recordExistsAction = RecordExistsAction.UPDATE;
        }
        Interactor interactor = getGetByValueRangeInteractor(startValue, endValue);
        interactor.setNeedsResultOfType(returnResultsOfType);

        return reactiveAeroMapper.getReactorClient()
                .operate(writePolicy, key, interactor.getOperation())
                .map(keyRecord -> (E)interactor.getResult(keyRecord.record.getList(binName)));
    }

    /**
     * Remove items from the list matching the specified key. If the list is mapped to a MAP in Aerospike, the start key and end key will dictate the range of keys to be removed,
     * inclusive of the start, exclusive of the end.
     * 

* If the list is mapped to a LIST in Aerospike however, the start and end range represent values to be removed from the list. *

* @param startKey Start key of the range to remove. * @param endKey End key of the range to remove. * @param returnResultsOfType Type to return. * @return A list of the records which have been removed from the database if returnResults is true, null otherwise. */ public Mono removeByValueRange(Object startKey, Object endKey, ReturnType returnResultsOfType) { return this.removeByValueRange(null, startKey, endKey, returnResultsOfType); } /** * Remove items from the list matching the specified key. If the list is mapped to a MAP in Aerospike, the start key and end key will dictate the range of keys to be removed, * inclusive of the start, exclusive of the end. *

* If the list is mapped to a LIST in Aerospike however, the start and end range represent values to be removed from the list. *

* @param writePolicy An Aerospike write policy to use for the operate() operation. * @param startKey Start key of the range to remove. * @param endKey End key of the range to remove. * @param returnResultsOfType Type to return. * @return A list of the records which have been removed from the database if returnResults is true, null otherwise. */ public Mono removeByValueRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { writePolicy = new WritePolicy(owningEntry.getWritePolicy()); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = getRemoveValueRangeInteractor(startKey, endKey); interactor.setNeedsResultOfType(returnResultsOfType); return reactiveAeroMapper.getReactorClient() .operate(writePolicy, key, interactor.getOperation()) .map(keyRecord -> (E)interactor.getResult(keyRecord.record.getList(binName))); } /** * Remove items from the list matching the specified key. If the list is mapped to a MAP in Aerospike, the start key and end key will dictate the range of keys to be removed, * inclusive of the start, exclusive of the end. *

* If the list is mapped to a LIST in Aerospike however, the start and end range represent values to be removed from the list. *

* @param startKey Start key of the range to remove. * @param endKey End key of the range to remove. * @param returnResultsOfType Type to return. * @return The result of the method is a list of the records which have been removed from the database if returnResults is true, null otherwise. */ public Mono removeByKeyRange(Object startKey, Object endKey, ReturnType returnResultsOfType) { return this.removeByKeyRange(null, startKey, endKey, returnResultsOfType); } /** * Remove items from the list matching the specified key. If the list is mapped to a MAP in Aerospike, the start key and end key will dictate the range of keys to be removed, * inclusive of the start, exclusive of the end. *

* If the list is mapped to a LIST in Aerospike however, the start and end range represent values to be removed from the list. *

* @param writePolicy An Aerospike write policy to use for the operate() operation. * @param startKey Start key of the range to remove. * @param endKey End key of the range to remove. * @param returnResultsOfType Type to return. * @return The result of the method is a list of the records which have been removed from the database if returnResults is true, null otherwise. */ public Mono removeByKeyRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { writePolicy = new WritePolicy(owningEntry.getWritePolicy()); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = getRemoveKeyRangeInteractor(startKey, endKey); interactor.setNeedsResultOfType(returnResultsOfType); return reactiveAeroMapper.getReactorClient() .operate(writePolicy, key, interactor.getOperation()) .map(keyRecord -> (E)interactor.getResult(keyRecord.record.getList(binName))); } private Interactor getGetByValueRangeInteractor(Object startValue, Object endValue) { DeferredOperation deferred = new DeferredOperation() { @Override public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { switch (operationParams.getNeedsResultOfType()) { case DEFAULT: case ELEMENTS: return new ResultsUnpacker[] { new ResultsUnpacker.ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRange(binName, getValue(startValue, false), getValue(endValue, false), TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValueRange(binName, getValue(startValue, false), getValue(endValue, false), TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); } } @Override public boolean isGetOperation() { return true; } }; return new Interactor(deferred); } private Value getValue(Object javaObject, boolean isKey) { Object aerospikeObject; if (isKey) { aerospikeObject = elementEntry.translateKeyToAerospikeKey(javaObject); } else { aerospikeObject = reactiveAeroMapper.getMappingConverter().translateToAerospike(javaObject); } if (aerospikeObject == null) { return null; } else { return Value.get(aerospikeObject); } } private Interactor getGetByKeyRangeInteractor(Object startKey, Object endKey) { DeferredOperation deferred = new DeferredOperation() { @Override public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { switch (operationParams.getNeedsResultOfType()) { case DEFAULT: case ELEMENTS: return new ResultsUnpacker[] { new ResultsUnpacker.ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRange(binName, getValue(startKey, true), getValue(endKey, true), TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByKeyRange(binName, getValue(startKey, true), getValue(endKey, true), TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); } } @Override public boolean isGetOperation() { return true; } }; return new Interactor(deferred); } private Interactor getRemoveKeyRangeInteractor(Object startKey, Object endKey) { DeferredOperation deferred = new DeferredOperation() { @Override public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { switch (operationParams.getNeedsResultOfType()) { case DEFAULT: case ELEMENTS: return new ResultsUnpacker[] { new ResultsUnpacker.ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRange(binName, getValue(startKey, true), getValue(endKey, true), TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByKeyRange(binName, getValue(startKey, true), getValue(endKey, true), TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); } } @Override public boolean isGetOperation() { return false; } }; return new Interactor(deferred); } private Interactor getRemoveKeyInteractor(Object key) { DeferredOperation deferred = new DeferredOperation() { @Override public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { switch (operationParams.getNeedsResultOfType()) { case DEFAULT: case ELEMENTS: return new ResultsUnpacker[] { new ResultsUnpacker.ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValue(binName, getValue(key, true), TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByKey(binName, getValue(key, true), TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); } } @Override public boolean isGetOperation() { return false; } }; return new Interactor(deferred); } private Interactor getRemoveValueRangeInteractor(Object startValue, Object endValue) { DeferredOperation deferred = new DeferredOperation() { @Override public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { switch (operationParams.getNeedsResultOfType()) { case DEFAULT: case ELEMENTS: return new ResultsUnpacker[] { new ResultsUnpacker.ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRange(binName, getValue(startValue, false), getValue(endValue, false), TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValueRange(binName, getValue(startValue, false), getValue(endValue, false), TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); } } @Override public boolean isGetOperation() { return false; } }; return new Interactor(deferred); } private Operation getAppendOperation(Object aerospikeObject) { if (aerospikeObject instanceof Map.Entry) { Map.Entry entry = (Map.Entry) aerospikeObject; return MapOperation.put(new MapPolicy(MapOrder.KEY_ORDERED, 0), binName, Value.get(entry.getKey()), Value.get(entry.getValue())); } else { return ListOperation.append(binName, Value.get(aerospikeObject)); } } public Mono append(E element) { return this.append(null, element); } public Mono append(WritePolicy writePolicy, E element) { Object result = listMapper.toAerospikeInstanceFormat(element); if (writePolicy == null) { writePolicy = new WritePolicy(owningEntry.getWritePolicy()); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } return reactiveAeroMapper.getReactorClient() .operate(writePolicy, key, getAppendOperation(result)) .map(keyRecord -> keyRecord.record.getLong(binName)); } public Mono get(int index) { return get(null, index); } private Interactor getIndexInteractor(int index) { if (listType == AerospikeEmbed.EmbedType.LIST) { return new Interactor(ListOperation.getByIndex(binName, index, ListReturnType.VALUE), new ResultsUnpacker.ElementUnpacker(instanceMapper)); } else { return new Interactor(MapOperation.getByIndex(binName, index, MapReturnType.KEY_VALUE), ResultsUnpacker.ListUnpacker.instance, new ResultsUnpacker.ElementUnpacker(instanceMapper)); } } public Mono get(Policy policy, int index) { if (policy == null) { policy = new Policy(owningEntry.getReadPolicy()); } Interactor interactor = getIndexInteractor(index); return reactiveAeroMapper.getReactorClient() .operate(null, key, interactor.getOperation()) .map(keyRecord -> (E)interactor.getResult(keyRecord.record.getList(binName))); } private Interactor getSizeInteractor() { if (listType == AerospikeEmbed.EmbedType.LIST) { return new Interactor(ListOperation.size(binName)); } else { return new Interactor(MapOperation.size(binName)); } } public Mono size(Policy policy) { if (policy == null) { policy = new Policy(owningEntry.getReadPolicy()); } Interactor interactor = getSizeInteractor(); return reactiveAeroMapper.getReactorClient(). operate(null, key, interactor.getOperation()) .map(keyRecord -> keyRecord.record.getLong(binName)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy