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

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

package com.aerospike.mapper.tools;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;

import javax.validation.constraints.NotNull;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Key;
import com.aerospike.client.Operation;
import com.aerospike.client.Record;
import com.aerospike.client.Value;
import com.aerospike.client.cdt.ListOperation;
import com.aerospike.client.cdt.ListReturnType;
import com.aerospike.client.cdt.MapOperation;
import com.aerospike.client.cdt.MapOrder;
import com.aerospike.client.cdt.MapPolicy;
import com.aerospike.client.cdt.MapReturnType;
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.annotations.AerospikeEmbed.EmbedType;
import com.aerospike.mapper.tools.ResultsUnpacker.ArrayUnpacker;
import com.aerospike.mapper.tools.ResultsUnpacker.ElementUnpacker;
import com.aerospike.mapper.tools.ResultsUnpacker.ListUnpacker;
import com.aerospike.mapper.tools.TypeUtils.AnnotatedType;
import com.aerospike.mapper.tools.mappers.ListMapper;

public class VirtualList {
	private final IAeroMapper mapper;
	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 EmbedType listType;
	private final EmbedType elementType;
	private final Function instanceMapper; 

	// package level visibility
	VirtualList(@NotNull IAeroMapper mapper, @NotNull Class owningClazz, @NotNull Object key, @NotNull String binName, @NotNull Class clazz) {
		this(mapper, null, owningClazz, key, binName, clazz);
	}
	
	// package level visibility
	VirtualList(@NotNull IAeroMapper mapper, @NotNull Object object, @NotNull String binName, @NotNull Class clazz) {
		this(mapper, object, null, null, binName, clazz);
	}

	private VirtualList(@NotNull IAeroMapper mapper, Object object, Class owningClazz, Object key, @NotNull String binName, @NotNull Class clazz) {
		if (object != null) {
			owningClazz = object.getClass();
		}
        this.owningEntry = ClassCache.getInstance().loadClass(owningClazz, mapper);
        Object aerospikeKey;
        if (key == null) {
        	aerospikeKey = owningEntry.getKey(object);
        }
        else {
        	aerospikeKey = owningEntry.translateKeyToAerospikeKey(key);
        }
        this.elementEntry = ClassCache.getInstance().loadClass(clazz, mapper);
        this.mapper = mapper;
        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));

        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() == EmbedType.DEFAULT ? EmbedType.LIST : embed.type();
        elementType = embed.elementType() == EmbedType.DEFAULT ? 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 VirtualList 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 VirtualList virtualList;
		private final List interactions;
		private int indexToReturn = -1;
		private final WritePolicy writePolicy;
		
		private MultiOperation(@NotNull VirtualList virtualList, @NotNull WritePolicy writePolicy) {
			this.virtualList = virtualList;
			this.interactions = new ArrayList<>();
			this.writePolicy = writePolicy;
		}
		
		public MultiOperation append(E item) {
			Object aerospikeItem = listMapper.toAerospikeInstanceFormat(item);
			this.interactions.add(new Interactor(virtualList.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 Object 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  T 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();
			}
						
    		Record record = this.virtualList.mapper.getClient().operate(writePolicy, key, operations);

    		T result;
    		if (count == 1) {
    			Object resultObj = record.getValue(binName);
    			result = (T)this.interactions.get(0).getResult(resultObj);
    		}
			else {
				List resultList = 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();
    			}
    			mapper.getMappingConverter().resolveDependencies(ClassCache.getInstance().loadClass(object.getClass(), mapper));
    		}
			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 List getByValueRange(Object startKey, Object endKey, ReturnType returnResultsOfType) {
		return this.getByValueRange(null, startKey, endKey, returnResultsOfType);
	}
	
	public List 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);
		Record record = this.mapper.getClient().operate(writePolicy, key, interactor.getOperation());

		return (List)interactor.getResult(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 List 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 List 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); Record record = this.mapper.getClient().operate(writePolicy, key, interactor.getOperation()); return (List)interactor.getResult(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 List 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 List 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); Record record = this.mapper.getClient().operate(writePolicy, key, interactor.getOperation()); return (List)interactor.getResult(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 ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == 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 = this.mapper.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 ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == 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 ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == 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 ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == 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 ArrayUnpacker(instanceMapper) }; default: return new ResultsUnpacker[0]; } } @Override public Operation getOperation(OperationParameters operationParams) { if (listType == 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 Entry) { Entry entry = (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 long append(E element) { return this.append(null, element); } public long append(WritePolicy writePolicy, E element) { Object result = listMapper.toAerospikeInstanceFormat(element); if (writePolicy == null) { writePolicy = new WritePolicy(owningEntry.getWritePolicy()); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Record record = this.mapper.getClient().operate(writePolicy, key, getAppendOperation(result)); return record.getLong(binName); } public E get(int index) { return get(null, index); } private Interactor getIndexInteractor(int index) { if (listType == EmbedType.LIST) { return new Interactor(ListOperation.getByIndex(binName, index, ListReturnType.VALUE), new ElementUnpacker(instanceMapper)); } else { return new Interactor(MapOperation.getByIndex(binName, index, MapReturnType.KEY_VALUE), ListUnpacker.instance, new ElementUnpacker(instanceMapper)); } } public E get(Policy policy, int index) { if (policy == null) { policy = new Policy(owningEntry.getReadPolicy()); } Interactor interactor = getIndexInteractor(index); Record record = this.mapper.getClient().operate(null, key, interactor.getOperation()); return (E)interactor.getResult(record.getList(binName)); } private Interactor getSizeInteractor() { if (listType == EmbedType.LIST) { return new Interactor(ListOperation.size(binName)); } else { return new Interactor(MapOperation.size(binName)); } } public long size(Policy policy) { if (policy == null) { policy = new Policy(owningEntry.getReadPolicy()); } Interactor interactor = getSizeInteractor(); Record record = this.mapper.getClient().operate(null, key, interactor.getOperation()); return record.getLong(binName); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy