├── images ├── classHierarchy.png ├── complexObjectGraph.png └── objectInstantiation.png ├── src ├── test │ └── java │ │ └── com │ │ └── aerospike │ │ └── mapper │ │ ├── model │ │ ├── ProductType.java │ │ ├── preload │ │ │ ├── Animal.java │ │ │ ├── Dog.java │ │ │ └── Cat.java │ │ ├── Product.java │ │ ├── Account.java │ │ └── PersonDifferentNames.java │ │ ├── examples │ │ ├── model │ │ │ ├── InterestType.java │ │ │ ├── accounts │ │ │ │ ├── AccountType.java │ │ │ │ ├── PortfolioAccount.java │ │ │ │ └── LoanAccount.java │ │ │ ├── Transaction.java │ │ │ ├── Branch.java │ │ │ ├── Valuation.java │ │ │ ├── Address.java │ │ │ ├── Checkbook.java │ │ │ ├── Property.java │ │ │ └── Customer.java │ │ ├── JavaMapperApplication.java │ │ ├── VirtualListExample.java │ │ └── NonJavaMapperApplication.java │ │ ├── reactive │ │ ├── ReactiveAeroMapperBaseTest.java │ │ ├── ReactiveObjectReferencesTest.java │ │ ├── ReactiveInheritanceTest.java │ │ ├── ReactiveInsertOnlyTest.java │ │ ├── ReactiveAeroMapperListOfObjectTransformTest.java │ │ ├── ReactiveRecursiveObjectTest.java │ │ ├── ReactiveMultipleParameterSetterTest.java │ │ ├── ReactiveAeroMapperObjectTransformTest.java │ │ ├── ReactiveDateCustomConverterTest.java │ │ ├── ReactiveScanTest.java │ │ ├── ReactiveMapTest.java │ │ ├── ReactiveListOfReferencesTest.java │ │ ├── ReactivePartialRecordsTest.java │ │ ├── ReactiveAnonymousReferencesTest.java │ │ ├── ReactiveAeroMapperComplexClassTest.java │ │ ├── ReactiveAeroMapperEnumTest.java │ │ └── ReactiveQueryTest.java │ │ ├── DefaultObjectMappingTest.java │ │ ├── BigIntegerBigDecimalTest.java │ │ ├── MessagesMappingTest.java │ │ ├── ObjectReferencesTest.java │ │ ├── InheritanceTest.java │ │ ├── AeroMapperListOfObjectTransformTest.java │ │ ├── InsertOnlyTest.java │ │ ├── NamespacePlaceHolderTest.java │ │ ├── MapTest.java │ │ ├── AeroMapperObjectTransformTest.java │ │ ├── AeroMapperBaseTest.java │ │ ├── DateCustomConverterTest.java │ │ ├── PartialRecordsTest.java │ │ ├── RecursiveObjectTest.java │ │ ├── MultipleParameterSetterTest.java │ │ ├── ListOfReferencesTest.java │ │ ├── EmbeddedClassTest.java │ │ ├── AnonymousReferencesTest.java │ │ ├── AeroMapperComplexClassTest.java │ │ ├── TestCustomTtl.java │ │ ├── AeroMapperEnumTest.java │ │ ├── DefaultFieldValuesTest.java │ │ ├── CollectionMapperTest.java │ │ ├── GenerationNotStoredTest.java │ │ ├── VirtualListReferenceTest.java │ │ ├── SubclassMapTest.java │ │ └── SubclassListTest.java └── main │ └── java │ └── com │ └── aerospike │ └── mapper │ ├── tools │ ├── mappers │ │ ├── ObjectMapper.java │ │ ├── LongMapper.java │ │ ├── DefaultMapper.java │ │ ├── DoubleMapper.java │ │ ├── IntMapper.java │ │ ├── FloatMapper.java │ │ ├── ByteMapper.java │ │ ├── ShortMapper.java │ │ ├── LocalDateMapper.java │ │ ├── LocalTimeMapper.java │ │ ├── DateMapper.java │ │ ├── BigDecimalMapper.java │ │ ├── BigIntegerMapper.java │ │ ├── CharacterMapper.java │ │ ├── InstantMapper.java │ │ ├── BooleanMapper.java │ │ ├── LocalDateTimeMapper.java │ │ ├── ArrayMapper.java │ │ ├── EnumMapper.java │ │ └── ObjectEmbedMapper.java │ ├── virtuallist │ │ ├── ReturnType.java │ │ ├── DeferredOperation.java │ │ ├── OperationParameters.java │ │ ├── ResultsUnpacker.java │ │ └── Interactor.java │ ├── Processor.java │ ├── configuration │ │ ├── Configuration.java │ │ ├── ReferenceConfig.java │ │ ├── EmbedConfig.java │ │ └── KeyConfig.java │ ├── TypeMapper.java │ ├── ConfigurationUtils.java │ ├── utils │ │ ├── MapperUtils.java │ │ └── ParserUtils.java │ ├── ThreadLocalKeySaver.java │ ├── PrimitiveDefaults.java │ ├── LoadedObjectResolver.java │ ├── DeferredObjectLoader.java │ └── IBaseAeroMapper.java │ ├── annotations │ ├── ToAerospike.java │ ├── FromAerospike.java │ ├── AerospikeOrdinal.java │ ├── AerospikeConstructor.java │ ├── AerospikeExclude.java │ ├── ParamFrom.java │ ├── AerospikeGetter.java │ ├── AerospikeSetter.java │ ├── AerospikeBin.java │ ├── AerospikeEnum.java │ ├── AerospikeKey.java │ ├── AerospikeRecord.java │ ├── AerospikeReference.java │ ├── AerospikeGeneration.java │ ├── AerospikeVersion.java │ └── AerospikeEmbed.java │ └── exceptions │ └── NotAnnotatedClass.java ├── .gitignore └── .github └── workflows ├── build.yml └── snyk-scan.yml /images/classHierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospike/java-object-mapper/HEAD/images/classHierarchy.png -------------------------------------------------------------------------------- /images/complexObjectGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospike/java-object-mapper/HEAD/images/complexObjectGraph.png -------------------------------------------------------------------------------- /images/objectInstantiation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospike/java-object-mapper/HEAD/images/objectInstantiation.png -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/ProductType.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model; 2 | 3 | public enum ProductType { 4 | CHECKING, 5 | SAVINGS 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/InterestType.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | public enum InterestType { 4 | INTEREST_ONLY, 5 | PRINCIPAL_INTEREST 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Maven 2 | target/ 3 | 4 | ## Eclipse 5 | .settings/ 6 | .project 7 | .classpath 8 | 9 | ## IntelliJ 10 | out/ 11 | .idea/ 12 | .idea_modules/ 13 | *.iml 14 | *.ipr 15 | *.iws 16 | 17 | ## OS X 18 | .DS_Store -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/accounts/AccountType.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model.accounts; 2 | 3 | public enum AccountType { 4 | SAVINGS, 5 | CHECKING, 6 | PORTFOLIO, 7 | LOAN 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/ObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public abstract class ObjectMapper extends TypeMapper { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/virtuallist/ReturnType.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.virtuallist; 2 | 3 | public enum ReturnType { 4 | DEFAULT, 5 | COUNT, 6 | INDEX, 7 | ELEMENTS, 8 | NONE 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/Processor.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | public interface Processor { 4 | /** 5 | * Process the given record. 6 | * 7 | * @param data - the record to be processed 8 | * @return true if further records should be processed, false if the processing loop should abort. 9 | */ 10 | boolean process(T data); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/ToAerospike.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface ToAerospike { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/FromAerospike.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface FromAerospike { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/virtuallist/DeferredOperation.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.virtuallist; 2 | 3 | import com.aerospike.client.Operation; 4 | 5 | public interface DeferredOperation { 6 | Operation getOperation(OperationParameters operationParams); 7 | 8 | ResultsUnpacker[] getUnpackers(OperationParameters operationParams); 9 | 10 | boolean isGetOperation(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/LongMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class LongMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | return value; 10 | } 11 | 12 | @Override 13 | public Object fromAerospikeFormat(Object value) { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/DefaultMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class DefaultMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | return value; 10 | } 11 | 12 | @Override 13 | public Object fromAerospikeFormat(Object value) { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/DoubleMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class DoubleMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | return value; 10 | } 11 | 12 | @Override 13 | public Object fromAerospikeFormat(Object value) { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeOrdinal.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.FIELD, ElementType.METHOD}) 10 | public @interface AerospikeOrdinal { 11 | int value() default 1; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.exceptions; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | 5 | public class NotAnnotatedClass extends AerospikeException { 6 | 7 | private static final long serialVersionUID = -4781097961894057707L; 8 | public static final int REASON_CODE = -109; 9 | 10 | public NotAnnotatedClass(String description) { 11 | super(REASON_CODE, description); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeConstructor.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags a constructor to be the one to use when a class as multiple constructors 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.CONSTRUCTOR) 13 | public @interface AerospikeConstructor { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeExclude.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Bins marked with AerospikeExclude will not be mapped to the database, irrespective of other annotations. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface AerospikeExclude { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/IntMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class IntMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | return value; 10 | } 11 | 12 | @Override 13 | public Object fromAerospikeFormat(Object value) { 14 | if (value == null) { 15 | return null; 16 | } 17 | return ((Number) value).intValue(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/FloatMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class FloatMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | return value; 10 | } 11 | 12 | @Override 13 | public Object fromAerospikeFormat(Object value) { 14 | if (value == null) { 15 | return null; 16 | } 17 | return ((Number) value).floatValue(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.configuration; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Configuration { 7 | private final List classes; 8 | 9 | public Configuration() { 10 | this.classes = new ArrayList<>(); 11 | } 12 | 13 | public List getClasses() { 14 | return classes; 15 | } 16 | 17 | public void add(ClassConfig config) { 18 | this.classes.add(config); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/ParamFrom.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags a field in a class to be mapped to an Aerospike bin. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.PARAMETER) 13 | public @interface ParamFrom { 14 | /** 15 | * The name of the bin to use. 16 | */ 17 | String value(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeGetter.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags a field in a class to be mapped to an Aerospike bin. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface AerospikeGetter { 14 | /** 15 | * The name of the bin to use. 16 | */ 17 | String name(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeSetter.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags a field in a class to be mapped to an Aerospike bin. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | public @interface AerospikeSetter { 14 | /** 15 | * The name of the bin to use. 16 | */ 17 | String name(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/TypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | public abstract class TypeMapper { 4 | public abstract Object toAerospikeFormat(Object value); 5 | 6 | public abstract Object fromAerospikeFormat(Object value); 7 | 8 | /** 9 | * Some types need to know if they're mapped to the correct class. If they do, they can override this method to glean that information 10 | */ 11 | public Object toAerospikeFormat(Object value, boolean isUnknownType, boolean isSubclassOfKnownType) { 12 | return toAerospikeFormat(value); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/ConfigurationUtils.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | public class ConfigurationUtils { 4 | public static boolean validateFieldOnClass(Class clazz, String fieldName) { 5 | try { 6 | clazz.getDeclaredField(fieldName); 7 | return true; 8 | } catch (NoSuchFieldException nsfe) { 9 | Class superclass = clazz.getSuperclass(); 10 | if (superclass != null) { 11 | return validateFieldOnClass(superclass, fieldName); 12 | } 13 | } 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/ByteMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class ByteMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | if (value == null) { 10 | return null; 11 | } 12 | return ((Number) value).longValue(); 13 | } 14 | 15 | @Override 16 | public Object fromAerospikeFormat(Object value) { 17 | if (value == null) { 18 | return null; 19 | } 20 | return ((Number) value).byteValue(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/ShortMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class ShortMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | if (value == null) { 10 | return null; 11 | } 12 | return ((Number) value).longValue(); 13 | } 14 | 15 | @Override 16 | public Object fromAerospikeFormat(Object value) { 17 | if (value == null) { 18 | return null; 19 | } 20 | return ((Number) value).shortValue(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeBin.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags a field in a class to be mapped to an Aerospike bin. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface AerospikeBin { 14 | /** 15 | * The name of the bin to use. If not specified, the field name is used for the bin name. 16 | */ 17 | String name() default ""; 18 | 19 | boolean useAccessors() default false; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.virtuallist; 2 | 3 | public class OperationParameters { 4 | private ReturnType needsResultOfType = ReturnType.NONE; 5 | 6 | public OperationParameters() { 7 | } 8 | 9 | public OperationParameters(ReturnType needsResultOfType) { 10 | super(); 11 | this.needsResultOfType = needsResultOfType; 12 | } 13 | 14 | public ReturnType getNeedsResultOfType() { 15 | return needsResultOfType; 16 | } 17 | 18 | public void setNeedsResultOfType(ReturnType needsResultOfType) { 19 | this.needsResultOfType = needsResultOfType; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/LocalDateMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import java.time.LocalDate; 6 | 7 | public class LocalDateMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | return ((LocalDate) value).toEpochDay(); 15 | } 16 | 17 | @Override 18 | public Object fromAerospikeFormat(Object value) { 19 | if (value == null) { 20 | return null; 21 | } 22 | return LocalDate.ofEpochDay((Long) value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/LocalTimeMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import java.time.LocalTime; 6 | 7 | public class LocalTimeMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | return ((LocalTime) value).toNanoOfDay(); 15 | } 16 | 17 | @Override 18 | public Object fromAerospikeFormat(Object value) { 19 | if (value == null) { 20 | return null; 21 | } 22 | return LocalTime.ofNanoOfDay((Long) value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/DateMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import java.util.Date; 4 | 5 | import com.aerospike.mapper.tools.TypeMapper; 6 | 7 | public class DateMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | return ((Date) value).getTime(); 15 | } 16 | 17 | @Override 18 | public Object fromAerospikeFormat(Object value) { 19 | if (value == null) { 20 | return null; 21 | } 22 | long longValue = (Long) value; 23 | return new Date(longValue); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/BigDecimalMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public class BigDecimalMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | BigDecimal bigInt = (BigDecimal) value; 15 | return bigInt.toString(); 16 | } 17 | 18 | @Override 19 | public Object fromAerospikeFormat(Object value) { 20 | if (value == null) { 21 | return null; 22 | } 23 | return new BigDecimal((String) value); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/BigIntegerMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import java.math.BigInteger; 6 | 7 | public class BigIntegerMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | BigInteger bigInt = (BigInteger) value; 15 | return bigInt.toString(); 16 | } 17 | 18 | @Override 19 | public Object fromAerospikeFormat(Object value) { 20 | if (value == null) { 21 | return null; 22 | } 23 | return new BigInteger((String) value); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/CharacterMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | public class CharacterMapper extends TypeMapper { 6 | 7 | @Override 8 | public Object toAerospikeFormat(Object value) { 9 | if (value == null) { 10 | return null; 11 | } else { 12 | char c = (Character) value; 13 | return (long) c; 14 | } 15 | } 16 | 17 | @Override 18 | public Object fromAerospikeFormat(Object value) { 19 | if (value == null) { 20 | return null; 21 | } 22 | long longVal = ((Number) value).longValue(); 23 | return (char) longVal; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeEnum.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Tags an enum field of a class to set a value field to store its value in the Aerospike database instead of the enum constant name. 10 | * default behaviour (without using this annotation or without specifying the value field's name) is to 11 | * store the entire constant name in the Aerospike database. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.FIELD) 15 | public @interface AerospikeEnum { 16 | String enumField() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeKey.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.FIELD, ElementType.METHOD}) 10 | public @interface AerospikeKey { 11 | /** 12 | * The setter attribute is used only on Methods where the method is used to set the key on lazy object instantiation 13 | */ 14 | boolean setter() default false; 15 | 16 | /** 17 | * Store the key as an Aerospike Bin, alternatively you can use @AerospikeRecord.sendKey to store the key in the record's metadata 18 | */ 19 | boolean storeAsBin() default true; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/preload/Animal.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model.preload; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeConstructor; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.annotations.ParamFrom; 7 | 8 | import lombok.Data; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | @Data 12 | @SuperBuilder 13 | @AerospikeRecord 14 | public abstract class Animal { 15 | @AerospikeKey 16 | private final String animalId; 17 | 18 | @AerospikeConstructor 19 | public Animal(@ParamFrom("animalId") String animalId) { 20 | super(); 21 | this.animalId = animalId; 22 | } 23 | 24 | public String getAnimalId() { 25 | return animalId; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.configuration; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType; 4 | 5 | public class ReferenceConfig { 6 | private ReferenceType type; 7 | private Boolean lazy; 8 | private Boolean batchLoad; 9 | 10 | public ReferenceConfig() { 11 | } 12 | 13 | public ReferenceConfig(ReferenceType type, boolean lazy, boolean batchLoad) { 14 | this.type = type; 15 | this.lazy = lazy; 16 | this.batchLoad = batchLoad; 17 | } 18 | 19 | public ReferenceType getType() { 20 | return type; 21 | } 22 | 23 | public Boolean getLazy() { 24 | return lazy; 25 | } 26 | 27 | public Boolean getBatchLoad() { 28 | return batchLoad; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/InstantMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import java.time.Instant; 4 | 5 | import com.aerospike.mapper.tools.TypeMapper; 6 | 7 | public class InstantMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | Instant instant = (Instant) value; 15 | return instant.getEpochSecond() * 1_000_000_000 + instant.getNano(); 16 | } 17 | 18 | @Override 19 | public Object fromAerospikeFormat(Object value) { 20 | if (value == null) { 21 | return null; 22 | } 23 | long longValue = (Long) value; 24 | return Instant.ofEpochSecond(longValue / 1_000_000_000, longValue % 1_000_000_000); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import static com.aerospike.client.Value.UseBoolBin; 6 | 7 | public class BooleanMapper extends TypeMapper { 8 | 9 | @Override 10 | public Object toAerospikeFormat(Object value) { 11 | if (value == null) { 12 | return null; 13 | } 14 | if (UseBoolBin) { 15 | return value; 16 | } 17 | return ((Boolean) value) ? 1 : 0; 18 | } 19 | 20 | @Override 21 | public Object fromAerospikeFormat(Object value) { 22 | if (value == null) { 23 | return null; 24 | } 25 | if (UseBoolBin) { 26 | return value; 27 | } 28 | return !Long.valueOf(0).equals(value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.configuration; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 4 | 5 | public class EmbedConfig { 6 | private EmbedType type; 7 | private EmbedType elementType; 8 | private Boolean saveKey; 9 | 10 | public EmbedType getType() { 11 | return type; 12 | } 13 | 14 | public EmbedType getElementType() { 15 | return elementType; 16 | } 17 | 18 | public Boolean getSaveKey() { 19 | return saveKey; 20 | } 21 | 22 | public void setType(EmbedType type) { 23 | this.type = type; 24 | } 25 | 26 | public void setElementType(EmbedType elementType) { 27 | this.elementType = elementType; 28 | } 29 | 30 | public void setSaveKey(Boolean saveKey) { 31 | this.saveKey = saveKey; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.reactor.AerospikeReactorClient; 4 | import com.aerospike.client.reactor.IAerospikeReactorClient; 5 | import com.aerospike.mapper.AeroMapperBaseTest; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.BeforeAll; 8 | 9 | import java.io.IOException; 10 | 11 | public class ReactiveAeroMapperBaseTest extends AeroMapperBaseTest { 12 | protected static IAerospikeReactorClient reactorClient; 13 | 14 | @BeforeAll 15 | public static void setupReactorClass() { 16 | reactorClient = new AerospikeReactorClient(client); 17 | } 18 | 19 | @AfterAll 20 | public static void cleanupReactorClass() throws IOException { 21 | if (reactorClient != null) { 22 | reactorClient.close(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/preload/Dog.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model.preload; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeConstructor; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.annotations.ParamFrom; 6 | 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | @Data 12 | @SuperBuilder 13 | @EqualsAndHashCode(callSuper = true) 14 | @AerospikeRecord(namespace = "test", set = "zoo") 15 | public class Dog extends Animal { 16 | 17 | private final String breed; 18 | 19 | @AerospikeConstructor 20 | public Dog(@ParamFrom("animalId") String dogId, @ParamFrom("breed") String breed) { 21 | super(dogId); 22 | this.breed = breed; 23 | } 24 | 25 | public String getBreed() { 26 | return breed; 27 | } 28 | 29 | public String getDogId() { 30 | return super.getAnimalId(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.utils; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.mapper.tools.ClassCache; 5 | import com.aerospike.mapper.tools.ClassCacheEntry; 6 | import com.aerospike.mapper.tools.IBaseAeroMapper; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | public class MapperUtils { 10 | 11 | private MapperUtils() { 12 | } 13 | 14 | public static ClassCacheEntry getEntryAndValidateNamespace(Class clazz, IBaseAeroMapper mapper) { 15 | ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, mapper); 16 | String namespace = null; 17 | if (entry != null) { 18 | namespace = entry.getNamespace(); 19 | } 20 | if (StringUtils.isBlank(namespace)) { 21 | throw new AerospikeException("Namespace not specified to perform database operation on a record of type " + clazz.getName()); 22 | } 23 | return entry; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import java.time.Instant; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | 7 | @AerospikeRecord 8 | public class Transaction { 9 | private long amount; 10 | private String description; 11 | private Instant time; 12 | private boolean isFraud; 13 | 14 | public long getAmount() { 15 | return amount; 16 | } 17 | 18 | public void setAmount(long amount) { 19 | this.amount = amount; 20 | } 21 | 22 | public String getDescription() { 23 | return description; 24 | } 25 | 26 | public void setDescription(String description) { 27 | this.description = description; 28 | } 29 | 30 | public Instant getTime() { 31 | return time; 32 | } 33 | 34 | public void setTime(Instant time) { 35 | this.time = time; 36 | } 37 | 38 | public boolean isFraud() { 39 | return isFraud; 40 | } 41 | 42 | public void setFraud(boolean isFraud) { 43 | this.isFraud = isFraud; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Branch.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeBin; 4 | import com.aerospike.mapper.annotations.AerospikeEmbed; 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.ParamFrom; 8 | 9 | @AerospikeRecord(namespace = "test", set = "branch") 10 | public class Branch { 11 | @AerospikeKey 12 | private final String id; 13 | @AerospikeBin(name = "addr") 14 | @AerospikeEmbed 15 | private final Address address; 16 | private final String name; 17 | 18 | public Branch(@ParamFrom("id") String id, @ParamFrom("addr") Address address, @ParamFrom("name") String name) { 19 | super(); 20 | this.id = id; 21 | this.address = address; 22 | this.name = name; 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public Address getAddress() { 30 | return address; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/preload/Cat.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model.preload; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeConstructor; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.annotations.ParamFrom; 6 | 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | @Data 12 | @SuperBuilder 13 | @EqualsAndHashCode(callSuper = true) 14 | @AerospikeRecord(namespace = "test", set = "zoo") 15 | public class Cat extends Animal { 16 | 17 | private final String breed; 18 | private final String lifeSpan; 19 | 20 | @AerospikeConstructor 21 | public Cat(@ParamFrom("animalId") String catId, @ParamFrom("breed") String breed, 22 | @ParamFrom("lifeSpan") String lifeSpan) { 23 | super(catId); 24 | this.breed = breed; 25 | this.lifeSpan = lifeSpan; 26 | } 27 | 28 | public String getBreed() { 29 | return breed; 30 | } 31 | 32 | public String getCatId() { 33 | return super.getAnimalId(); 34 | } 35 | 36 | public String getLifeSpan() { 37 | return lifeSpan; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | import com.aerospike.client.Key; 4 | 5 | import java.util.ArrayDeque; 6 | import java.util.Deque; 7 | 8 | /** 9 | * Save the keys. Note that this is effectively a stack of keys, as A can load B which can load C, and C needs B's key, 10 | * not A's. 11 | */ 12 | public class ThreadLocalKeySaver { 13 | 14 | private static final ThreadLocal> threadLocalKeys = ThreadLocal.withInitial(ArrayDeque::new); 15 | 16 | private ThreadLocalKeySaver() { 17 | } 18 | 19 | public static void save(Key key) { 20 | threadLocalKeys.get().addLast(key); 21 | LoadedObjectResolver.begin(); 22 | } 23 | 24 | public static void clear() { 25 | LoadedObjectResolver.end(); 26 | threadLocalKeys.get().removeLast(); 27 | if (threadLocalKeys.get().isEmpty()) { 28 | threadLocalKeys.remove(); 29 | } 30 | } 31 | 32 | public static Key get() { 33 | Deque keys = threadLocalKeys.get(); 34 | if (keys.isEmpty()) { 35 | return null; 36 | } 37 | return keys.getLast(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeRecord.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface AerospikeRecord { 11 | String namespace() default ""; 12 | 13 | String set() default ""; 14 | 15 | String shortName() default ""; 16 | 17 | /** 18 | * The TTL for the record. As this must be a primitive value Integer.MIN_VALUE is used to indicate that the 19 | * value has not been explicitly set. 20 | */ 21 | int ttl() default Integer.MIN_VALUE; 22 | 23 | /** 24 | * Determine whether to add all the bins or not. If true, all bins will be added without having to map them via @AerospikeBin 25 | */ 26 | boolean mapAll() default true; 27 | 28 | int version() default 1; 29 | 30 | boolean sendKey() default false; 31 | 32 | boolean durableDelete() default false; 33 | 34 | String factoryClass() default ""; 35 | 36 | String factoryMethod() default ""; 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/DefaultObjectMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.tools.AeroMapper; 8 | 9 | public class DefaultObjectMappingTest extends AeroMapperBaseTest { 10 | @AerospikeRecord(namespace = "test", set = "testSet") 11 | public static class Child { 12 | public int age; 13 | @AerospikeKey 14 | public int id; 15 | public String name; 16 | } 17 | 18 | @AerospikeRecord(namespace = "test", set = "testSet") 19 | public static class Parent { 20 | @AerospikeKey 21 | public int id; 22 | public Child child; 23 | } 24 | 25 | @Test 26 | public void test() { 27 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 28 | Child child = new Child(); 29 | child.age = 17; 30 | child.id = 3; 31 | child.name = "bob"; 32 | 33 | Parent parent = new Parent(); 34 | parent.id = 1; 35 | parent.child = child; 36 | 37 | mapper.save(parent); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeReference.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Bins marked with AerospikeReference need to exist as a separate entity to the referencing entity. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface AerospikeReference { 14 | /** 15 | * Fields marked as being lazy references will not be read from Aerospike at runtime when the parent class is 16 | * read. Instead, a new object with just the key populated will be created 17 | */ 18 | boolean lazy() default false; 19 | 20 | /** 21 | * When a reference is to be loaded, it can either be loaded inline or it can be loaded via a batch load. The 22 | * batch load is typically significantly more efficient. Set this flag to
false
to prevent the batch load 23 | */ 24 | boolean batchLoad() default true; 25 | 26 | enum ReferenceType { 27 | ID, 28 | DIGEST 29 | } 30 | 31 | ReferenceType type() default ReferenceType.ID; 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build project 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | # Checkout repo using https://github.com/marketplace/actions/checkout 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 8 24 | 25 | # Aerospike cluster for integration tests (https://github.com/reugn/github-action-aerospike) 26 | - name: Set up Aerospike Database 27 | uses: reugn/github-action-aerospike@v1 28 | with: 29 | server-version: 7.2.0.6 30 | 31 | # See: https://github.com/actions/cache/blob/master/examples.md#java---maven 32 | - name: Cache local Maven repository 33 | uses: actions/cache@v4 34 | with: 35 | path: ~/.m2/repository 36 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 37 | restore-keys: | 38 | ${{ runner.os }}-maven- 39 | 40 | - name: Build with Maven 41 | run: mvn clean test -B -U -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/PrimitiveDefaults.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | public class PrimitiveDefaults { 4 | // These gets initialized to their default values 5 | private static boolean DEFAULT_BOOLEAN; 6 | private static byte DEFAULT_BYTE; 7 | private static short DEFAULT_SHORT; 8 | private static int DEFAULT_INT; 9 | private static long DEFAULT_LONG; 10 | private static float DEFAULT_FLOAT; 11 | private static double DEFAULT_DOUBLE; 12 | 13 | public static Object getDefaultValue(Class clazz) { 14 | if (clazz.equals(boolean.class)) { 15 | return DEFAULT_BOOLEAN; 16 | } else if (clazz.equals(byte.class)) { 17 | return DEFAULT_BYTE; 18 | } else if (clazz.equals(short.class)) { 19 | return DEFAULT_SHORT; 20 | } else if (clazz.equals(int.class)) { 21 | return DEFAULT_INT; 22 | } else if (clazz.equals(long.class)) { 23 | return DEFAULT_LONG; 24 | } else if (clazz.equals(float.class)) { 25 | return DEFAULT_FLOAT; 26 | } else if (clazz.equals(double.class)) { 27 | return DEFAULT_DOUBLE; 28 | } else { 29 | return null; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/BigIntegerBigDecimalTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.tools.AeroMapper; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.math.BigDecimal; 12 | import java.math.BigInteger; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class BigIntegerBigDecimalTest extends AeroMapperBaseTest { 17 | 18 | @Data 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @AerospikeRecord(namespace = "test", set = "bigType") 22 | public static class BigTypes { 23 | @AerospikeKey 24 | public int id; 25 | public String name; 26 | public BigDecimal bigD; 27 | public BigInteger bigI; 28 | } 29 | 30 | @Test 31 | public void runTest() { 32 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 33 | BigTypes types = new BigTypes(1, "test", new BigDecimal("123456789.123456789"), new BigInteger("12345678901234567890")); 34 | mapper.save(types); 35 | BigTypes readTypes = mapper.read(BigTypes.class, 1); 36 | assertEquals(types, readTypes); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.configuration; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | public class KeyConfig { 6 | private String field; 7 | private String getter; 8 | private String setter; 9 | private Boolean storeAsBin; 10 | 11 | public String getField() { 12 | return field; 13 | } 14 | 15 | public String getGetter() { 16 | return getter; 17 | } 18 | 19 | public String getSetter() { 20 | return setter; 21 | } 22 | 23 | public Boolean getStoreAsBin() { 24 | return storeAsBin; 25 | } 26 | 27 | public void setStoreAsBin(boolean value) { 28 | this.storeAsBin = value; 29 | } 30 | 31 | public void setField(String field) { 32 | this.field = field; 33 | } 34 | 35 | public void setGetter(String getter) { 36 | this.getter = getter; 37 | } 38 | 39 | public void setSetter(String setter) { 40 | this.setter = setter; 41 | } 42 | 43 | public boolean isGetter(String methodName) { 44 | return (!StringUtils.isBlank(this.getter)) && this.getter.equals(methodName); 45 | } 46 | 47 | public boolean isSetter(String methodName) { 48 | return (!StringUtils.isBlank(this.setter)) && this.setter.equals(methodName); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/snyk-scan.yml: -------------------------------------------------------------------------------- 1 | name: Snyk scan 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | snyk-security: 12 | permissions: 13 | security-events: write 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Run Snyk to check for vulnerabilities 20 | uses: snyk/actions/maven@master 21 | continue-on-error: true # To make sure that SARIF upload gets called 22 | env: 23 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 24 | with: 25 | args: --all-projects --sarif-file-output=snyk.sarif 26 | 27 | - name: Check output file 28 | id: out-file 29 | run: | 30 | if test -f "snyk.sarif"; then 31 | echo "::set-output name=exists::true"; else 32 | echo "::set-output name=exists::false" 33 | fi 34 | 35 | - name: Handle undefined security-severity 36 | if: steps.out-file.outputs.exists == 'true' 37 | run: | 38 | sed -i 's/"security-severity": "undefined"/"security-severity": "0"/g' snyk.sarif 39 | 40 | - name: Upload result to GitHub Code Scanning 41 | if: steps.out-file.outputs.exists == 'true' 42 | uses: github/codeql-action/upload-sarif@v3 43 | with: 44 | sarif_file: snyk.sarif -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/MessagesMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | import static org.junit.jupiter.api.Assertions.fail; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.aerospike.client.AerospikeException; 9 | import com.aerospike.mapper.annotations.AerospikeKey; 10 | import com.aerospike.mapper.annotations.AerospikeRecord; 11 | import com.aerospike.mapper.annotations.AerospikeVersion; 12 | import com.aerospike.mapper.tools.AeroMapper; 13 | 14 | public class MessagesMappingTest extends AeroMapperBaseTest { 15 | @AerospikeRecord(namespace = "test", set = "testSet") 16 | public static class InvalidVersion { 17 | @AerospikeKey 18 | public int id; 19 | @AerospikeVersion(max = -3) 20 | public int versioned; 21 | } 22 | 23 | @Test 24 | public void testVersion() { 25 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 26 | InvalidVersion invalid = new InvalidVersion(); 27 | invalid.id = 1; 28 | invalid.versioned = 3; 29 | try { 30 | mapper.save(invalid); 31 | fail("Exception should have been thrown"); 32 | } catch (AerospikeException e) { 33 | assertTrue(e.getMessage().toLowerCase().contains("version"), "Inaccurate error message does not reference version"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/ObjectReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeEmbed; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 9 | import com.aerospike.mapper.tools.AeroMapper; 10 | 11 | public class ObjectReferencesTest extends AeroMapperBaseTest { 12 | @AerospikeRecord(namespace = "test", set = "parent") 13 | public static class Parent { 14 | @AerospikeKey 15 | private int id; 16 | @AerospikeEmbed(type = EmbedType.LIST) 17 | private Child child; 18 | } 19 | 20 | @AerospikeRecord(namespace = "test", set = "child") 21 | public static class Child { 22 | @AerospikeKey 23 | private String key; 24 | private int age; 25 | private String name; 26 | } 27 | 28 | @Test 29 | public void test() { 30 | Child child = new Child(); 31 | child.key = "child1"; 32 | child.age = 17; 33 | child.name = "bob"; 34 | 35 | Parent parent = new Parent(); 36 | parent.id = 1; 37 | parent.child = child; 38 | 39 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 40 | mapper.save(parent); 41 | mapper.save(child); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | import com.aerospike.client.Key; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class LoadedObjectResolver { 9 | 10 | private static final ThreadLocal threadLocalObjects = ThreadLocal.withInitial(LoadedObjectMap::new); 11 | 12 | private LoadedObjectResolver() { 13 | } 14 | 15 | public static void begin() { 16 | LoadedObjectMap map = threadLocalObjects.get(); 17 | map.referenceCount++; 18 | } 19 | 20 | public static void end() { 21 | LoadedObjectMap map = threadLocalObjects.get(); 22 | map.referenceCount--; 23 | if (map.referenceCount == 0) { 24 | threadLocalObjects.remove(); 25 | } 26 | } 27 | 28 | public static void setObjectForCurrentKey(Object object) { 29 | Key currentKey = ThreadLocalKeySaver.get(); 30 | LoadedObjectMap map = threadLocalObjects.get(); 31 | if (currentKey != null) { 32 | map.objectMap.put(currentKey, object); 33 | } 34 | } 35 | 36 | public static Object get(Key key) { 37 | LoadedObjectMap map = threadLocalObjects.get(); 38 | return map.objectMap.get(key); 39 | } 40 | 41 | private static class LoadedObjectMap { 42 | private final Map objectMap = new HashMap<>(); 43 | private int referenceCount = 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | 6 | @AerospikeRecord(namespace = "test", set = "product") 7 | public class Product { 8 | 9 | private long id; 10 | private String name; 11 | private ProductType type; 12 | private int version; 13 | 14 | public Product() { 15 | } 16 | 17 | public Product(long id, int version, String name, ProductType type) { 18 | super(); 19 | this.id = id; 20 | this.name = name; 21 | this.type = type; 22 | this.version = version; 23 | } 24 | 25 | @AerospikeKey 26 | public String getKey() { 27 | return id + ":" + version; 28 | } 29 | 30 | public long getId() { 31 | return id; 32 | } 33 | 34 | public void setId(long id) { 35 | this.id = id; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public ProductType getType() { 47 | return type; 48 | } 49 | 50 | public void setType(ProductType type) { 51 | this.type = type; 52 | } 53 | 54 | public int getVersion() { 55 | return version; 56 | } 57 | 58 | public void setVersion(int version) { 59 | this.version = version; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.utils; 2 | 3 | public class ParserUtils { 4 | private static final ParserUtils instance = new ParserUtils(); 5 | 6 | public static ParserUtils getInstance() { 7 | return instance; 8 | } 9 | 10 | private ParserUtils() { 11 | } 12 | 13 | public String get(String value) { 14 | return parseString(value); 15 | } 16 | 17 | private String parseString(String value) { 18 | if (value == null || value.length() <= 3) { 19 | return value; 20 | } 21 | if ((value.startsWith("${") || value.startsWith("#{")) && value.endsWith("}")) { 22 | // Strip off the identifying tokens and split into value and default 23 | String[] values = value.substring(2, value.length() - 1).split(":"); 24 | 25 | String translatedValue; 26 | if (value.startsWith("${")) { 27 | translatedValue = System.getProperty(values[0]); 28 | } else { 29 | translatedValue = System.getenv(values[0]); 30 | } 31 | 32 | if (translatedValue != null) { 33 | return translatedValue; 34 | } 35 | if (values.length > 1) { 36 | // A default was provided, use it. 37 | return values[1]; 38 | } 39 | // No environment/property variable was found, no default, return null 40 | return null; 41 | } 42 | return value; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import com.aerospike.client.policy.GenerationPolicy; 4 | import com.aerospike.client.policy.WritePolicy; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Mark a field or property to be used for optimistic concurrency control using Aerospike's generation field. 13 | *

14 | * The field or property must be of Integer or int type. When reading an object which has a field marked 15 | * with @AerospikeGeneration, the returned record's generation field will be mapped into the @AerospikeGeneration field. 16 | * When writing the record, if the @AerospikeGeneration field is non-zero, the generation will be set in the 17 | * {@link WritePolicy#generation} field and the {@link WritePolicy#generationPolicy} will be set to 18 | * {@link GenerationPolicy#EXPECT_GEN_EQUAL}. 19 | *

20 | * Example usage: 21 | *

22 |  * @AerospikeRecord(namespace = "test", set = "account")
23 |  * public class Account {
24 |  *     @AerospikeKey
25 |  *     private int id;
26 |  *     @AerospikeBin
27 |  *     private String name;
28 |  *     @AerospikeGeneration
29 |  *     private int generation;
30 |  * }
31 |  * 
32 | * 33 | * @author timfaulkes 34 | */ 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Target({ElementType.FIELD, ElementType.METHOD}) 37 | public @interface AerospikeGeneration { 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveObjectReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | public class ReactiveObjectReferencesTest extends ReactiveAeroMapperBaseTest { 11 | 12 | @AerospikeRecord(namespace = "test", set = "parent") 13 | public static class Parent { 14 | @AerospikeKey 15 | private int id; 16 | @AerospikeEmbed(type = AerospikeEmbed.EmbedType.LIST) 17 | private Child child; 18 | } 19 | 20 | @AerospikeRecord(namespace = "test", set = "child") 21 | public static class Child { 22 | @AerospikeKey 23 | private String key; 24 | private int age; 25 | private String name; 26 | } 27 | 28 | @Test 29 | public void test() { 30 | Child child = new Child(); 31 | child.key = "child1"; 32 | child.age = 17; 33 | child.name = "bob"; 34 | 35 | Parent parent = new Parent(); 36 | parent.id = 1; 37 | parent.child = child; 38 | 39 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 40 | reactiveMapper.save(parent).subscribeOn(Schedulers.parallel()).block(); 41 | reactiveMapper.save(child).subscribeOn(Schedulers.parallel()).block(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeVersion.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Specify the version of a record. Records without the @AerospikeVersion annotation are assumed to be version 1 of a record. 10 | *

11 | * Versions are typically used for embedded records to be stored in a list. Since a list item has no name associated with it, 12 | * the order of the attributes determines which part of the object to map the value to. By default the items are mapped 13 | * alphabetically, but this presents issues is an item is added or removed from the object. 14 | *

15 | * For example, consider a Person object with an Account stored as a list:

16 | *

17 |  * @AerospikeRecord(namespace = "test", set = "account")
18 |  * public class Account {
19 |  *     @AerospikeBin
20 |  *     private String name;
21 |  *     @AerospikeBin
22 |  *     private int balance
23 |  * }
24 |  *
25 |  * @AerospikeRecord(namespace = "test", set = "people")
26 |  * public class Person {
27 |  *     @AerospikeBin
28 |  *     private String name;
29 |  *     @AerospikeBin
30 |  *     @AerospikeEmbed(type = EmbedType.LIST)
31 |  *     private Account account;
32 |  * }
33 |  * 
34 | * 35 | * @author timfaulkes 36 | */ 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @Target({ElementType.FIELD, ElementType.METHOD}) 39 | public @interface AerospikeVersion { 40 | int min() default 1; 41 | 42 | int max() default Integer.MAX_VALUE; 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/JavaMapperApplication.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples; 2 | 3 | import com.aerospike.client.AerospikeClient; 4 | import com.aerospike.client.IAerospikeClient; 5 | import com.aerospike.mapper.examples.model.Customer; 6 | import com.aerospike.mapper.tools.AeroMapper; 7 | import com.fasterxml.jackson.core.JsonProcessingException; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.fasterxml.jackson.databind.ObjectWriter; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | 13 | public class JavaMapperApplication extends ApplicationBase { 14 | 15 | public void run() throws JsonProcessingException { 16 | IAerospikeClient client = new AerospikeClient(null, "127.0.0.1", 3000); 17 | 18 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 19 | Customer customer = createAndPopulateCustomer(); 20 | 21 | mapper.save(customer); 22 | Customer customer2 = null; 23 | for (int i = 0; i < 100; i++) { 24 | long now = System.nanoTime(); 25 | customer2 = mapper.read(Customer.class, customer.getCustomerId()); 26 | System.out.printf("Customer graph read time: %.3fms%n", (System.nanoTime() - now) / 1000000f); 27 | } 28 | 29 | ObjectWriter objectWriter = new ObjectMapper().writerWithDefaultPrettyPrinter(); 30 | String readString = objectWriter.writeValueAsString(customer2); 31 | System.out.println(readString); 32 | String originalObject = objectWriter.writeValueAsString(customer); 33 | assertEquals(originalObject, readString); 34 | 35 | client.close(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/LocalDateTimeMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.mapper.tools.TypeMapper; 4 | 5 | import java.time.LocalDate; 6 | import java.time.LocalDateTime; 7 | import java.time.LocalTime; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * Map a java.time.LocalDateTime to Aerospike. 13 | *

14 | * If we store the data in a single long we can only store to the millisecond 15 | * precision, like: 16 | *

17 |  * return Date.from(((LocalDateTime) value).toInstant(ZoneOffset.UTC)).getTime();
18 |  * 
19 | * Whereas LocalDateTime can store down to the nanosecond precision. To store this properly 20 | * we will split it into date and time components and store both in a list. 21 | * 22 | * @author tfaulkes 23 | */ 24 | public class LocalDateTimeMapper extends TypeMapper { 25 | 26 | @Override 27 | public Object toAerospikeFormat(Object value) { 28 | if (value == null) { 29 | return null; 30 | } 31 | LocalDateTime dateTime = (LocalDateTime) value; 32 | LocalDate date = dateTime.toLocalDate(); 33 | LocalTime time = dateTime.toLocalTime(); 34 | return Arrays.asList(date.toEpochDay(), time.toNanoOfDay()); 35 | } 36 | 37 | @Override 38 | public Object fromAerospikeFormat(Object value) { 39 | if (value == null) { 40 | return null; 41 | } 42 | @SuppressWarnings("unchecked") 43 | List values = (List) value; 44 | LocalDate date = LocalDate.ofEpochDay(values.get(0)); 45 | LocalTime time = LocalTime.ofNanoOfDay(values.get(1)); 46 | return LocalDateTime.of(date, time); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model.accounts; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.aerospike.mapper.annotations.AerospikeBin; 9 | import com.aerospike.mapper.annotations.AerospikeRecord; 10 | import com.aerospike.mapper.annotations.ParamFrom; 11 | import com.aerospike.mapper.examples.model.Property; 12 | 13 | @AerospikeRecord(namespace = "test", set = "portfolio") 14 | public class PortfolioAccount extends Account { 15 | 16 | @AerospikeBin(name = "props") 17 | private final List properties; 18 | @AerospikeBin(name = "excls") 19 | private int[] contractClausesExcluded; 20 | private final Map interestRates; 21 | 22 | public PortfolioAccount(@ParamFrom("id") String accountId, @ParamFrom("custId") String customerId, @ParamFrom("title") String title, @ParamFrom("type") AccountType type) { 23 | super(accountId, customerId, title, type); 24 | this.properties = new ArrayList<>(); 25 | this.contractClausesExcluded = new int[0]; 26 | this.interestRates = new HashMap<>(); 27 | } 28 | 29 | public void setContractClausesExcluded(int... clauseIds) { 30 | this.contractClausesExcluded = clauseIds; 31 | } 32 | 33 | public void addPropertyToPortfolio(Property property, float interestRate) { 34 | this.properties.add(property); 35 | this.interestRates.put(property.getId(), interestRate); 36 | } 37 | 38 | public List getProperties() { 39 | return properties; 40 | } 41 | 42 | public int[] getContractClausesExcluded() { 43 | return contractClausesExcluded; 44 | } 45 | 46 | public Map getInterestRates() { 47 | return interestRates; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/InheritanceTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeEmbed; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.tools.AeroMapper; 9 | 10 | public class InheritanceTest extends AeroMapperBaseTest { 11 | @AerospikeRecord(namespace = "test", set = "A") 12 | public static class A { 13 | @AerospikeKey 14 | public int id; 15 | public String name; 16 | @AerospikeEmbed 17 | public B topB; 18 | @AerospikeEmbed 19 | public B cAsAb; 20 | @AerospikeEmbed 21 | public B dAsAb; 22 | } 23 | 24 | @AerospikeRecord(namespace = "test", set = "B") 25 | public static class B { 26 | @AerospikeKey 27 | public int id; 28 | public String name; 29 | } 30 | 31 | // Inherit namespace and set from B 32 | @AerospikeRecord 33 | public static class C extends B { 34 | public String otherName; 35 | } 36 | 37 | // Map into a separate table 38 | @AerospikeRecord(namespace = "test", set = "D") 39 | public static class D extends B { 40 | public int age; 41 | } 42 | 43 | @Test 44 | public void run() { 45 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 46 | A a = new A(); 47 | a.id = 1; 48 | a.name = "test"; 49 | 50 | B b = new B(); 51 | b.id = 2; 52 | b.name = "b"; 53 | 54 | C c = new C(); 55 | c.id = 3; 56 | c.name = "c"; 57 | c.otherName = "masquerading as a B"; 58 | 59 | D d = new D(); 60 | d.id = 4; 61 | d.name = "d"; 62 | d.age = 312; 63 | a.topB = b; 64 | a.cAsAb = c; 65 | a.dAsAb = d; 66 | 67 | mapper.save(a, b, c, d); 68 | A a2 = mapper.read(A.class, a.id); 69 | compare(a, a2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Valuation.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import java.util.Date; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeEmbed; 6 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | 9 | @AerospikeRecord 10 | public class Valuation { 11 | private Date date; 12 | private long amount; 13 | private long amountMargin; 14 | private String valuer; 15 | private long valuerCompanyId; 16 | @AerospikeEmbed(type = EmbedType.LIST) 17 | private Address valuerAddress; 18 | 19 | public Valuation() { 20 | } 21 | 22 | public Valuation(Date date, long amount, long amountMargin, String valuer, long valuerCompanyId, Address valuerAddress) { 23 | super(); 24 | this.date = date; 25 | this.amount = amount; 26 | this.amountMargin = amountMargin; 27 | this.valuer = valuer; 28 | this.valuerCompanyId = valuerCompanyId; 29 | this.valuerAddress = valuerAddress; 30 | } 31 | 32 | public Date getDate() { 33 | return date; 34 | } 35 | 36 | public void setDate(Date date) { 37 | this.date = date; 38 | } 39 | 40 | public long getAmount() { 41 | return amount; 42 | } 43 | 44 | public void setAmount(long amount) { 45 | this.amount = amount; 46 | } 47 | 48 | public long getAmountMargin() { 49 | return amountMargin; 50 | } 51 | 52 | public void setAmountMargin(long amountMargin) { 53 | this.amountMargin = amountMargin; 54 | } 55 | 56 | public String getValuer() { 57 | return valuer; 58 | } 59 | 60 | public void setValuer(String valuer) { 61 | this.valuer = valuer; 62 | } 63 | 64 | public long getValuerCompanyId() { 65 | return valuerCompanyId; 66 | } 67 | 68 | public void setValuerCompanyId(long valuerCompanyId) { 69 | this.valuerCompanyId = valuerCompanyId; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Address.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeBin; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.annotations.ParamFrom; 7 | 8 | // Addresses are only ever embedded in parent records, so do not need information to map to Aerospike. 9 | @AerospikeRecord 10 | public class Address { 11 | @AerospikeKey 12 | private String line1; 13 | private String line2; 14 | private String city; 15 | private String state; 16 | @AerospikeBin(name = "zip") 17 | private String zipCode; 18 | 19 | public Address( 20 | @ParamFrom("line1") String line1, 21 | @ParamFrom("line2") String line2, 22 | @ParamFrom("city") String city, 23 | @ParamFrom("state") String state, 24 | @ParamFrom("zip") String zipCode) { 25 | super(); 26 | this.line1 = line1; 27 | this.line2 = line2; 28 | this.city = city; 29 | this.state = state; 30 | this.zipCode = zipCode; 31 | } 32 | 33 | public String getLine1() { 34 | return line1; 35 | } 36 | 37 | public void setLine1(String line1) { 38 | this.line1 = line1; 39 | } 40 | 41 | public String getLine2() { 42 | return line2; 43 | } 44 | 45 | public void setLine2(String line2) { 46 | this.line2 = line2; 47 | } 48 | 49 | public String getCity() { 50 | return city; 51 | } 52 | 53 | public void setCity(String city) { 54 | this.city = city; 55 | } 56 | 57 | public String getState() { 58 | return state; 59 | } 60 | 61 | public void setState(String state) { 62 | this.state = state; 63 | } 64 | 65 | public String getZipCode() { 66 | return zipCode; 67 | } 68 | 69 | public void setZipCode(String zipCode) { 70 | this.zipCode = zipCode; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Checkbook.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import java.util.Date; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.ParamFrom; 8 | 9 | @AerospikeRecord(namespace = "test", set = "chkbook") 10 | public class Checkbook { 11 | private String acctId; 12 | private long first; 13 | private long last; 14 | private final Date issued; 15 | private boolean recalled; 16 | private Branch issuer; 17 | 18 | public Checkbook(@ParamFrom("acctId") String acctId, @ParamFrom("first") long first, @ParamFrom("last") long last, @ParamFrom("issued") Date issued) { 19 | super(); 20 | this.acctId = acctId; 21 | this.first = first; 22 | this.last = last; 23 | this.issued = issued; 24 | } 25 | 26 | public long getFirst() { 27 | return first; 28 | } 29 | 30 | public long getLast() { 31 | return last; 32 | } 33 | 34 | public Date getIssued() { 35 | return issued; 36 | } 37 | 38 | public boolean isRecalled() { 39 | return recalled; 40 | } 41 | 42 | public void setRecalled(boolean recalled) { 43 | this.recalled = recalled; 44 | } 45 | 46 | public Branch getIssuer() { 47 | return issuer; 48 | } 49 | 50 | public void setIssuer(Branch issuer) { 51 | this.issuer = issuer; 52 | } 53 | 54 | /* The checkbook class does not have a key in it's own right, it uses a composite key */ 55 | @AerospikeKey 56 | public String getKey() { 57 | return String.format("%s-%d-%d", acctId, first, last); 58 | } 59 | 60 | @AerospikeKey(setter = true) 61 | public void setKey(String key) { 62 | int index = key.lastIndexOf('-'); 63 | this.last = Long.parseLong(key.substring(index + 1)); 64 | int firstIndex = key.lastIndexOf('-', index - 1); 65 | this.first = Long.parseLong(key.substring(firstIndex + 1, index)); 66 | this.acctId = key.substring(0, firstIndex); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AeroMapperListOfObjectTransformTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.aerospike.mapper.annotations.AerospikeEmbed; 11 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 12 | import com.aerospike.mapper.annotations.AerospikeKey; 13 | import com.aerospike.mapper.annotations.AerospikeRecord; 14 | import com.aerospike.mapper.tools.AeroMapper; 15 | 16 | public class AeroMapperListOfObjectTransformTest extends AeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "testSet", sendKey = true) 19 | public static class OwningClass { 20 | @AerospikeKey 21 | private int id; 22 | @AerospikeEmbed(elementType = EmbedType.LIST) 23 | private final List children; 24 | 25 | public OwningClass() { 26 | children = new ArrayList<>(); 27 | } 28 | } 29 | 30 | @AerospikeRecord() 31 | public static class OwnedClass { 32 | public String name; 33 | public int id; 34 | @AerospikeKey 35 | public Date date; 36 | 37 | public OwnedClass(String name, int id, Date date) { 38 | super(); 39 | this.name = name; 40 | this.id = id; 41 | this.date = date; 42 | } 43 | 44 | public OwnedClass() { 45 | } 46 | } 47 | 48 | private AeroMapper mapper; 49 | 50 | @BeforeEach 51 | public void setup() { 52 | mapper = new AeroMapper.Builder(client).build(); 53 | client.truncate(null, "test", "testSet", null); 54 | } 55 | 56 | @Test 57 | public void test() { 58 | OwningClass owner = new OwningClass(); 59 | owner.id = 1; 60 | owner.children.add(new OwnedClass("a", 1, new Date())); 61 | owner.children.add(new OwnedClass("b", 2, new Date(new Date().getTime() - 5000))); 62 | owner.children.add(new OwnedClass("c", 3, new Date(new Date().getTime() - 10000))); 63 | 64 | mapper.save(owner); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/InsertOnlyTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.client.Key; 5 | import com.aerospike.client.Record; 6 | import com.aerospike.client.policy.WritePolicy; 7 | import com.aerospike.mapper.annotations.AerospikeKey; 8 | import com.aerospike.mapper.annotations.AerospikeRecord; 9 | import com.aerospike.mapper.tools.AeroMapper; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | 16 | public class InsertOnlyTest extends AeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "testSet") 19 | public static class DataClass { 20 | @AerospikeKey 21 | int a; 22 | int b; 23 | int c; 24 | int d; 25 | int e; 26 | } 27 | 28 | @BeforeEach 29 | public void setup() { 30 | client.delete(null, new Key("test", "testSet", 1)); 31 | } 32 | 33 | @Test 34 | public void testInsertOnly() { 35 | WritePolicy writePolicy = new WritePolicy(client.getWritePolicyDefault()); 36 | writePolicy.totalTimeout = 2000; 37 | writePolicy.socketTimeout = 100; 38 | AeroMapper mapper = new AeroMapper.Builder(client) 39 | .withWritePolicy(writePolicy).forClasses(DataClass.class) 40 | .build(); 41 | 42 | DataClass dataClass = new DataClass(); 43 | dataClass.a = 1; 44 | dataClass.b = 2; 45 | dataClass.c = 3; 46 | dataClass.d = 4; 47 | dataClass.e = 5; 48 | 49 | // Insert 50 | mapper.insert(dataClass); 51 | 52 | Key key = new Key("test", "testSet", 1); 53 | Record record = client.get(null, key); 54 | assertEquals(5, record.bins.size()); 55 | assertEquals(3, record.getInt("c")); 56 | 57 | // Try to insert again and get an exception 58 | dataClass.c = 9; 59 | dataClass.e = 11; 60 | assertThrows(AerospikeException.class, () -> mapper.insert(dataClass, "a", "c", "e")); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/NamespacePlaceHolderTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.AeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.Properties; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class NamespacePlaceHolderTest extends AeroMapperBaseTest { 14 | 15 | @AerospikeRecord(namespace = "${namespace.name:default}", set = "set1") 16 | public static class PlaceHolderModel { 17 | 18 | @AerospikeKey 19 | String id; 20 | String name; 21 | String email; 22 | } 23 | 24 | @Test 25 | public void shouldSave() { 26 | Properties props = System.getProperties(); 27 | props.setProperty("namespace.name", "test"); 28 | 29 | PlaceHolderModel placeHolderModel1 = new PlaceHolderModel(); 30 | placeHolderModel1.id = "id1"; 31 | placeHolderModel1.name = "name1"; 32 | placeHolderModel1.email = "name1@gmail.com"; 33 | 34 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 35 | mapper.save(placeHolderModel1); 36 | 37 | PlaceHolderModel placeHolderModelRead = mapper.read(PlaceHolderModel.class, placeHolderModel1.id); 38 | assertEquals(placeHolderModel1.email, placeHolderModelRead.email); 39 | } 40 | 41 | @Test 42 | public void shouldFailOnNonExistingNamespace() { 43 | Properties props = System.getProperties(); 44 | props.setProperty("namespace.name", "non-existing-namespace"); 45 | 46 | PlaceHolderModel placeHolderModel1 = new PlaceHolderModel(); 47 | placeHolderModel1.id = "id1"; 48 | placeHolderModel1.name = "name1"; 49 | placeHolderModel1.email = "name1@gmail.com"; 50 | 51 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 52 | 53 | try { 54 | mapper.save(placeHolderModel1); 55 | fail(); 56 | } catch (AerospikeException ae) { 57 | assertTrue(true); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Property.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.aerospike.mapper.annotations.AerospikeBin; 7 | import com.aerospike.mapper.annotations.AerospikeEmbed; 8 | import com.aerospike.mapper.annotations.AerospikeKey; 9 | import com.aerospike.mapper.annotations.AerospikeRecord; 10 | import com.aerospike.mapper.annotations.ParamFrom; 11 | 12 | @AerospikeRecord(namespace = "test", set = "property") 13 | public class Property { 14 | 15 | @AerospikeKey 16 | private final long id; 17 | @AerospikeEmbed 18 | private final Address address; 19 | @AerospikeEmbed 20 | private final List valuations; 21 | @AerospikeBin(name = "cyear") 22 | private final int constructionYear; 23 | @AerospikeBin(name = "cnstrn") 24 | private String construction; 25 | private String notes; 26 | 27 | public Property(@ParamFrom("id") long id, @ParamFrom("address") Address address, @ParamFrom("cyear") int constructionYear, @ParamFrom("cnstrn") String construction) { 28 | super(); 29 | this.id = id; 30 | this.address = address; 31 | this.constructionYear = constructionYear; 32 | this.construction = construction; 33 | this.valuations = new ArrayList<>(); 34 | } 35 | 36 | public long getId() { 37 | return id; 38 | } 39 | 40 | public List getValuations() { 41 | return valuations; 42 | } 43 | 44 | public Property addValuation(Valuation valuation) { 45 | this.valuations.add(valuation); 46 | return this; 47 | } 48 | 49 | public String getConstruction() { 50 | return construction; 51 | } 52 | 53 | public void setConstruction(String construction) { 54 | this.construction = construction; 55 | } 56 | 57 | public String getNotes() { 58 | return notes; 59 | } 60 | 61 | public void setNotes(String notes) { 62 | this.notes = notes; 63 | } 64 | 65 | public Address getAddress() { 66 | return address; 67 | } 68 | 69 | public int getConstructionYear() { 70 | return constructionYear; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model.accounts; 2 | 3 | import java.util.Date; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeBin; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.ParamFrom; 8 | import com.aerospike.mapper.examples.model.InterestType; 9 | import com.aerospike.mapper.examples.model.Property; 10 | 11 | // Loan account rolls up under the Account 12 | @AerospikeRecord 13 | public class LoanAccount extends Account { 14 | 15 | @AerospikeBin(name = "prop") 16 | private Property securityProperty; 17 | @AerospikeBin(name = "intType") 18 | private InterestType interestType; 19 | @AerospikeBin(name = "orig") 20 | private final Date originationDate; 21 | @AerospikeBin(name = "exp") 22 | private final Date expirationDate; 23 | private final float rate; 24 | 25 | public LoanAccount( 26 | @ParamFrom("id") String accountId, 27 | @ParamFrom("custId") String customerId, 28 | @ParamFrom("title") String title, 29 | @ParamFrom("type") AccountType type, 30 | @ParamFrom("orig") Date originationDate, 31 | @ParamFrom("exp") Date expirationDate, 32 | @ParamFrom("rate") float rate) { 33 | super(accountId, customerId, title, type); 34 | this.rate = rate; 35 | this.expirationDate = expirationDate; 36 | this.originationDate = originationDate; 37 | } 38 | 39 | public Property getSecurityProperty() { 40 | return securityProperty; 41 | } 42 | 43 | public void setSecurityProperty(Property securityProperty) { 44 | this.securityProperty = securityProperty; 45 | } 46 | 47 | public InterestType getInterestType() { 48 | return interestType; 49 | } 50 | 51 | public void setInterestType(InterestType interestType) { 52 | this.interestType = interestType; 53 | } 54 | 55 | public Date getOriginationDate() { 56 | return originationDate; 57 | } 58 | 59 | public Date getExpirationDate() { 60 | return expirationDate; 61 | } 62 | 63 | public float getRate() { 64 | return rate; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveInheritanceTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | public class ReactiveInheritanceTest extends ReactiveAeroMapperBaseTest { 11 | @AerospikeRecord(namespace = "test", set = "A") 12 | public static class A { 13 | @AerospikeKey 14 | public int id; 15 | public String name; 16 | @AerospikeEmbed 17 | public B topB; 18 | @AerospikeEmbed 19 | public B cAsAb; 20 | @AerospikeEmbed 21 | public B dAsAb; 22 | } 23 | 24 | @AerospikeRecord(namespace = "test", set = "B") 25 | public static class B { 26 | @AerospikeKey 27 | public int id; 28 | public String name; 29 | } 30 | 31 | // Inherit namespace and set from B 32 | @AerospikeRecord 33 | public static class C extends B { 34 | public String otherName; 35 | } 36 | 37 | // Map into a separate table 38 | @AerospikeRecord(namespace = "test", set = "D") 39 | public static class D extends B { 40 | public int age; 41 | } 42 | 43 | @Test 44 | public void run() { 45 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 46 | A a = new A(); 47 | a.id = 1; 48 | a.name = "test"; 49 | 50 | B b = new B(); 51 | b.id = 2; 52 | b.name = "b"; 53 | 54 | C c = new C(); 55 | c.id = 3; 56 | c.name = "c"; 57 | c.otherName = "masquerading as a B"; 58 | 59 | D d = new D(); 60 | d.id = 4; 61 | d.name = "d"; 62 | d.age = 312; 63 | a.topB = b; 64 | a.cAsAb = c; 65 | a.dAsAb = d; 66 | 67 | reactiveMapper.save(a, b, c, d).subscribeOn(Schedulers.parallel()).collectList().block(); 68 | A a2 = reactiveMapper.read(A.class, a.id).subscribeOn(Schedulers.parallel()).block(); 69 | compare(a, a2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.client.Key; 5 | import com.aerospike.client.Record; 6 | import com.aerospike.client.policy.WritePolicy; 7 | import com.aerospike.mapper.annotations.AerospikeKey; 8 | import com.aerospike.mapper.annotations.AerospikeRecord; 9 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | 16 | public class ReactiveInsertOnlyTest extends ReactiveAeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "testSet") 19 | public static class DataClass { 20 | @AerospikeKey 21 | int a; 22 | int b; 23 | int c; 24 | int d; 25 | int e; 26 | } 27 | 28 | @BeforeEach 29 | public void setup() { 30 | client.delete(null, new Key("test", "testSet", 1)); 31 | } 32 | 33 | @Test 34 | public void testInsertOnly() { 35 | WritePolicy writePolicy = new WritePolicy(client.getWritePolicyDefault()); 36 | writePolicy.totalTimeout = 2000; 37 | writePolicy.socketTimeout = 100; 38 | ReactiveAeroMapper mapper = new ReactiveAeroMapper.Builder(reactorClient) 39 | .withWritePolicy(writePolicy).forClasses(DataClass.class) 40 | .build(); 41 | 42 | DataClass dataClass = new DataClass(); 43 | dataClass.a = 1; 44 | dataClass.b = 2; 45 | dataClass.c = 3; 46 | dataClass.d = 4; 47 | dataClass.e = 5; 48 | 49 | // Insert 50 | mapper.insert(dataClass).block(); 51 | 52 | Key key = new Key("test", "testSet", 1); 53 | Record record = client.get(null, key); 54 | assertEquals(5, record.bins.size()); 55 | assertEquals(3, record.getInt("c")); 56 | 57 | // Try to insert again and get an exception 58 | dataClass.c = 9; 59 | dataClass.e = 11; 60 | assertThrows(AerospikeException.class, () -> mapper.insert(dataClass, "a", "c", "e").block()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/virtuallist/ResultsUnpacker.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.virtuallist; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.function.Function; 6 | 7 | public interface ResultsUnpacker { 8 | Object unpack(Object object); 9 | 10 | class ListUnpacker implements ResultsUnpacker { 11 | private ListUnpacker() { 12 | } 13 | 14 | @Override 15 | public Object unpack(Object object) { 16 | if (object == null) { 17 | return null; 18 | } else { 19 | List list = (List) object; 20 | return list.isEmpty() ? null : list.get(0); 21 | } 22 | } 23 | 24 | public final static ListUnpacker instance = new ListUnpacker(); 25 | } 26 | 27 | class IdentityUnpacker implements ResultsUnpacker { 28 | private IdentityUnpacker() { 29 | } 30 | 31 | @Override 32 | public Object unpack(Object object) { 33 | return object; 34 | } 35 | 36 | public final static IdentityUnpacker instance = new IdentityUnpacker(); 37 | } 38 | 39 | class ElementUnpacker implements ResultsUnpacker { 40 | Function function; 41 | 42 | public ElementUnpacker(Function itemMapper) { 43 | this.function = itemMapper; 44 | } 45 | 46 | @Override 47 | public Object unpack(Object object) { 48 | return function.apply(object); 49 | } 50 | } 51 | 52 | class ArrayUnpacker implements ResultsUnpacker { 53 | Function function; 54 | 55 | public ArrayUnpacker(Function itemMapper) { 56 | this.function = itemMapper; 57 | } 58 | 59 | @Override 60 | public Object unpack(Object object) { 61 | if (object == null) { 62 | return null; 63 | } 64 | 65 | List source = (List) object; 66 | List results = new ArrayList<>(source.size()); 67 | for (Object thisObject : source) { 68 | results.add(function.apply(thisObject)); 69 | } 70 | return results; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/Account.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeBin; 4 | import com.aerospike.mapper.annotations.AerospikeExclude; 5 | import com.aerospike.mapper.annotations.AerospikeGetter; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.annotations.AerospikeReference; 9 | import com.aerospike.mapper.annotations.AerospikeSetter; 10 | 11 | @AerospikeRecord(namespace = "test", set = "account", version = 2) 12 | public class Account { 13 | @AerospikeKey 14 | private long id; 15 | // Allow the "title" bin to be called something else by setting this environment variable. If not set, it will be "title" 16 | @AerospikeBin(name = "#{ACCOUNT_TITLE_BIN_NAME}") 17 | private String title; 18 | private int balance; 19 | 20 | @AerospikeReference 21 | private Product product; 22 | 23 | @AerospikeExclude 24 | private int unmapped; 25 | 26 | public long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | 42 | public int getBalance() { 43 | return balance; 44 | } 45 | 46 | public void setBalance(int balance) { 47 | this.balance = balance; 48 | } 49 | 50 | public int getUnmapped() { 51 | return unmapped; 52 | } 53 | 54 | public void setUnmapped(int unmapped) { 55 | this.unmapped = unmapped; 56 | } 57 | 58 | @AerospikeSetter(name = "bob") 59 | public void setCraziness(int value) { 60 | unmapped = value / 3; 61 | } 62 | 63 | @AerospikeGetter(name = "bob") 64 | public int getCraziness() { 65 | return unmapped * 3; 66 | } 67 | 68 | public Product getProduct() { 69 | return product; 70 | } 71 | 72 | public void setProduct(Product product) { 73 | this.product = product; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return String.format("id: %d, title: %s, balance: %d, unmapped: %d", id, title, balance, unmapped); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListOfObjectTransformTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import reactor.core.scheduler.Schedulers; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | public class ReactiveAeroMapperListOfObjectTransformTest extends ReactiveAeroMapperBaseTest { 16 | 17 | @AerospikeRecord(namespace = "test", set = "testSet", sendKey = true) 18 | public static class OwningClass { 19 | @AerospikeKey 20 | private int id; 21 | @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.LIST) 22 | private final List children; 23 | 24 | public OwningClass() { 25 | children = new ArrayList<>(); 26 | } 27 | } 28 | 29 | @AerospikeRecord() 30 | public static class OwnedClass { 31 | public String name; 32 | public int id; 33 | @AerospikeKey 34 | public Date date; 35 | 36 | public OwnedClass(String name, int id, Date date) { 37 | super(); 38 | this.name = name; 39 | this.id = id; 40 | this.date = date; 41 | } 42 | 43 | public OwnedClass() { 44 | } 45 | } 46 | 47 | private ReactiveAeroMapper reactiveMapper; 48 | 49 | @BeforeEach 50 | public void setup() { 51 | reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 52 | reactorClient.getAerospikeClient().truncate(null, "test", "testSet", null); 53 | } 54 | 55 | @Test 56 | public void test() { 57 | OwningClass owner = new OwningClass(); 58 | owner.id = 1; 59 | owner.children.add(new OwnedClass("a", 1, new Date())); 60 | owner.children.add(new OwnedClass("b", 2, new Date(new Date().getTime() - 5000))); 61 | owner.children.add(new OwnedClass("c", 3, new Date(new Date().getTime() - 10000))); 62 | 63 | reactiveMapper.save(owner).subscribeOn(Schedulers.parallel()).block(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/MapTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.aerospike.mapper.annotations.AerospikeKey; 11 | import com.aerospike.mapper.annotations.AerospikeRecord; 12 | import com.aerospike.mapper.tools.AeroMapper; 13 | 14 | public class MapTest extends AeroMapperBaseTest { 15 | @AerospikeRecord(namespace = "test", set = "A") 16 | public static class A { 17 | public String name; 18 | public int age; 19 | @AerospikeKey 20 | public int id; 21 | 22 | A() { 23 | } 24 | 25 | public A(String name, int age, int id) { 26 | super(); 27 | this.name = name; 28 | this.id = id; 29 | } 30 | } 31 | 32 | @AerospikeRecord(namespace = "test", set = "B") 33 | public static class B { 34 | @AerospikeKey 35 | public int id; 36 | public Map batchAs; 37 | } 38 | 39 | @Test 40 | public void runTest() { 41 | A a1 = new A("a", 10, 1); 42 | A a2 = new A("b", 20, 2); 43 | A a3 = new A("c", 30, 3); 44 | A a4 = new A("d", 40, 4); 45 | A a5 = new A("e", 50, 5); 46 | A a6 = new A("f", 60, 6); 47 | 48 | B b = new B(); 49 | b.batchAs = new HashMap<>(); 50 | b.id = 1; 51 | 52 | b.batchAs.put(10, a1); 53 | b.batchAs.put(11, a2); 54 | b.batchAs.put(12, a3); 55 | b.batchAs.put(13, a4); 56 | b.batchAs.put(14, a5); 57 | b.batchAs.put(15, a6); 58 | 59 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 60 | mapper.save(b); 61 | mapper.save(a1); 62 | mapper.save(a2); 63 | mapper.save(a3); 64 | mapper.save(a4); 65 | mapper.save(a5); 66 | mapper.save(a6); 67 | 68 | B b2 = mapper.read(B.class, 1); 69 | 70 | assertEquals(b.id, b2.id); 71 | assertEquals(b.batchAs.size(), b2.batchAs.size()); 72 | for (int i = 10; i <= 15; i++) { 73 | assertEquals(b.batchAs.get(i).age, b2.batchAs.get(i).age); 74 | assertEquals(b.batchAs.get(i).id, b2.batchAs.get(i).id); 75 | assertEquals(b.batchAs.get(i).name, b2.batchAs.get(i).name); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/virtuallist/Interactor.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.virtuallist; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | import com.aerospike.client.Operation; 6 | 7 | public class Interactor { 8 | private Operation operation; 9 | private DeferredOperation deferredOperation; 10 | private final OperationParameters deferredParameters; 11 | private ResultsUnpacker[] resultsUnpackers; 12 | 13 | public Interactor(@NotNull Operation operation, @NotNull ResultsUnpacker... resultsUnpackers) { 14 | super(); 15 | this.operation = operation; 16 | this.resultsUnpackers = resultsUnpackers; 17 | this.deferredParameters = null; 18 | } 19 | 20 | public Interactor(@NotNull DeferredOperation deferredOperation) { 21 | super(); 22 | this.deferredOperation = deferredOperation; 23 | this.deferredParameters = new OperationParameters(); 24 | } 25 | 26 | public void setNeedsResultOfType(ReturnType returnType) { 27 | if (this.deferredParameters != null) { 28 | this.deferredParameters.setNeedsResultOfType(returnType); 29 | } 30 | } 31 | 32 | public Operation getOperation() { 33 | if (operation == null && deferredOperation != null) { 34 | operation = deferredOperation.getOperation(this.deferredParameters); 35 | resultsUnpackers = deferredOperation.getUnpackers(this.deferredParameters); 36 | } 37 | return operation; 38 | } 39 | 40 | public Object getResult(Object rawResult) { 41 | Object result = rawResult; 42 | for (ResultsUnpacker thisUnpacker : resultsUnpackers) { 43 | result = thisUnpacker.unpack(result); 44 | } 45 | return result; 46 | } 47 | 48 | public boolean isWriteOperation() { 49 | if (this.operation != null) { 50 | switch (operation.type) { 51 | case ADD: 52 | case APPEND: 53 | case BIT_MODIFY: 54 | case CDT_MODIFY: 55 | case DELETE: 56 | case MAP_MODIFY: 57 | case PREPEND: 58 | case TOUCH: 59 | case WRITE: 60 | return true; 61 | default: 62 | return false; 63 | } 64 | } else { 65 | return !this.deferredOperation.isGetOperation(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class DeferredObjectLoader { 7 | public interface DeferredSetter { 8 | void setValue(Object object); 9 | } 10 | 11 | public static class DeferredObject { 12 | private final Object key; 13 | private final Class type; 14 | private final boolean isDigest; 15 | 16 | public DeferredObject(Object key, Class type, boolean isDigest) { 17 | super(); 18 | this.key = key; 19 | this.type = type; 20 | this.isDigest = isDigest; 21 | } 22 | 23 | public Object getKey() { 24 | return key; 25 | } 26 | 27 | public Class getType() { 28 | return type; 29 | } 30 | 31 | public boolean isDigest() { 32 | return isDigest; 33 | } 34 | } 35 | 36 | public static class DeferredObjectSetter { 37 | private final DeferredSetter setter; 38 | private final DeferredObject object; 39 | 40 | public DeferredObjectSetter(DeferredSetter setter, DeferredObject object) { 41 | super(); 42 | this.setter = setter; 43 | this.object = object; 44 | } 45 | 46 | public DeferredSetter getSetter() { 47 | return setter; 48 | } 49 | 50 | public DeferredObject getObject() { 51 | return object; 52 | } 53 | } 54 | 55 | 56 | private static final ThreadLocal> threadLocalLoader = ThreadLocal.withInitial(ArrayList::new); 57 | 58 | public static void save(DeferredObjectSetter object) { 59 | threadLocalLoader.get().add(object); 60 | } 61 | 62 | public static void clear() { 63 | threadLocalLoader.get().clear(); 64 | } 65 | 66 | public static List get() { 67 | return threadLocalLoader.get(); 68 | } 69 | 70 | public static void add(DeferredObjectSetter deferredSetter) { 71 | threadLocalLoader.get().add(deferredSetter); 72 | } 73 | 74 | public static List getAndClear() { 75 | List localArray = threadLocalLoader.get(); 76 | List setters = new ArrayList<>(localArray); 77 | localArray.clear(); 78 | return setters; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AeroMapperObjectTransformTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.aerospike.mapper.annotations.AerospikeBin; 9 | import com.aerospike.mapper.annotations.AerospikeEmbed; 10 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 11 | import com.aerospike.mapper.annotations.AerospikeKey; 12 | import com.aerospike.mapper.annotations.AerospikeRecord; 13 | import com.aerospike.mapper.tools.AeroMapper; 14 | 15 | public class AeroMapperObjectTransformTest extends AeroMapperBaseTest { 16 | @AerospikeRecord() 17 | public static class Transaction { 18 | public String name; 19 | public int value; 20 | @AerospikeKey 21 | public long time; 22 | 23 | public Transaction() { 24 | } 25 | 26 | public Transaction(String name, int value, long time) { 27 | super(); 28 | this.name = name; 29 | this.value = value; 30 | this.time = time; 31 | } 32 | } 33 | 34 | @AerospikeRecord(namespace = "test", set = "account") 35 | public static class Account { 36 | @AerospikeKey 37 | @AerospikeBin(name = "id") 38 | public int accountId; 39 | 40 | @AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST) 41 | public List txns; 42 | 43 | @AerospikeEmbed(type = EmbedType.LIST, elementType = EmbedType.LIST) 44 | public List txns2; 45 | 46 | public Account() { 47 | this.txns = new ArrayList<>(); 48 | this.txns2 = new ArrayList<>(); 49 | } 50 | } 51 | 52 | @Test 53 | public void saveTest() { 54 | Account account = new Account(); 55 | account.accountId = 1; 56 | account.txns.add(new Transaction("details1", 100, 101)); 57 | account.txns.add(new Transaction("details2", 200, 99)); 58 | account.txns.add(new Transaction("details3", 300, 1010)); 59 | 60 | account.txns2.add(new Transaction("details1", 100, 101)); 61 | account.txns2.add(new Transaction("details2", 200, 99)); 62 | account.txns2.add(new Transaction("details3", 300, 1010)); 63 | 64 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 65 | mapper.save(account); 66 | 67 | Account account2 = mapper.read(Account.class, 1); 68 | System.out.println(account2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools; 2 | 3 | import com.aerospike.client.policy.BatchPolicy; 4 | import com.aerospike.client.policy.Policy; 5 | import com.aerospike.client.policy.ScanPolicy; 6 | import com.aerospike.client.policy.WritePolicy; 7 | import com.aerospike.client.policy.QueryPolicy; 8 | import com.aerospike.mapper.tools.converters.MappingConverter; 9 | 10 | public interface IBaseAeroMapper { 11 | 12 | MappingConverter getMappingConverter(); 13 | 14 | IAeroMapper asMapper(); 15 | 16 | /** 17 | * Return the read policy to be used for the passed class. This is a convenience method only and should rarely be needed 18 | * 19 | * @param clazz - the class to return the read policy for. 20 | * @return - the appropriate read policy. If none is set, the client's readPolicyDefault is returned. 21 | */ 22 | Policy getReadPolicy(Class clazz); 23 | 24 | /** 25 | * Return the write policy to be used for the passed class. This is a convenience method only and should rarely be needed 26 | * 27 | * @param clazz - the class to return the write policy for. 28 | * @return - the appropriate write policy. If none is set, the client's writePolicyDefault is returned. 29 | */ 30 | WritePolicy getWritePolicy(Class clazz); 31 | 32 | /** 33 | * Return the batch policy to be used for the passed class. This is a convenience method only and should rarely be needed 34 | * 35 | * @param clazz - the class to return the batch policy for. 36 | * @return - the appropriate batch policy. If none is set, the client's batchPolicyDefault is returned. 37 | */ 38 | BatchPolicy getBatchPolicy(Class clazz); 39 | 40 | /** 41 | * Return the scan policy to be used for the passed class. This is a convenience method only and should rarely be needed 42 | * 43 | * @param clazz - the class to return the scan policy for. 44 | * @return - the appropriate scan policy. If none is set, the client's scanPolicyDefault is returned. 45 | */ 46 | ScanPolicy getScanPolicy(Class clazz); 47 | 48 | /** 49 | * Return the query policy to be used for the passed class. This is a convenience method only and should rarely be needed 50 | * 51 | * @param clazz - the class to return the query policy for. 52 | * @return - the appropriate query policy. If none is set, the client's queryPolicyDefault is returned. 53 | */ 54 | QueryPolicy getQueryPolicy(Class clazz); 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveRecursiveObjectTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.RecursiveObjectTest; 4 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertNotNull; 9 | 10 | public class ReactiveRecursiveObjectTest extends ReactiveAeroMapperBaseTest { 11 | 12 | @Test 13 | public void runTest() { 14 | RecursiveObjectTest.A a1 = new RecursiveObjectTest.A("a", 10, 1); 15 | a1.a = a1; 16 | 17 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 18 | reactiveMapper.save(a1).block(); 19 | 20 | RecursiveObjectTest.A a2 = reactiveMapper.read(RecursiveObjectTest.A.class, a1.id).block(); 21 | assertNotNull(a2); 22 | assertEquals(a1.age, a2.age); 23 | assertEquals(a1.name, a2.name); 24 | assertEquals(a1.id, a2.id); 25 | } 26 | 27 | @Test 28 | public void runMultipleObjectTest() { 29 | RecursiveObjectTest.A a1 = new RecursiveObjectTest.A("a", 10, 1); 30 | a1.a = a1; 31 | RecursiveObjectTest.B b = new RecursiveObjectTest.B(); 32 | b.id = 10; 33 | b.a = a1; 34 | 35 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 36 | reactiveMapper.save(a1).block(); 37 | reactiveMapper.save(b).block(); 38 | 39 | RecursiveObjectTest.B b2 = reactiveMapper.read(RecursiveObjectTest.B.class, b.id).block(); 40 | assertNotNull(b2); 41 | assertEquals(b.id, b2.id); 42 | assertEquals(b.a.age, b2.a.age); 43 | assertEquals(b.a.name, b2.a.name); 44 | assertEquals(b.a.id, b2.a.id); 45 | } 46 | 47 | @Test 48 | public void runTest2() { 49 | RecursiveObjectTest.A a1 = new RecursiveObjectTest.A("a", 10, 1); 50 | a1.a = new RecursiveObjectTest.A("a2", 11, 11); 51 | 52 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 53 | reactiveMapper.save(a1, a1.a).blockLast(); 54 | 55 | RecursiveObjectTest.A a2 = reactiveMapper.read(RecursiveObjectTest.A.class, a1.id).block(); 56 | assertNotNull(a2); 57 | assertEquals(a1.age, a2.age); 58 | assertEquals(a1.name, a2.name); 59 | assertEquals(a1.id, a2.id); 60 | assertEquals(a1.a.id, a2.a.id); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.aerospike.client.async.NioEventLoops; 6 | import com.aerospike.client.policy.ClientPolicy; 7 | 8 | import com.aerospike.client.AerospikeClient; 9 | import com.aerospike.client.Host; 10 | import com.aerospike.client.IAerospikeClient; 11 | import com.aerospike.mapper.tools.ClassCache; 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | import com.fasterxml.jackson.databind.ObjectWriter; 15 | import org.junit.jupiter.api.AfterAll; 16 | import org.junit.jupiter.api.BeforeAll; 17 | import org.junit.jupiter.api.BeforeEach; 18 | 19 | public abstract class AeroMapperBaseTest { 20 | 21 | public static final String NAMESPACE = "test"; 22 | protected static IAerospikeClient client; 23 | 24 | @BeforeAll 25 | public static void setupClass() { 26 | ClientPolicy policy = new ClientPolicy(); 27 | // Set event loops to use in asynchronous commands. 28 | policy.eventLoops = new NioEventLoops(1); 29 | Host[] hosts = Host.parseHosts(System.getProperty("test.host", "localhost:3000"), 3000); 30 | client = new AerospikeClient(policy, hosts); 31 | } 32 | 33 | @AfterAll 34 | public static void cleanupClass() { 35 | if (client != null) { 36 | client.close(); 37 | } 38 | } 39 | 40 | @BeforeEach 41 | public void clearCache() { 42 | ClassCache.getInstance().clear(); 43 | } 44 | 45 | public void compare(Object original, Object read) { 46 | this.compare(original, read, false); 47 | } 48 | 49 | public void compare(Object original, Object read, boolean showObjects) { 50 | try { 51 | ObjectWriter objectWriter = new ObjectMapper().writerWithDefaultPrettyPrinter(); 52 | String readString = objectWriter.writeValueAsString(read); 53 | if (showObjects) { 54 | System.out.println("------ Read Data -----\n" + readString); 55 | } 56 | String originalObject = objectWriter.writeValueAsString(original); 57 | if (showObjects) { 58 | System.out.println("------ Original Data -----\n" + originalObject); 59 | } 60 | assertEquals(originalObject, readString); 61 | } catch (JsonProcessingException jpe) { 62 | throw new RuntimeException(jpe); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/DateCustomConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.text.ParseException; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import com.aerospike.client.Key; 12 | import com.aerospike.client.Record; 13 | import com.aerospike.mapper.annotations.AerospikeKey; 14 | import com.aerospike.mapper.annotations.AerospikeRecord; 15 | import com.aerospike.mapper.annotations.FromAerospike; 16 | import com.aerospike.mapper.annotations.ToAerospike; 17 | import com.aerospike.mapper.tools.AeroMapper; 18 | 19 | public class DateCustomConverterTest extends AeroMapperBaseTest { 20 | 21 | public static class DateConverter { 22 | private static final ThreadLocal dateFormatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS zzzZ")); 23 | 24 | @ToAerospike 25 | public String toAerospike(Date date) { 26 | if (date == null) { 27 | return null; 28 | } 29 | return dateFormatter.get().format(date); 30 | } 31 | 32 | @FromAerospike 33 | public Date fromAerospike(String dateStr) throws ParseException { 34 | if (dateStr == null) { 35 | return null; 36 | } 37 | return dateFormatter.get().parse(dateStr); 38 | } 39 | } 40 | 41 | @AerospikeRecord(namespace = "test", set = "dateFormat") 42 | public static class DateContainer { 43 | @AerospikeKey 44 | public long key; 45 | public Date date; 46 | } 47 | 48 | @Test 49 | public void testSave() throws ParseException { 50 | AeroMapper convertingMapper = new AeroMapper.Builder(client).addConverter(new DateConverter()).build(); 51 | 52 | Date date = new Date(); 53 | 54 | DateContainer container = new DateContainer(); 55 | container.key = 1; 56 | container.date = date; 57 | 58 | convertingMapper.save(container); 59 | DateContainer container2 = convertingMapper.read(DateContainer.class, container.key); 60 | compare(container, container2, true); 61 | 62 | Record record = client.get(null, new Key("test", "dateFormat", 1)); 63 | String dateStr = record.getString("date"); 64 | Date date2 = DateConverter.dateFormatter.get().parse(dateStr); 65 | System.out.println("Expected: " + date + ", received " + date2); 66 | assertEquals(date, date2); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/PartialRecordsTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.aerospike.client.Key; 8 | import com.aerospike.client.Record; 9 | import com.aerospike.client.policy.WritePolicy; 10 | import com.aerospike.mapper.annotations.AerospikeKey; 11 | import com.aerospike.mapper.annotations.AerospikeRecord; 12 | import com.aerospike.mapper.tools.AeroMapper; 13 | 14 | public class PartialRecordsTest extends AeroMapperBaseTest { 15 | 16 | @AerospikeRecord(namespace = "test", set = "testSet") 17 | public static class DataClass { 18 | @AerospikeKey 19 | int a; 20 | int b; 21 | int c; 22 | int d; 23 | int e; 24 | } 25 | 26 | @Test 27 | public void testPartialSave() { 28 | WritePolicy writePolicy = new WritePolicy(client.getWritePolicyDefault()); 29 | writePolicy.totalTimeout = 2000; 30 | writePolicy.socketTimeout = 100; 31 | AeroMapper mapper = new AeroMapper.Builder(client) 32 | .withWritePolicy(writePolicy).forClasses(DataClass.class) 33 | .build(); 34 | 35 | DataClass dataClass = new DataClass(); 36 | dataClass.a = 1; 37 | dataClass.b = 2; 38 | dataClass.c = 3; 39 | dataClass.d = 4; 40 | dataClass.e = 5; 41 | 42 | // Do a full save 43 | mapper.save(dataClass); 44 | 45 | Key key = new Key("test", "testSet", 1); 46 | Record record = client.get(null, key); 47 | assertEquals(5, record.bins.size()); 48 | assertEquals(3, record.getInt("c")); 49 | 50 | // Perform a partial save, which should replace the record with just these bins 51 | dataClass.c = 9; 52 | dataClass.e = 11; 53 | mapper.save(dataClass, "a", "c", "e"); 54 | 55 | record = client.get(null, key); 56 | assertEquals(3, record.bins.size()); 57 | assertEquals(1, record.getInt("a")); 58 | assertEquals(9, record.getInt("c")); 59 | assertEquals(11, record.getInt("e")); 60 | 61 | // Now do an update of bin c and add bin d 62 | dataClass.c = 99; 63 | mapper.update(dataClass, "c", "d"); 64 | 65 | record = client.get(null, key); 66 | assertEquals(4, record.bins.size()); 67 | assertEquals(1, record.getInt("a")); 68 | assertEquals(99, record.getInt("c")); 69 | assertEquals(4, record.getInt("d")); 70 | assertEquals(11, record.getInt("e")); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/RecursiveObjectTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.tools.AeroMapper; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class RecursiveObjectTest extends AeroMapperBaseTest { 12 | @AerospikeRecord(namespace = "test", set = "A") 13 | public static class A { 14 | public String name; 15 | public int age; 16 | @AerospikeKey 17 | public int id; 18 | // @AerospikeReference(batchLoad = false) 19 | public A a; 20 | 21 | A() { 22 | } 23 | 24 | public A(String name, int age, int id) { 25 | super(); 26 | this.name = name; 27 | this.age = age; 28 | this.id = id; 29 | } 30 | } 31 | 32 | @AerospikeRecord(namespace = "test", set = "B") 33 | public static class B { 34 | @AerospikeKey 35 | public int id; 36 | public A a; 37 | } 38 | 39 | @Test 40 | public void runTest() { 41 | A a1 = new A("a", 10, 1); 42 | a1.a = a1; 43 | 44 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 45 | mapper.save(a1); 46 | 47 | A a2 = mapper.read(A.class, a1.id); 48 | assertEquals(a1.age, a2.age); 49 | assertEquals(a1.name, a2.name); 50 | assertEquals(a1.id, a2.id); 51 | } 52 | 53 | @Test 54 | public void runMultipleObjectTest() { 55 | A a1 = new A("a", 10, 1); 56 | a1.a = a1; 57 | B b = new B(); 58 | b.id = 10; 59 | b.a = a1; 60 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 61 | mapper.save(a1); 62 | mapper.save(b); 63 | 64 | B b2 = mapper.read(B.class, b.id); 65 | assertEquals(b.id, b2.id); 66 | assertEquals(b.a.age, b2.a.age); 67 | assertEquals(b.a.name, b2.a.name); 68 | assertEquals(b.a.id, b2.a.id); 69 | } 70 | 71 | @Test 72 | public void runTest2() { 73 | A a1 = new A("a", 10, 1); 74 | a1.a = new A("a2", 11, 11); 75 | 76 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 77 | mapper.save(a1, a1.a); 78 | 79 | A a2 = mapper.read(A.class, a1.id); 80 | assertEquals(a1.age, a2.age); 81 | assertEquals(a1.name, a2.name); 82 | assertEquals(a1.id, a2.id); 83 | assertEquals(a1.a.id, a2.a.id); 84 | } 85 | } -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveMultipleParameterSetterTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.Key; 4 | import com.aerospike.client.Value; 5 | import com.aerospike.mapper.annotations.*; 6 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | public class ReactiveMultipleParameterSetterTest extends ReactiveAeroMapperBaseTest { 13 | 14 | @AerospikeRecord(namespace = "test", set = "A", mapAll = false) 15 | public static class A { 16 | private String key; 17 | private String value1; 18 | private long value2; 19 | 20 | @AerospikeKey 21 | public String getKey() { 22 | return key; 23 | } 24 | 25 | @AerospikeKey(setter = true) 26 | public void setKey(String key) { 27 | this.key = key; 28 | } 29 | 30 | @AerospikeGetter(name = "v1") 31 | public String getValue1() { 32 | return value1; 33 | } 34 | 35 | @AerospikeSetter(name = "v1") 36 | public void setValue1(String value1, Value owningKey) { 37 | assertEquals("B-1", owningKey.getObject()); 38 | this.value1 = value1; 39 | } 40 | 41 | @AerospikeGetter(name = "v2") 42 | public long getValue2() { 43 | return value2; 44 | } 45 | 46 | @AerospikeSetter(name = "v2") 47 | public void setValue2(long value2, Key key) { 48 | assertEquals("test", key.namespace); 49 | assertEquals("B", key.setName); 50 | assertEquals("B-1", key.userKey.getObject()); 51 | this.value2 = value2; 52 | } 53 | } 54 | 55 | @AerospikeRecord(namespace = "test", set = "B") 56 | public static class B { 57 | @AerospikeKey 58 | private String key; 59 | @AerospikeEmbed 60 | private A a; 61 | } 62 | 63 | @Test 64 | public void test() { 65 | A a = new A(); 66 | a.key = "A-1"; 67 | a.value1 = "value1"; 68 | a.value2 = 1000; 69 | 70 | B b = new B(); 71 | b.key = "B-1"; 72 | b.a = a; 73 | 74 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 75 | reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); 76 | B b2 = reactiveMapper.read(B.class, b.key).subscribeOn(Schedulers.parallel()).block(); 77 | 78 | compare(b, b2); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/MultipleParameterSetterTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.aerospike.client.Key; 8 | import com.aerospike.client.Value; 9 | import com.aerospike.mapper.annotations.AerospikeEmbed; 10 | import com.aerospike.mapper.annotations.AerospikeGetter; 11 | import com.aerospike.mapper.annotations.AerospikeKey; 12 | import com.aerospike.mapper.annotations.AerospikeRecord; 13 | import com.aerospike.mapper.annotations.AerospikeSetter; 14 | import com.aerospike.mapper.tools.AeroMapper; 15 | 16 | public class MultipleParameterSetterTest extends AeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "A", mapAll = false) 19 | public static class A { 20 | private String key; 21 | private String value1; 22 | private long value2; 23 | 24 | @AerospikeKey 25 | public String getKey() { 26 | return key; 27 | } 28 | 29 | @AerospikeKey(setter = true) 30 | public void setKey(String key) { 31 | this.key = key; 32 | } 33 | 34 | @AerospikeGetter(name = "v1") 35 | public String getValue1() { 36 | return value1; 37 | } 38 | 39 | @AerospikeSetter(name = "v1") 40 | public void setValue1(String value1, Value owningKey) { 41 | assertEquals("B-1", owningKey.getObject()); 42 | this.value1 = value1; 43 | } 44 | 45 | @AerospikeGetter(name = "v2") 46 | public long getValue2() { 47 | return value2; 48 | } 49 | 50 | @AerospikeSetter(name = "v2") 51 | public void setValue2(long value2, Key key) { 52 | assertEquals("test", key.namespace); 53 | assertEquals("B", key.setName); 54 | assertEquals("B-1", key.userKey.getObject()); 55 | this.value2 = value2; 56 | } 57 | } 58 | 59 | @AerospikeRecord(namespace = "test", set = "B") 60 | public static class B { 61 | @AerospikeKey 62 | private String key; 63 | @AerospikeEmbed 64 | private A a; 65 | } 66 | 67 | @Test 68 | public void test() { 69 | A a = new A(); 70 | a.key = "A-1"; 71 | a.value1 = "value1"; 72 | a.value2 = 1000; 73 | 74 | B b = new B(); 75 | b.key = "B-1"; 76 | b.a = a; 77 | 78 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 79 | mapper.save(b); 80 | B b2 = mapper.read(B.class, b.key); 81 | 82 | compare(b, b2); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperObjectTransformTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeBin; 4 | import com.aerospike.mapper.annotations.AerospikeEmbed; 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 8 | import org.junit.jupiter.api.Test; 9 | import reactor.core.scheduler.Schedulers; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class ReactiveAeroMapperObjectTransformTest extends ReactiveAeroMapperBaseTest { 15 | 16 | @AerospikeRecord() 17 | public static class Transaction { 18 | public String name; 19 | public int value; 20 | @AerospikeKey 21 | public long time; 22 | 23 | public Transaction() { 24 | } 25 | 26 | public Transaction(String name, int value, long time) { 27 | super(); 28 | this.name = name; 29 | this.value = value; 30 | this.time = time; 31 | } 32 | } 33 | 34 | @AerospikeRecord(namespace = "test", set = "account") 35 | public static class Account { 36 | @AerospikeKey 37 | @AerospikeBin(name = "id") 38 | public int accountId; 39 | 40 | @AerospikeEmbed(type = AerospikeEmbed.EmbedType.MAP, elementType = AerospikeEmbed.EmbedType.LIST) 41 | public List txns; 42 | 43 | @AerospikeEmbed(type = AerospikeEmbed.EmbedType.LIST, elementType = AerospikeEmbed.EmbedType.LIST) 44 | public List txns2; 45 | 46 | public Account() { 47 | this.txns = new ArrayList<>(); 48 | this.txns2 = new ArrayList<>(); 49 | } 50 | } 51 | 52 | @Test 53 | public void saveTest() { 54 | Account account = new Account(); 55 | account.accountId = 1; 56 | account.txns.add(new Transaction("details1", 100, 101)); 57 | account.txns.add(new Transaction("details2", 200, 99)); 58 | account.txns.add(new Transaction("details3", 300, 1010)); 59 | 60 | account.txns2.add(new Transaction("details1", 100, 101)); 61 | account.txns2.add(new Transaction("details2", 200, 99)); 62 | account.txns2.add(new Transaction("details3", 300, 1010)); 63 | 64 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 65 | reactiveMapper.save(account).subscribeOn(Schedulers.parallel()).block(); 66 | 67 | Account account2 = reactiveMapper.read(Account.class, 1).subscribeOn(Schedulers.parallel()).block(); 68 | System.out.println(account2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/ArrayMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import java.lang.reflect.Array; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import com.aerospike.mapper.tools.DeferredObjectLoader; 8 | import com.aerospike.mapper.tools.TypeMapper; 9 | import com.aerospike.mapper.tools.utils.TypeUtils; 10 | import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObject; 11 | import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObjectSetter; 12 | import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredSetter; 13 | 14 | public class ArrayMapper extends TypeMapper { 15 | 16 | private final Class instanceClass; 17 | private final boolean supportedWithoutTranslation; 18 | private final TypeMapper instanceClassMapper; 19 | private final Boolean allowBatch; 20 | 21 | public ArrayMapper(final Class instanceClass, final TypeMapper instanceClassMapper, final boolean allowBatch) { 22 | this.instanceClass = instanceClass; 23 | this.supportedWithoutTranslation = TypeUtils.isByteType(instanceClass); 24 | this.instanceClassMapper = instanceClassMapper; 25 | this.allowBatch = allowBatch; 26 | } 27 | 28 | @Override 29 | public Object toAerospikeFormat(Object value) { 30 | if (value == null) { 31 | return null; 32 | } 33 | int length = Array.getLength(value); 34 | if (this.supportedWithoutTranslation) { 35 | return value; 36 | } 37 | 38 | List results = new ArrayList<>(); 39 | for (int i = 0; i < length; i++) { 40 | results.add(this.instanceClassMapper.toAerospikeFormat(Array.get(value, i))); 41 | } 42 | return results; 43 | } 44 | 45 | @Override 46 | public Object fromAerospikeFormat(Object value) { 47 | if (value == null) { 48 | return null; 49 | } 50 | List list = (List) value; 51 | if (this.supportedWithoutTranslation) { 52 | return value; 53 | } 54 | 55 | Object result = Array.newInstance(instanceClass, list.size()); 56 | for (int i = 0; i < list.size(); i++) { 57 | 58 | Object obj = list.get(i); 59 | Object item = this.instanceClassMapper.fromAerospikeFormat(obj); 60 | if (!allowBatch || (!(item instanceof DeferredObject))) { 61 | Array.set(result, i, item); 62 | } else { 63 | final int thisIndex = i; 64 | DeferredSetter setter = object -> Array.set(result, thisIndex, object); 65 | DeferredObjectLoader.add(new DeferredObjectSetter(setter, (DeferredObject) item)); 66 | } 67 | } 68 | return result; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/ListOfReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.annotations.AerospikeReference; 6 | import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType; 7 | import com.aerospike.mapper.tools.AeroMapper; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ListOfReferencesTest extends AeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "item") 19 | public static class Item { 20 | @AerospikeKey 21 | private int id; 22 | private Date due; 23 | private String desc; 24 | 25 | public Item(int id, Date due, String desc) { 26 | super(); 27 | this.id = id; 28 | this.due = due; 29 | this.desc = desc; 30 | } 31 | 32 | public Item() { 33 | } 34 | } 35 | 36 | @AerospikeRecord(namespace = "test", set = "container") 37 | public static class Container { 38 | @AerospikeKey 39 | private int id; 40 | private String name; 41 | @AerospikeReference(type = ReferenceType.ID) 42 | private final List items; 43 | 44 | public Container() { 45 | this.items = new ArrayList<>(); 46 | } 47 | } 48 | 49 | @Test 50 | public void testListOfReferences() { 51 | Container container = new Container(); 52 | container.id = 1; 53 | container.name = "container"; 54 | 55 | container.items.add(new Item(100, new Date(), "Item 1")); 56 | container.items.add(new Item(200, new Date(), "Item 2")); 57 | container.items.add(new Item(300, new Date(), "Item 3")); 58 | container.items.add(new Item(400, new Date(), "Item 4")); 59 | 60 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 61 | mapper.save(container); 62 | for (int i = 0; i < container.items.size(); i++) { 63 | mapper.save(container.items.get(i)); 64 | } 65 | 66 | Container newVersion = mapper.read(Container.class, 1); 67 | assertEquals(container.id, newVersion.id); 68 | assertEquals(container.name, newVersion.name); 69 | assertEquals(container.items.size(), newVersion.items.size()); 70 | for (int i = 0; i < container.items.size(); i++) { 71 | assertEquals(container.items.get(i).desc, newVersion.items.get(i).desc); 72 | assertEquals(container.items.get(i).id, newVersion.items.get(i).id); 73 | assertEquals(container.items.get(i).due, newVersion.items.get(i).due); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/EmbeddedClassTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.AeroMapper; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | 19 | public class EmbeddedClassTest extends AeroMapperBaseTest { 20 | 21 | @Test 22 | void testEmbed() { 23 | Embed2 embed2 = new Embed2(Collections.singletonList(new Embed3("s3", "s4"))); 24 | Embed1 record = new Embed1(Collections.singletonList(embed2), new Embed3("s1", "s2"), "id"); 25 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 26 | 27 | mapper.save(record); 28 | Embed1 read = mapper.read(Embed1.class, record.getId()); 29 | assertEquals(record, read); 30 | } 31 | 32 | @Test 33 | void testDerived() { 34 | Derived derived = new Derived("str1", 1); 35 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 36 | 37 | mapper.save(derived); 38 | Derived read = mapper.read(Derived.class, derived.getStr()); 39 | assertEquals(derived, read); 40 | } 41 | 42 | @Data 43 | @NoArgsConstructor 44 | @AllArgsConstructor 45 | @AerospikeRecord(namespace = "test", set = "embed") 46 | private static class Embed1 { 47 | @AerospikeEmbed 48 | public List bList; 49 | @AerospikeEmbed 50 | private Embed3 embed3; 51 | @AerospikeKey 52 | private String id; 53 | } 54 | 55 | @Data 56 | @NoArgsConstructor 57 | @AllArgsConstructor 58 | @AerospikeRecord // test backward 59 | private static class Embed2 { 60 | @AerospikeEmbed 61 | public List cList; 62 | } 63 | 64 | @Data 65 | @NoArgsConstructor 66 | @AllArgsConstructor 67 | private static class Embed3 { 68 | public String s1; 69 | public String s2; 70 | } 71 | 72 | @EqualsAndHashCode(callSuper = true) 73 | @Getter 74 | @NoArgsConstructor 75 | @AerospikeRecord(namespace = "test", set = "embed") 76 | private static class Derived extends Base { 77 | @AerospikeKey 78 | private String str; 79 | 80 | Derived(String str, int i1) { 81 | super(i1); 82 | this.str = str; 83 | } 84 | } 85 | 86 | @EqualsAndHashCode 87 | @NoArgsConstructor 88 | @AllArgsConstructor 89 | private static class Base { 90 | private int i1; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveDateCustomConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.Key; 4 | import com.aerospike.client.query.KeyRecord; 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.FromAerospike; 8 | import com.aerospike.mapper.annotations.ToAerospike; 9 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 10 | import org.junit.jupiter.api.Test; 11 | import reactor.core.scheduler.Schedulers; 12 | 13 | import java.text.ParseException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.Date; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | 19 | public class ReactiveDateCustomConverterTest extends ReactiveAeroMapperBaseTest { 20 | public static class DateConverter { 21 | private static final ThreadLocal dateFormatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS zzzZ")); 22 | 23 | @ToAerospike 24 | public String toAerospike(Date date) { 25 | if (date == null) { 26 | return null; 27 | } 28 | return dateFormatter.get().format(date); 29 | } 30 | 31 | @FromAerospike 32 | public Date fromAerospike(String dateStr) throws ParseException { 33 | if (dateStr == null) { 34 | return null; 35 | } 36 | return dateFormatter.get().parse(dateStr); 37 | } 38 | } 39 | 40 | @AerospikeRecord(namespace = "test", set = "dateFormat") 41 | public static class DateContainer { 42 | @AerospikeKey 43 | public long key; 44 | public Date date; 45 | } 46 | 47 | @Test 48 | public void testSave() throws ParseException { 49 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).addConverter(new DateConverter()).build(); 50 | 51 | Date date = new Date(); 52 | 53 | DateContainer container = new DateContainer(); 54 | container.key = 1; 55 | container.date = date; 56 | 57 | reactiveMapper.save(container).subscribeOn(Schedulers.parallel()).block(); 58 | DateContainer container2 = reactiveMapper.read(DateContainer.class, container.key).subscribeOn(Schedulers.parallel()).block(); 59 | compare(container, container2, true); 60 | 61 | KeyRecord keyRecord = reactorClient.get(null, new Key("test", "dateFormat", 1)).subscribeOn(Schedulers.parallel()).block(); 62 | assert keyRecord != null; 63 | String dateStr = keyRecord.record.getString("date"); 64 | Date date2 = DateConverter.dateFormatter.get().parse(dateStr); 65 | System.out.println("Expected: " + date + ", received " + date2); 66 | assertEquals(date, date2); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.exp.Exp; 4 | import com.aerospike.client.policy.ScanPolicy; 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.ParamFrom; 8 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 9 | import org.junit.jupiter.api.Test; 10 | import reactor.core.scheduler.Schedulers; 11 | 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ReactiveScanTest extends ReactiveAeroMapperBaseTest { 17 | @AerospikeRecord(namespace = "test", set = "testScan") 18 | public static class Person { 19 | @AerospikeKey 20 | private final int id; 21 | private final String name; 22 | private final int age; 23 | 24 | public Person(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFrom("age") int age) { 25 | super(); 26 | this.id = id; 27 | this.name = name; 28 | this.age = age; 29 | } 30 | 31 | public int getId() { 32 | return id; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public int getAge() { 40 | return age; 41 | } 42 | } 43 | 44 | private ReactiveAeroMapper populate() { 45 | reactorClient.getAerospikeClient().truncate(null, "test", "testScan", null); 46 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 47 | reactiveMapper.save(new Person(1, "Tim", 312), 48 | new Person(2, "Bob", 44), 49 | new Person(3, "Sue", 56), 50 | new Person(4, "Rob", 23), 51 | new Person(5, "Jim", 32), 52 | new Person(6, "Bob", 78)).subscribeOn(Schedulers.parallel()).collectList().block(); 53 | return reactiveMapper; 54 | } 55 | 56 | @Test 57 | public void scanTest() { 58 | ReactiveAeroMapper reactiveMapper = populate(); 59 | List results = reactiveMapper.scan(Person.class).subscribeOn(Schedulers.parallel()).collectList().block(); 60 | assert results != null; 61 | assertEquals(6, results.size()); 62 | } 63 | 64 | @Test 65 | public void scanTestWithFilter() { 66 | ReactiveAeroMapper reactiveMapper = populate(); 67 | ScanPolicy scanPolicy = new ScanPolicy(reactiveMapper.getScanPolicy(Person.class)); 68 | scanPolicy.filterExp = Exp.build(Exp.eq(Exp.stringBin("name"), Exp.val("Bob"))); 69 | List results = reactiveMapper.scan(scanPolicy, Person.class).subscribeOn(Schedulers.parallel()).collectList().block(); 70 | assert results != null; 71 | assertEquals(2, results.size()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.mapper.tools.TypeMapper; 5 | 6 | import java.lang.reflect.Field; 7 | 8 | public class EnumMapper extends TypeMapper { 9 | 10 | private final Class> clazz; 11 | private final String enumField; 12 | private final Field enumRequestedField; 13 | 14 | public EnumMapper(Class> clazz, String enumField) { 15 | this.clazz = clazz; 16 | this.enumField = enumField; 17 | if (!enumField.isEmpty()) { 18 | try { 19 | this.enumRequestedField = clazz.getDeclaredField(enumField); 20 | this.enumRequestedField.setAccessible(true); 21 | } catch (NoSuchFieldException e) { 22 | throw toAerospikeException(e); 23 | } 24 | } else { 25 | this.enumRequestedField = null; 26 | } 27 | } 28 | 29 | @Override 30 | public Object toAerospikeFormat(Object value) { 31 | if (value == null) { 32 | return null; 33 | } 34 | 35 | if (!enumField.isEmpty()) { 36 | if (enumRequestedField == null) { 37 | return null; 38 | } 39 | try { 40 | Object enumValue = enumRequestedField.get(value); 41 | return enumValue == null ? null : enumValue.toString(); 42 | } catch (IllegalAccessException e) { 43 | throw toAerospikeException(e); 44 | } 45 | } 46 | return value.toString(); 47 | } 48 | 49 | @Override 50 | public Object fromAerospikeFormat(Object value) { 51 | if (value == null) { 52 | return null; 53 | } 54 | 55 | String stringValue = (String) value; 56 | Enum[] constants = clazz.getEnumConstants(); 57 | 58 | if (!enumField.isEmpty()) { 59 | try { 60 | for (Enum thisEnum : constants) { 61 | if (enumRequestedField.get(thisEnum).equals(stringValue)) { 62 | return thisEnum; 63 | } 64 | } 65 | } catch (IllegalAccessException e) { 66 | throw toAerospikeException(e); 67 | } 68 | } else { 69 | for (Enum thisEnum : constants) { 70 | if (thisEnum.toString().equals(stringValue)) { 71 | return thisEnum; 72 | } 73 | } 74 | } 75 | throw new AerospikeException(String.format("Enum value of \"%s\" not found in type %s", stringValue, clazz)); 76 | } 77 | 78 | private AerospikeException toAerospikeException(Exception e) { 79 | return new AerospikeException("Cannot Map requested enum, issue with the requested enumField.", e); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AnonymousReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.tools.AeroMapper; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | 13 | public class AnonymousReferencesTest extends AeroMapperBaseTest { 14 | 15 | @Test 16 | @SuppressWarnings({"unchecked", "rawtypes"}) 17 | public void runner() { 18 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 19 | B b = new B(); 20 | b.id = 2; 21 | b.name = "a B"; 22 | mapper.save(b); 23 | 24 | B b1 = new B(); 25 | b1.id = 3; 26 | b1.name = "another B"; 27 | mapper.save(b1); 28 | 29 | A a = new A(); 30 | a.id = 1; 31 | a.namedB.add(b); 32 | a.namedB.add(b1); 33 | 34 | a.unnamedB.add(b); 35 | a.unnamedB.add(b1); 36 | 37 | List nonB = new ArrayList<>(); 38 | nonB.add(2L); 39 | nonB.add("B"); 40 | a.nonB.add(nonB); 41 | 42 | mapper.save(a); 43 | A a2 = mapper.read(A.class, a.id); 44 | assertEquals(a.id, a2.id); 45 | assertEquals(a.unnamedB.size(), a2.unnamedB.size()); 46 | assertEquals(((B) a.unnamedB.get(0)).id, ((B) a2.unnamedB.get(0)).id); 47 | assertEquals(((B) a.unnamedB.get(0)).name, ((B) a2.unnamedB.get(0)).name); 48 | assertEquals(((B) a.unnamedB.get(1)).id, ((B) a2.unnamedB.get(1)).id); 49 | assertEquals(((B) a.unnamedB.get(1)).name, ((B) a2.unnamedB.get(1)).name); 50 | 51 | assertEquals(a.namedB.size(), a2.namedB.size()); 52 | assertEquals(a.namedB.get(0).id, a2.namedB.get(0).id); 53 | assertEquals(a.namedB.get(0).name, a2.namedB.get(0).name); 54 | assertEquals(a.namedB.get(1).id, a2.namedB.get(1).id); 55 | assertEquals(a.namedB.get(1).name, a2.namedB.get(1).name); 56 | 57 | assertEquals(a.nonB.size(), a2.nonB.size()); 58 | assertEquals(((List) a.nonB.get(0)).get(0), ((List) a2.nonB.get(0)).get(0)); 59 | assertEquals(((List) a.nonB.get(0)).get(1), ((List) a2.nonB.get(0)).get(1)); 60 | } 61 | 62 | @AerospikeRecord(namespace = "test", set = "A") 63 | public static class A { 64 | @AerospikeKey 65 | public int id; 66 | public List namedB; 67 | public List unnamedB; 68 | public List nonB; 69 | 70 | public A() { 71 | namedB = new ArrayList<>(); 72 | unnamedB = new ArrayList<>(); 73 | nonB = new ArrayList<>(); 74 | } 75 | } 76 | 77 | @AerospikeRecord(namespace = "test", set = "B") 78 | public static class B { 79 | @AerospikeKey 80 | public int id; 81 | public String name; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveMapTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 6 | import org.junit.jupiter.api.Test; 7 | import reactor.core.scheduler.Schedulers; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | public class ReactiveMapTest extends ReactiveAeroMapperBaseTest { 15 | @AerospikeRecord(namespace = "test", set = "A") 16 | public static class A { 17 | public String name; 18 | public int age; 19 | @AerospikeKey 20 | public int id; 21 | 22 | A() { 23 | } 24 | 25 | public A(String name, int age, int id) { 26 | super(); 27 | this.name = name; 28 | this.id = id; 29 | } 30 | } 31 | 32 | @AerospikeRecord(namespace = "test", set = "B") 33 | public static class B { 34 | @AerospikeKey 35 | public int id; 36 | public Map batchAs; 37 | } 38 | 39 | @Test 40 | public void runTest() { 41 | A a1 = new A("a", 10, 1); 42 | A a2 = new A("b", 20, 2); 43 | A a3 = new A("c", 30, 3); 44 | A a4 = new A("d", 40, 4); 45 | A a5 = new A("e", 50, 5); 46 | A a6 = new A("f", 60, 6); 47 | 48 | B b = new B(); 49 | b.batchAs = new HashMap<>(); 50 | b.id = 1; 51 | 52 | b.batchAs.put(10, a1); 53 | b.batchAs.put(11, a2); 54 | b.batchAs.put(12, a3); 55 | b.batchAs.put(13, a4); 56 | b.batchAs.put(14, a5); 57 | b.batchAs.put(15, a6); 58 | 59 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 60 | reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); 61 | reactiveMapper.save(a1).subscribeOn(Schedulers.parallel()).block(); 62 | reactiveMapper.save(a2).subscribeOn(Schedulers.parallel()).block(); 63 | reactiveMapper.save(a3).subscribeOn(Schedulers.parallel()).block(); 64 | reactiveMapper.save(a4).subscribeOn(Schedulers.parallel()).block(); 65 | reactiveMapper.save(a5).subscribeOn(Schedulers.parallel()).block(); 66 | reactiveMapper.save(a6).subscribeOn(Schedulers.parallel()).block(); 67 | 68 | B b2 = reactiveMapper.read(B.class, 1).subscribeOn(Schedulers.parallel()).block(); 69 | 70 | assert b2 != null; 71 | assertEquals(b.id, b2.id); 72 | assertEquals(b.batchAs.size(), b2.batchAs.size()); 73 | for (int i = 10; i <= 15; i++) { 74 | assertEquals(b.batchAs.get(i).age, b2.batchAs.get(i).age); 75 | assertEquals(b.batchAs.get(i).id, b2.batchAs.get(i).id); 76 | assertEquals(b.batchAs.get(i).name, b2.batchAs.get(i).name); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.Instant; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import com.aerospike.mapper.annotations.AerospikeKey; 10 | import com.aerospike.mapper.annotations.AerospikeRecord; 11 | import com.aerospike.mapper.tools.AeroMapper; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | 15 | public class AeroMapperComplexClassTest extends AeroMapperBaseTest { 16 | 17 | @AerospikeRecord(namespace = "test", set = "testSet", sendKey = true) 18 | public static class ComplexClass { 19 | private long trid; 20 | private String cnid; 21 | private double amt; 22 | @AerospikeKey 23 | private Instant trts; 24 | private String res; 25 | private String meid; 26 | private List byte4Fields; 27 | private List byte2Fields; 28 | private List byte1Fields; 29 | private List charFields; 30 | private List strFields; 31 | private boolean testData = false; 32 | 33 | public ComplexClass() { 34 | } 35 | } 36 | 37 | private AeroMapper mapper; 38 | 39 | @BeforeEach 40 | public void setup() { 41 | mapper = new AeroMapper.Builder(client).build(); 42 | client.truncate(null, "test", "testSet", null); 43 | } 44 | 45 | @Test 46 | public void test() { 47 | 48 | ComplexClass complex = new ComplexClass(); 49 | complex.amt = 100.1; 50 | complex.trts = Instant.now(); 51 | complex.trid = 18; 52 | complex.cnid = "CN19"; 53 | complex.res = "result"; 54 | complex.meid = "ME11"; 55 | complex.byte4Fields = Arrays.asList(1, 2, 3, 4, 5); 56 | complex.byte2Fields = Arrays.asList(new Short[]{1, 2, 3, 4, 5, 6, 7}); 57 | complex.byte1Fields = Arrays.asList(new Byte[]{1, 2, 3, -1, -2, 127, -128, 0}); 58 | complex.charFields = Arrays.asList(new Character[]{9, 8, 7, 6, 0, 255, 245, 128, 127}); 59 | mapper.save(complex); 60 | 61 | ComplexClass complex2 = mapper.read(ComplexClass.class, complex.trts); 62 | for (int i = 0; i < complex.charFields.size(); i++) { 63 | assertEquals(complex.charFields.get(i), complex2.charFields.get(i)); 64 | } 65 | for (int i = 0; i < complex.byte1Fields.size(); i++) { 66 | assertEquals(complex.byte1Fields.get(i), complex2.byte1Fields.get(i)); 67 | } 68 | for (int i = 0; i < complex.byte2Fields.size(); i++) { 69 | assertEquals(complex.byte2Fields.get(i), complex2.byte2Fields.get(i)); 70 | } 71 | for (int i = 0; i < complex.byte4Fields.size(); i++) { 72 | assertEquals(complex.byte4Fields.get(i), complex2.byte4Fields.get(i)); 73 | } 74 | assertEquals(complex.trts, complex2.trts); 75 | System.out.println("Complex2 loaded"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/VirtualListExample.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples; 2 | 3 | import com.aerospike.mapper.AeroMapperBaseTest; 4 | import com.aerospike.mapper.annotations.AerospikeEmbed; 5 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.tools.AeroMapper; 9 | import com.aerospike.mapper.tools.virtuallist.ReturnType; 10 | import com.aerospike.mapper.tools.virtuallist.VirtualList; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | public class VirtualListExample extends AeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "item") 19 | public static class Item { 20 | @AerospikeKey 21 | private int id; 22 | private Date due; 23 | private String desc; 24 | 25 | public Item(int id, Date due, String desc) { 26 | super(); 27 | this.id = id; 28 | this.due = due; 29 | this.desc = desc; 30 | } 31 | 32 | public Item() { 33 | } 34 | } 35 | 36 | @AerospikeRecord(namespace = "test", set = "container") 37 | public static class Container { 38 | @AerospikeKey 39 | private int id; 40 | private String name; 41 | @AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST) 42 | private final List items; 43 | 44 | public Container() { 45 | this.items = new ArrayList<>(); 46 | } 47 | } 48 | 49 | public void testListOfReferences() { 50 | Container container = new Container(); 51 | container.id = 1; 52 | container.name = "container"; 53 | 54 | container.items.add(new Item(100, new Date(), "Item 1")); 55 | container.items.add(new Item(200, new Date(), "Item 2")); 56 | container.items.add(new Item(300, new Date(), "Item 3")); 57 | container.items.add(new Item(400, new Date(), "Item 4")); 58 | 59 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 60 | mapper.save(container); 61 | 62 | VirtualList list = mapper.asBackedList(container, "items", Item.class); 63 | // perform a single operation. NOTE: This does NOT change the backed item, just the database 64 | list.append(new Item(500, new Date(), "Item5")); 65 | 66 | /* 67 | List results = (List) list.beginMultiOperation() 68 | .append(new Item(600, new Date(), "Item6")) 69 | .removeByKey(200) 70 | .getByKeyRange(100, 450) 71 | .end(); 72 | 73 | System.out.println(results.size()); 74 | */ 75 | long count = (long) list.beginMultiOperation() 76 | .append(new Item(600, new Date(), "Item6")) 77 | .removeByKey(200) 78 | .removeByKeyRange(20, 350).asResultOfType(ReturnType.COUNT) 79 | .getByKeyRange(100, 450) 80 | .end(); 81 | 82 | System.out.println(count); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveListOfReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.annotations.AerospikeReference; 6 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ReactiveListOfReferencesTest extends ReactiveAeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "item") 19 | public static class Item { 20 | @AerospikeKey 21 | private int id; 22 | private Date due; 23 | private String desc; 24 | 25 | public Item(int id, Date due, String desc) { 26 | super(); 27 | this.id = id; 28 | this.due = due; 29 | this.desc = desc; 30 | } 31 | 32 | public Item() { 33 | } 34 | } 35 | 36 | @AerospikeRecord(namespace = "test", set = "container") 37 | public static class Container { 38 | @AerospikeKey 39 | private int id; 40 | private String name; 41 | @AerospikeReference(type = AerospikeReference.ReferenceType.ID) 42 | private final List items; 43 | 44 | public Container() { 45 | this.items = new ArrayList<>(); 46 | } 47 | } 48 | 49 | @Test 50 | public void testListOfReferences() { 51 | Container container = new Container(); 52 | container.id = 1; 53 | container.name = "container"; 54 | 55 | container.items.add(new Item(100, new Date(), "Item 1")); 56 | container.items.add(new Item(200, new Date(), "Item 2")); 57 | container.items.add(new Item(300, new Date(), "Item 3")); 58 | container.items.add(new Item(400, new Date(), "Item 4")); 59 | 60 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 61 | reactiveMapper.save(container).subscribeOn(Schedulers.parallel()).block(); 62 | for (int i = 0; i < container.items.size(); i++) { 63 | reactiveMapper.save(container.items.get(i)).subscribeOn(Schedulers.parallel()).block(); 64 | } 65 | 66 | Container newVersion = reactiveMapper.read(Container.class, 1).subscribeOn(Schedulers.parallel()).block(); 67 | assert newVersion != null; 68 | assertEquals(container.id, newVersion.id); 69 | assertEquals(container.name, newVersion.name); 70 | assertEquals(container.items.size(), newVersion.items.size()); 71 | for (int i = 0; i < container.items.size(); i++) { 72 | assertEquals(container.items.get(i).desc, newVersion.items.get(i).desc); 73 | assertEquals(container.items.get(i).id, newVersion.items.get(i).id); 74 | assertEquals(container.items.get(i).due, newVersion.items.get(i).due); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/TestCustomTtl.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.aerospike.client.Key; 8 | import com.aerospike.client.Record; 9 | import com.aerospike.client.policy.WritePolicy; 10 | import com.aerospike.mapper.annotations.AerospikeKey; 11 | import com.aerospike.mapper.annotations.AerospikeRecord; 12 | import com.aerospike.mapper.tools.AeroMapper; 13 | 14 | public class TestCustomTtl extends AeroMapperBaseTest { 15 | 16 | @AerospikeRecord(namespace = "test", set = "classWithTtl", ttl=300) 17 | public static class ClassWithTtl { 18 | @AerospikeKey 19 | public int id; 20 | public String name; 21 | } 22 | 23 | @AerospikeRecord(namespace = "test", set = "classWithTtl") 24 | public static class ClassWithTtlViaPolicy { 25 | @AerospikeKey 26 | public int id; 27 | public String name; 28 | } 29 | 30 | @Test 31 | public void testTtl() { 32 | AeroMapper mapper = new AeroMapper.Builder(client) 33 | .build(); 34 | 35 | ClassWithTtl myRecord = new ClassWithTtl(); 36 | myRecord.id = 1; 37 | myRecord.name = "not overridden ttl"; 38 | mapper.save(myRecord); 39 | 40 | WritePolicy customWritePolicy = new WritePolicy(mapper.getWritePolicy(ClassWithTtl.class)); 41 | customWritePolicy.expiration = 100; 42 | myRecord.id = 2; 43 | myRecord.name = "overridden ttl"; 44 | mapper.save(customWritePolicy, myRecord); 45 | 46 | // To validate the TTL, read the records as raw records rather than via the mapper interface 47 | Record readClient1 = client.get(null, new Key("test", "classWithTtl", 1)); 48 | Record readClient2 = client.get(null, new Key("test", "classWithTtl", 2)); 49 | 50 | assertTrue(readClient1.getTimeToLive() > 290 && readClient1.getTimeToLive() <= 300); 51 | assertTrue(readClient2.getTimeToLive() > 90 && readClient2.getTimeToLive() <= 100); 52 | } 53 | 54 | @Test 55 | public void testTtlViaPolicy() { 56 | WritePolicy writePolicy = new WritePolicy(); 57 | writePolicy.expiration = 300; 58 | AeroMapper mapper = new AeroMapper.Builder(client) 59 | .withWritePolicy(writePolicy).forClasses(ClassWithTtlViaPolicy.class) 60 | .build(); 61 | 62 | ClassWithTtlViaPolicy myRecord = new ClassWithTtlViaPolicy(); 63 | myRecord.id = 1; 64 | myRecord.name = "not overridden ttl"; 65 | mapper.save(myRecord); 66 | 67 | WritePolicy customWritePolicy = new WritePolicy(mapper.getWritePolicy(ClassWithTtlViaPolicy.class)); 68 | customWritePolicy.expiration = 100; 69 | myRecord.id = 2; 70 | myRecord.name = "overridden ttl"; 71 | mapper.save(customWritePolicy, myRecord); 72 | 73 | // To validate the TTL, read the records as raw records rather than via the mapper interface 74 | Record readClient1 = client.get(null, new Key("test", "classWithTtl", 1)); 75 | Record readClient2 = client.get(null, new Key("test", "classWithTtl", 2)); 76 | 77 | assertTrue(readClient1.getTimeToLive() > 290 && readClient1.getTimeToLive() <= 300); 78 | assertTrue(readClient2.getTimeToLive() > 90 && readClient2.getTimeToLive() <= 100); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactivePartialRecordsTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.Key; 4 | import com.aerospike.client.Record; 5 | import com.aerospike.client.policy.WritePolicy; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 9 | import org.junit.jupiter.api.Test; 10 | import reactor.core.scheduler.Schedulers; 11 | 12 | import java.util.Objects; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ReactivePartialRecordsTest extends ReactiveAeroMapperBaseTest { 17 | @AerospikeRecord(namespace = "test", set = "testSet") 18 | public static class DataClass { 19 | @AerospikeKey 20 | int a; 21 | int b; 22 | int c; 23 | int d; 24 | int e; 25 | } 26 | 27 | @Test 28 | public void testPartialSave() { 29 | WritePolicy writePolicy = new WritePolicy(reactorClient.getWritePolicyDefault()); 30 | writePolicy.totalTimeout = 2000; 31 | writePolicy.socketTimeout = 100; 32 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient) 33 | .withWritePolicy(writePolicy).forClasses(DataClass.class) 34 | .build(); 35 | 36 | DataClass dataClass = new DataClass(); 37 | dataClass.a = 1; 38 | dataClass.b = 2; 39 | dataClass.c = 3; 40 | dataClass.d = 4; 41 | dataClass.e = 5; 42 | 43 | // Do a full save 44 | reactiveMapper.save(dataClass).subscribeOn(Schedulers.parallel()).block(); 45 | 46 | Key key = new Key("test", "testSet", 1); 47 | Record record = Objects.requireNonNull(reactorClient.get(null, key).subscribeOn(Schedulers.parallel()).block()).record; 48 | assertEquals(5, record.bins.size()); 49 | assertEquals(3, record.getInt("c")); 50 | 51 | // Perform a partial save, which should replace the record with just these bins 52 | dataClass.c = 9; 53 | dataClass.e = 11; 54 | reactiveMapper.save(dataClass, "a", "c", "e").subscribeOn(Schedulers.parallel()).block(); 55 | 56 | record = Objects.requireNonNull(reactorClient.get(null, key).subscribeOn(Schedulers.parallel()).block()).record; 57 | assertEquals(3, record.bins.size()); 58 | assertEquals(1, record.getInt("a")); 59 | assertEquals(9, record.getInt("c")); 60 | assertEquals(11, record.getInt("e")); 61 | 62 | // Now do an update of bin c and add bin d 63 | dataClass.c = 99; 64 | reactiveMapper.update(dataClass, "c", "d").subscribeOn(Schedulers.parallel()).block(); 65 | 66 | record = Objects.requireNonNull(reactorClient.get(null, key).subscribeOn(Schedulers.parallel()).block()).record; 67 | assertEquals(4, record.bins.size()); 68 | assertEquals(1, record.getInt("a")); 69 | assertEquals(99, record.getInt("c")); 70 | assertEquals(4, record.getInt("d")); 71 | assertEquals(11, record.getInt("e")); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/AeroMapperEnumTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEnum; 4 | import com.aerospike.mapper.annotations.AerospikeKey; 5 | import com.aerospike.mapper.annotations.AerospikeRecord; 6 | import com.aerospike.mapper.tools.AeroMapper; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class AeroMapperEnumTest extends AeroMapperBaseTest { 12 | 13 | @Test 14 | public void runTest() { 15 | A a1 = new A(1, "a", 10, Status.MARRIED, Country.ARGENTINA, Country.ARGENTINA); 16 | A a2 = new A(2, "b", 20, Status.SINGLE, Country.DENMARK, Country.DENMARK); 17 | A a3 = new A(3, "c", 30, Status.COMPLICATED, Country.UNITED_STATES, Country.UNITED_STATES); 18 | A a4 = new A(4, "d", 40, null, null, null); 19 | 20 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 21 | mapper.save(a1); 22 | mapper.save(a2); 23 | mapper.save(a3); 24 | mapper.save(a4); 25 | 26 | A a11 = mapper.read(A.class, 1); 27 | A a12 = mapper.read(A.class, 2); 28 | A a13 = mapper.read(A.class, 3); 29 | A a14 = mapper.read(A.class, 4); 30 | 31 | assertEquals(a1.id, a11.id); 32 | assertEquals(a1.status, a11.status); 33 | assertEquals(a1.country, a11.country); 34 | 35 | assertEquals(a2.name, a12.name); 36 | assertEquals(a2.country, a12.country); 37 | assertEquals(a2.countryAnno, a12.countryAnno); 38 | 39 | assertEquals(a3.age, a13.age); 40 | assertEquals(a3.country, a13.country); 41 | assertEquals(a3.countryAnno, a13.countryAnno); 42 | 43 | assertEquals(a4.age, a14.age); 44 | assertEquals(a4.country, a14.country); 45 | assertEquals(a4.countryAnno, a14.countryAnno); 46 | } 47 | 48 | enum Status { 49 | MARRIED, 50 | SINGLE, 51 | COMPLICATED 52 | } 53 | 54 | enum Country { 55 | ARGENTINA("AR"), 56 | CHINA("CN"), 57 | DENMARK("DK"), 58 | UNITED_STATES("US"); 59 | 60 | private final String countryCode; 61 | 62 | Country(String countryCode) { 63 | this.countryCode = countryCode; 64 | } 65 | 66 | String getCountryCode() { 67 | return this.countryCode; 68 | } 69 | } 70 | 71 | @AerospikeRecord(namespace = "test", set = "A") 72 | public static class A { 73 | @AerospikeKey 74 | public int id; 75 | public String name; 76 | public int age; 77 | public Status status; 78 | @AerospikeEnum(enumField = "countryCode") 79 | public Country countryAnno; 80 | private Country country; 81 | 82 | public A() { 83 | } 84 | 85 | public A(int id, String name, int age, Status status, Country country, Country countryAnno) { 86 | super(); 87 | this.id = id; 88 | this.name = name; 89 | this.age = age; 90 | this.status = status; 91 | this.country = country; 92 | this.countryAnno = countryAnno; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAnonymousReferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 6 | import org.junit.jupiter.api.Test; 7 | import reactor.core.scheduler.Schedulers; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | public class ReactiveAnonymousReferencesTest extends ReactiveAeroMapperBaseTest { 15 | 16 | @AerospikeRecord(namespace = "test", set = "A") 17 | public static class A { 18 | @AerospikeKey 19 | public int id; 20 | public List namedB; 21 | public List unnamedB; 22 | public List nonB; 23 | 24 | public A() { 25 | namedB = new ArrayList<>(); 26 | unnamedB = new ArrayList<>(); 27 | nonB = new ArrayList<>(); 28 | } 29 | } 30 | 31 | @AerospikeRecord(namespace = "test", set = "B") 32 | public static class B { 33 | @AerospikeKey 34 | public int id; 35 | public String name; 36 | } 37 | 38 | @Test 39 | public void runner() { 40 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 41 | B b = new B(); 42 | b.id = 2; 43 | b.name = "a B"; 44 | reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); 45 | 46 | B b1 = new B(); 47 | b1.id = 3; 48 | b1.name = "another B"; 49 | reactiveMapper.save(b1).subscribeOn(Schedulers.parallel()).block(); 50 | 51 | A a = new A(); 52 | a.id = 1; 53 | a.namedB.add(b); 54 | a.namedB.add(b1); 55 | 56 | a.unnamedB.add(b); 57 | a.unnamedB.add(b1); 58 | 59 | List nonB = new ArrayList(); 60 | nonB.add(2L); 61 | nonB.add("B"); 62 | a.nonB.add(nonB); 63 | 64 | reactiveMapper.save(a).subscribeOn(Schedulers.parallel()).block(); 65 | A a2 = reactiveMapper.read(A.class, a.id).subscribeOn(Schedulers.parallel()).block(); 66 | assert a2 != null; 67 | assertEquals(a.id, a2.id); 68 | assertEquals(a.unnamedB.size(), a2.unnamedB.size()); 69 | assertEquals(((B) a.unnamedB.get(0)).id, ((B) a2.unnamedB.get(0)).id); 70 | assertEquals(((B) a.unnamedB.get(0)).name, ((B) a2.unnamedB.get(0)).name); 71 | assertEquals(((B) a.unnamedB.get(1)).id, ((B) a2.unnamedB.get(1)).id); 72 | assertEquals(((B) a.unnamedB.get(1)).name, ((B) a2.unnamedB.get(1)).name); 73 | 74 | assertEquals(a.namedB.size(), a2.namedB.size()); 75 | assertEquals(a.namedB.get(0).id, a2.namedB.get(0).id); 76 | assertEquals(a.namedB.get(0).name, a2.namedB.get(0).name); 77 | assertEquals(a.namedB.get(1).id, a2.namedB.get(1).id); 78 | assertEquals(a.namedB.get(1).name, a2.namedB.get(1).name); 79 | 80 | assertEquals(a.nonB.size(), a2.nonB.size()); 81 | assertEquals(((List) a.nonB.get(0)).get(0), ((List) a2.nonB.get(0)).get(0)); 82 | assertEquals(((List) a.nonB.get(0)).get(1), ((List) a2.nonB.get(0)).get(1)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Bins marked with AerospikeEmbed will have the objects they reference embedded in the parent object, either as a list or a map 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface AerospikeEmbed { 14 | 15 | enum EmbedType { 16 | LIST, 17 | MAP, 18 | DEFAULT 19 | } 20 | 21 | EmbedType type() default EmbedType.DEFAULT; 22 | 23 | /** 24 | * The elementType is used for sub-elements. For example, if there is: 25 | *
26 |      * @AerospikeBin
27 |      * @AerospikeEmbed(elementType = EmbedType.LIST)
28 |      * private List accounts;
29 |      * 
30 | * then the objects will be stored in the database as lists of lists, rather than lists of maps. 31 | */ 32 | EmbedType elementType() default EmbedType.DEFAULT; 33 | 34 | /** 35 | * Determine whether the key should be saved in the sub-object. This is used only for translating 36 | * a list of objects in Java into a map of objects which contains a list. For example:

37 | *

38 |      * public class Transaction {
39 |      *    private String name;
40 |      *    private int value;
41 |      *    @AerospikeKey
42 |      *    private long time;
43 |      *
44 |      *    public Transaction() {}
45 |      *    public Transaction(String name, int value, long time) {
46 |      *        super();
47 |      *        this.name = name;
48 |      *        this.value = value;
49 |      *        this.time = time;
50 |      *    }
51 |      * }
52 |      *
53 |      * ...
54 |      *
55 |      * @AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST)
56 |      * public List<Transaction> txns;
57 |      * 
58 | *

59 | * Assume elements have been inserted into the list as follows: 60 | *

61 |      *  account.txns.add(new Transaction("details1", 100, 101));
62 |      *  account.txns.add(new Transaction("details2", 200, 99));
63 |      *  account.txns.add(new Transaction("details3", 300, 1010));
64 |      * 
65 | *

66 | * Since these elements are stored in a map, the AerospikeKey field (time in this case) will be the key to the map. 67 | * By default, the key is then dropped from the list as it is not needed, resulting in: 68 | *

69 |      * KEY_ORDERED_MAP('{99:["details2", 200], 101:["details1", 100], 1010:["details3", 300]}')
70 |      * 
71 | *

72 | * If it is desired for the key to be part of the list as well, this value can be set to true: 73 | * 74 | *

75 |      * @AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST, saveKey = true)
76 |      * public List<Transaction> txns;
77 |      * 
78 | *

79 | * This would result in: 80 | *

81 |      * KEY_ORDERED_MAP('{99:["details2", 99, 200], 101:["details1", 101, 100], 1010:["details3", 1010, 300]}')
82 |      * 
83 | */ 84 | boolean saveKey() default false; 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeKey; 4 | import com.aerospike.mapper.annotations.AerospikeRecord; 5 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | import java.time.Instant; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ReactiveAeroMapperComplexClassTest extends ReactiveAeroMapperBaseTest { 17 | 18 | @AerospikeRecord(namespace = "test", set = "testSet", sendKey = true) 19 | public static class ComplexClass { 20 | private long trid; 21 | private String cnid; 22 | private double amt; 23 | @AerospikeKey 24 | private Instant trts; 25 | private String res; 26 | private String meid; 27 | private List byte4Fields; 28 | private List byte2Fields; 29 | private List byte1Fields; 30 | private List charFields; 31 | private List strFields; 32 | private boolean testData = false; 33 | 34 | public ComplexClass() { 35 | } 36 | } 37 | 38 | private ReactiveAeroMapper reactiveMapper; 39 | 40 | @BeforeEach 41 | public void setup() { 42 | reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 43 | reactorClient.getAerospikeClient().truncate(null, "test", "testSet", null); 44 | } 45 | 46 | @Test 47 | public void test() { 48 | ComplexClass complex = new ComplexClass(); 49 | complex.amt = 100.1; 50 | complex.trts = Instant.now(); 51 | complex.trid = 18; 52 | complex.cnid = "CN19"; 53 | complex.res = "result"; 54 | complex.meid = "ME11"; 55 | complex.byte4Fields = Arrays.asList(1, 2, 3, 4, 5); 56 | complex.byte2Fields = Arrays.asList(new Short[]{1, 2, 3, 4, 5, 6, 7}); 57 | complex.byte1Fields = Arrays.asList(new Byte[]{1, 2, 3, -1, -2, 127, -128, 0}); 58 | complex.charFields = Arrays.asList(new Character[]{9, 8, 7, 6, 0, 255, 245, 128, 127}); 59 | reactiveMapper.save(complex).subscribeOn(Schedulers.parallel()).block(); 60 | 61 | ComplexClass complex2 = reactiveMapper.read(ComplexClass.class, complex.trts).subscribeOn(Schedulers.parallel()).block(); 62 | assert complex2 != null; 63 | for (int i = 0; i < complex.charFields.size(); i++) { 64 | assertEquals(complex.charFields.get(i), complex2.charFields.get(i)); 65 | } 66 | for (int i = 0; i < complex.byte1Fields.size(); i++) { 67 | assertEquals(complex.byte1Fields.get(i), complex2.byte1Fields.get(i)); 68 | } 69 | for (int i = 0; i < complex.byte2Fields.size(); i++) { 70 | assertEquals(complex.byte2Fields.get(i), complex2.byte2Fields.get(i)); 71 | } 72 | for (int i = 0; i < complex.byte4Fields.size(); i++) { 73 | assertEquals(complex.byte4Fields.get(i), complex2.byte4Fields.get(i)); 74 | } 75 | assertEquals(complex.trts, complex2.trts); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.client.Key; 4 | import com.aerospike.client.Record; 5 | import com.aerospike.client.policy.WritePolicy; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.tools.AeroMapper; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static com.aerospike.client.Value.UseBoolBin; 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class DefaultFieldValuesTest extends AeroMapperBaseTest { 15 | 16 | @AerospikeRecord(namespace = "test", set = "testSet") 17 | public static class DefaultFieldsClass { 18 | @AerospikeKey 19 | String key; 20 | Integer i; 21 | int i2; 22 | Long l; 23 | long l2; 24 | Short s; 25 | short s2; 26 | Float f; 27 | float f2; 28 | Double d; 29 | double d2; 30 | Character c; 31 | char c2; 32 | Byte b; 33 | byte b2; 34 | Boolean bool; 35 | boolean bool2; 36 | } 37 | 38 | @Test 39 | public void testDefaultValues() { 40 | WritePolicy writePolicy = new WritePolicy(client.getWritePolicyDefault()); 41 | writePolicy.totalTimeout = 2000; 42 | writePolicy.socketTimeout = 100; 43 | AeroMapper mapper = new AeroMapper.Builder(client) 44 | .withWritePolicy(writePolicy).forClasses(DefaultFieldsClass.class) 45 | .build(); 46 | 47 | DefaultFieldsClass obj = new DefaultFieldsClass(); 48 | obj.key = "dfc"; 49 | mapper.save(obj); 50 | 51 | DefaultFieldsClass dfc = mapper.read(DefaultFieldsClass.class, "dfc"); 52 | assertNull(dfc.i); 53 | assertEquals(0, dfc.i2); 54 | assertNull(dfc.l); 55 | assertEquals(0, dfc.l2); 56 | assertNull(dfc.s); 57 | assertEquals(0, dfc.s2); 58 | assertNull(dfc.f); 59 | assertEquals(0, dfc.f2); 60 | assertNull(dfc.d); 61 | assertEquals(0, dfc.d2); 62 | assertNull(dfc.c); 63 | assertEquals(0, dfc.c2); 64 | assertNull(dfc.b); 65 | assertEquals(0, dfc.b2); 66 | assertNull(dfc.bool); 67 | assertFalse(dfc.bool2); 68 | } 69 | 70 | @Test 71 | public void testBooleanValue() { 72 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 73 | 74 | DefaultFieldsClass obj = new DefaultFieldsClass(); 75 | obj.key = "dfc"; 76 | 77 | Key key = new Key("test", "testSet", "dfc"); 78 | 79 | UseBoolBin = false; 80 | mapper.save(obj); 81 | Record record = client.get(null, key); 82 | assertTrue(record.bins.get("bool2") instanceof Long); 83 | assertEquals(0, record.getLong("bool2")); 84 | DefaultFieldsClass dfc = mapper.read(DefaultFieldsClass.class, "dfc"); 85 | assertFalse(dfc.bool2); 86 | 87 | UseBoolBin = true; 88 | mapper.save(obj); 89 | record = client.get(null, key); 90 | assertTrue(record.bins.get("bool2") instanceof Boolean); 91 | assertFalse(record.getBoolean("bool2")); 92 | dfc = mapper.read(DefaultFieldsClass.class, "dfc"); 93 | assertFalse(dfc.bool2); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.tools.mappers; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 5 | import com.aerospike.mapper.tools.ClassCache; 6 | import com.aerospike.mapper.tools.ClassCacheEntry; 7 | import com.aerospike.mapper.tools.IBaseAeroMapper; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class ObjectEmbedMapper extends ObjectMapper { 13 | 14 | private final Class referencedClass; 15 | private final IBaseAeroMapper mapper; 16 | private final EmbedType type; 17 | private final boolean skipKey; 18 | 19 | public ObjectEmbedMapper(final Class clazz, final EmbedType type, final IBaseAeroMapper mapper, boolean skipKey) { 20 | this.referencedClass = clazz; 21 | this.mapper = mapper; 22 | this.type = type; 23 | this.skipKey = skipKey; 24 | } 25 | 26 | @Override 27 | public Object toAerospikeFormat(Object value) { 28 | if (value == null) { 29 | return null; 30 | } 31 | if (isSimple(value)) { 32 | return value; 33 | } 34 | // In this case we want to store a reference to the object. 35 | boolean needsType = !(referencedClass.equals(value.getClass())); 36 | // Use the actual class here in case a subclass is passed. In that case needsType will be true. 37 | ClassCacheEntry entry = ClassCache.getInstance().loadClass(value.getClass(), mapper, false); 38 | switch (type) { 39 | case LIST: 40 | return entry.getList(value, skipKey, needsType); 41 | case MAP: // Fall through 42 | // If unspecified, default to a MAP for embedded objects 43 | case DEFAULT: 44 | return entry.getMap(value, needsType); 45 | default: 46 | throw new AerospikeException("Unspecified EmbedType"); 47 | } 48 | } 49 | 50 | @SuppressWarnings("unchecked") 51 | @Override 52 | public Object fromAerospikeFormat(Object value) { 53 | if (value == null) { 54 | return null; 55 | } 56 | if (isSimple(value)) { 57 | return value; 58 | } 59 | ClassCacheEntry entry = ClassCache.getInstance().loadClass(referencedClass, mapper); 60 | try { 61 | switch (type) { 62 | case LIST: 63 | List listValue = (List) value; 64 | return entry.constructAndHydrate(listValue, skipKey); 65 | case MAP: // Fall through 66 | case DEFAULT: 67 | return entry.constructAndHydrate((Map) value); 68 | default: 69 | throw new AerospikeException("Unspecified EmbedType"); 70 | } 71 | } catch (Exception e) { 72 | throw new AerospikeException(e); 73 | } 74 | } 75 | 76 | private boolean isSimple(Object value) { 77 | Class clazz = value.getClass(); 78 | return clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class) 79 | || clazz.equals(Character.class) || Number.class.isAssignableFrom(clazz); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/CollectionMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import com.aerospike.mapper.annotations.AerospikeEmbed; 4 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 5 | import com.aerospike.mapper.annotations.AerospikeKey; 6 | import com.aerospike.mapper.annotations.AerospikeRecord; 7 | import com.aerospike.mapper.annotations.ParamFrom; 8 | import com.aerospike.mapper.tools.AeroMapper; 9 | import com.aerospike.mapper.tools.virtuallist.VirtualList; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class CollectionMapperTest extends AeroMapperBaseTest { 16 | @AerospikeRecord 17 | public static class CollectionElement { 18 | @AerospikeKey 19 | public int id; 20 | public String name; 21 | public long date; 22 | 23 | public CollectionElement(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFrom("date") long date) { 24 | super(); 25 | this.id = id; 26 | this.name = name; 27 | this.date = date; 28 | } 29 | 30 | public int getId() { 31 | return id; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public long getDate() { 39 | return date; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return String.format("{id=%d, name=%s, date=%d}", id, name, date); 45 | } 46 | } 47 | 48 | @AerospikeRecord(namespace = "test", set = "testSet1") 49 | public static class Collection { 50 | @AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST) 51 | public List elements; 52 | 53 | @AerospikeKey 54 | public int id; 55 | 56 | public Collection() { 57 | elements = new ArrayList<>(); 58 | } 59 | } 60 | 61 | @Test 62 | public void test() { 63 | Collection collection = new Collection(); 64 | collection.id = 1; 65 | 66 | collection.elements.add(new CollectionElement(102, "bob", 12345)); 67 | collection.elements.add(new CollectionElement(101, "joe", 23456)); 68 | collection.elements.add(new CollectionElement(100, "sue", 34567)); 69 | 70 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 71 | mapper.save(collection); 72 | 73 | VirtualList list = mapper.asBackedList(collection, "elements", CollectionElement.class); 74 | // list.append(new CollectionElement(103, "tom", 45678)); 75 | // System.out.println("Get by index returned: " + list.get(2)); 76 | // System.out.println("Delete by Key Range returned: " + list.removeByKeyRange(100, 102, true)); 77 | Object results = list.beginMultiOperation() 78 | .append(new CollectionElement(103, "tom", 45678)) 79 | .append(new CollectionElement(104, "tim", 22222)) 80 | .append(new CollectionElement(105, "sam", 33333)) 81 | .append(new CollectionElement(106, "rob", 44444)) 82 | .getByKeyRange(101, 105) 83 | // .removeByKeyRange(100, 102).asResult() 84 | // .get(0) 85 | // .size() 86 | .end(); 87 | 88 | System.out.println(results); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperEnumTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.aerospike.mapper.annotations.AerospikeEnum; 8 | import com.aerospike.mapper.annotations.AerospikeKey; 9 | import com.aerospike.mapper.annotations.AerospikeRecord; 10 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 11 | 12 | import reactor.core.scheduler.Schedulers; 13 | 14 | public class ReactiveAeroMapperEnumTest extends ReactiveAeroMapperBaseTest { 15 | 16 | enum Status { 17 | MARRIED, 18 | SINGLE, 19 | COMPLICATED 20 | } 21 | 22 | enum Country { 23 | ARGENTINA("AR"), 24 | CHINA("CN"), 25 | DENMARK("DK"), 26 | UNITED_STATES("US"); 27 | 28 | private final String countryCode; 29 | 30 | Country(String countryCode) { 31 | this.countryCode = countryCode; 32 | } 33 | 34 | String getCountryCode() { 35 | return this.countryCode; 36 | } 37 | } 38 | 39 | @AerospikeRecord(namespace = "test", set = "A") 40 | public static class A { 41 | @AerospikeKey 42 | public int id; 43 | public String name; 44 | public int age; 45 | public Status status; 46 | public Country country; 47 | @AerospikeEnum(enumField = "countryCode") 48 | public Country countryAnno; 49 | 50 | 51 | public A() { 52 | } 53 | 54 | public A(int id, String name, int age, Status status, Country country, Country countryAnno) { 55 | super(); 56 | this.id = id; 57 | this.name = name; 58 | this.age = age; 59 | this.status = status; 60 | this.country = country; 61 | this.countryAnno = countryAnno; 62 | } 63 | } 64 | 65 | @Test 66 | public void runTest() { 67 | A a1 = new A(1, "a", 10, Status.MARRIED, Country.ARGENTINA, Country.ARGENTINA); 68 | A a2 = new A(2, "b", 20, Status.SINGLE, Country.DENMARK, Country.DENMARK); 69 | A a3 = new A(3, "c", 30, Status.COMPLICATED, Country.UNITED_STATES, Country.UNITED_STATES); 70 | 71 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 72 | reactiveMapper.save(a1).subscribeOn(Schedulers.parallel()).block(); 73 | reactiveMapper.save(a2).subscribeOn(Schedulers.parallel()).block(); 74 | reactiveMapper.save(a3).subscribeOn(Schedulers.parallel()).block(); 75 | 76 | A a11 = reactiveMapper.read(A.class, 1).subscribeOn(Schedulers.parallel()).block(); 77 | A a12 = reactiveMapper.read(A.class, 2).subscribeOn(Schedulers.parallel()).block(); 78 | A a13 = reactiveMapper.read(A.class, 3).subscribeOn(Schedulers.parallel()).block(); 79 | 80 | assert a11 != null; 81 | assertEquals(a1.id, a11.id); 82 | assertEquals(a1.status, a11.status); 83 | assertEquals(a1.country, a11.country); 84 | assert a12 != null; 85 | assertEquals(a2.name, a12.name); 86 | assertEquals(a2.country, a12.country); 87 | assertEquals(a2.countryAnno, a12.countryAnno); 88 | assert a13 != null; 89 | assertEquals(a3.age, a13.age); 90 | assertEquals(a3.countryAnno, a13.countryAnno); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/NonJavaMapperApplication.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples; 2 | 3 | import com.aerospike.client.Bin; 4 | import com.aerospike.client.IAerospikeClient; 5 | import com.aerospike.client.AerospikeClient; 6 | import com.aerospike.client.Key; 7 | import com.aerospike.client.Record; 8 | import com.aerospike.mapper.examples.model.Customer; 9 | import com.fasterxml.jackson.core.JsonProcessingException; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.fasterxml.jackson.databind.ObjectWriter; 12 | 13 | import java.util.Date; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | public class NonJavaMapperApplication extends ApplicationBase { 18 | 19 | private void save(IAerospikeClient client, Customer customer) { 20 | Key key = new Key("test", "customer", customer.getCustomerId()); 21 | 22 | Bin customerId = new Bin("id", customer.getCustomerId()); 23 | Bin firstName = new Bin("firstName", customer.getFirstName()); 24 | Bin lastName = new Bin("lastName", customer.getLastName()); 25 | Bin dateOfBirth = new Bin("dob", customer.getDateOfBirth() == null ? null : customer.getDateOfBirth().getTime()); 26 | Bin phone = new Bin("phone", customer.getPhone()); 27 | Bin joinedBank = new Bin("joinedBank", customer.getJoinedBank() == null ? null : customer.getJoinedBank().getTime()); 28 | Bin vip = new Bin("vip", customer.isVip()); 29 | Bin salutation = new Bin("greet", customer.getPreferredSalutation()); 30 | 31 | client.put(null, key, customerId, firstName, lastName, dateOfBirth, phone, joinedBank, vip, salutation); 32 | } 33 | 34 | private Customer read(IAerospikeClient client, String id) { 35 | Key key = new Key("test", "customer", id); 36 | Record record = client.get(null, key); 37 | 38 | Customer result = new Customer(record.getString("id"), 39 | record.getString("firstName"), 40 | record.getString("lastName")); 41 | 42 | long date = record.getLong("dob"); 43 | result.setDateOfBirth(new Date(date)); 44 | result.setPhone(record.getString("phone")); 45 | date = record.getLong("joinedBank"); 46 | result.setJoinedBank(new Date(date)); 47 | result.setVip(record.getBoolean("vip")); 48 | result.setPreferredSalutation(record.getString("greet")); 49 | 50 | return result; 51 | } 52 | 53 | public void run() throws JsonProcessingException { 54 | IAerospikeClient client = new AerospikeClient(null, "127.0.0.1", 3000); 55 | 56 | Customer customer = createAndPopulateCustomer(); 57 | save(client, customer); 58 | Customer customer2 = null; 59 | for (int i = 0; i < 100; i++) { 60 | long now = System.nanoTime(); 61 | customer2 = read(client, customer.getCustomerId()); 62 | System.out.printf("Customer graph read time: %.3fms%n", (System.nanoTime() - now) / 1000000f); 63 | } 64 | 65 | ObjectWriter objectWriter = new ObjectMapper().writerWithDefaultPrettyPrinter(); 66 | String readString = objectWriter.writeValueAsString(customer2); 67 | System.out.println(readString); 68 | String originalObject = objectWriter.writeValueAsString(customer); 69 | System.out.println(originalObject); 70 | assertEquals(originalObject, readString); 71 | 72 | client.close(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.reactive; 2 | 3 | import com.aerospike.client.AerospikeException; 4 | import com.aerospike.client.query.Filter; 5 | import com.aerospike.client.query.IndexType; 6 | import com.aerospike.mapper.annotations.AerospikeKey; 7 | import com.aerospike.mapper.annotations.AerospikeRecord; 8 | import com.aerospike.mapper.annotations.ParamFrom; 9 | import com.aerospike.mapper.tools.ReactiveAeroMapper; 10 | import org.junit.jupiter.api.Test; 11 | import reactor.core.scheduler.Schedulers; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | public class ReactiveQueryTest extends ReactiveAeroMapperBaseTest { 18 | @AerospikeRecord(namespace = "test", set = "testScan") 19 | public static class A { 20 | @AerospikeKey 21 | private final int id; 22 | private final String name; 23 | private final int age; 24 | 25 | public A(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFrom("age") int age) { 26 | super(); 27 | this.id = id; 28 | this.name = name; 29 | this.age = age; 30 | } 31 | 32 | public int getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public int getAge() { 41 | return age; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return String.format("id:%d, name:%s, age:%d", id, name, age); 47 | } 48 | } 49 | 50 | private ReactiveAeroMapper populate() { 51 | reactorClient.getAerospikeClient().truncate(null, "test", "testScan", null); 52 | ReactiveAeroMapper reactiveMapper = new ReactiveAeroMapper.Builder(reactorClient).build(); 53 | reactiveMapper.save(new A(1, "Tim", 312), 54 | new A(2, "Bob", 44), 55 | new A(3, "Sue", 56), 56 | new A(4, "Rob", 23), 57 | new A(5, "Jim", 32), 58 | new A(6, "Bob", 78), 59 | new A(7, "Fred", 23), 60 | new A(8, "Wilma", 11), 61 | new A(9, "Barney", 54), 62 | new A(10, "Steve", 72), 63 | new A(11, "Bam Bam", 19), 64 | new A(12, "Betty", 34), 65 | new A(13, "Del", 7), 66 | new A(14, "Khon", 98), 67 | new A(15, "Dave", 21), 68 | new A(16, "Mike", 32), 69 | new A(17, "Darren", 14), 70 | new A(18, "Lucy", 45), 71 | new A(19, "Gertrude", 36), 72 | new A(20, "Lucinda", 63)).subscribeOn(Schedulers.parallel()).collectList().block(); 73 | 74 | try { 75 | reactorClient.getAerospikeClient().createIndex(null, "test", "testScan", "age_idx", "age", IndexType.NUMERIC).waitTillComplete(); 76 | } catch (AerospikeException ae) { 77 | // swallow the exception 78 | } 79 | return reactiveMapper; 80 | } 81 | 82 | @Test 83 | public void queryTest() { 84 | ReactiveAeroMapper reactiveMapper = populate(); 85 | List results = reactiveMapper.query(A.class, Filter.range("age", 30, 54)).subscribeOn(Schedulers.parallel()).collectList().block(); 86 | assert results != null; 87 | assertEquals(7, results.size()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.model; 2 | 3 | import java.util.Base64; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import com.aerospike.mapper.annotations.AerospikeBin; 8 | import com.aerospike.mapper.annotations.AerospikeKey; 9 | import com.aerospike.mapper.annotations.AerospikeRecord; 10 | 11 | @AerospikeRecord(namespace = "test", set = "people") 12 | public class PersonDifferentNames { 13 | 14 | @AerospikeKey 15 | @AerospikeBin(name = "s") 16 | private String ssn; 17 | 18 | @AerospikeBin(name = "f") 19 | private String firstName; 20 | 21 | @AerospikeBin(name = "l") 22 | private String lastName; 23 | 24 | @AerospikeBin(name = "a") 25 | private int age; 26 | 27 | private Date dateOfBirth; 28 | private boolean isValid; 29 | private float balance; 30 | private double height; 31 | private byte[] photo; 32 | private List list; 33 | 34 | public PersonDifferentNames() { 35 | } 36 | 37 | public String getSsn() { 38 | return ssn; 39 | } 40 | 41 | public void setSsn(String ssn) { 42 | this.ssn = ssn; 43 | } 44 | 45 | public String getFirstName() { 46 | return firstName; 47 | } 48 | 49 | public void setFirstName(String firstName) { 50 | this.firstName = firstName; 51 | } 52 | 53 | public String getLastName() { 54 | return lastName; 55 | } 56 | 57 | public void setLastName(String lastName) { 58 | this.lastName = lastName; 59 | } 60 | 61 | public int getAge() { 62 | return age; 63 | } 64 | 65 | public void setAge(int age) { 66 | this.age = age; 67 | } 68 | 69 | public Date getDateOfBirth() { 70 | return dateOfBirth; 71 | } 72 | 73 | public void setDateOfBirth(Date dateOfBirth) { 74 | this.dateOfBirth = dateOfBirth; 75 | } 76 | 77 | public boolean isValid() { 78 | return isValid; 79 | } 80 | 81 | public void setValid(boolean isValid) { 82 | this.isValid = isValid; 83 | } 84 | 85 | public float getBalance() { 86 | return balance; 87 | } 88 | 89 | public void setBalance(float balance) { 90 | this.balance = balance; 91 | } 92 | 93 | public double getHeight() { 94 | return height; 95 | } 96 | 97 | public void setHeight(double height) { 98 | this.height = height; 99 | } 100 | 101 | public byte[] getPhoto() { 102 | return photo; 103 | } 104 | 105 | public void setPhoto(byte[] photo) { 106 | this.photo = photo; 107 | } 108 | 109 | public List getList() { 110 | return list; 111 | } 112 | 113 | public void setList(List list) { 114 | this.list = list; 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | byte[] bytes = getPhoto(); 120 | String byteStr = bytes == null ? "null" : Base64.getEncoder().encodeToString(bytes); 121 | return String.format("{ssn=%s, firstName=%s, lastName=%s, age=%d, dob=%s, valid=%b, balance=%f, height=%f, photo=%s}", 122 | this.getSsn(), this.getFirstName(), this.getLastName(), this.getAge(), 123 | this.dateOfBirth == null ? null : this.getDateOfBirth().toString(), this.isValid(), 124 | this.getBalance(), getHeight(), byteStr); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/examples/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper.examples.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import com.aerospike.mapper.annotations.AerospikeBin; 8 | import com.aerospike.mapper.annotations.AerospikeEmbed; 9 | import com.aerospike.mapper.annotations.AerospikeKey; 10 | import com.aerospike.mapper.annotations.AerospikeRecord; 11 | import com.aerospike.mapper.annotations.ParamFrom; 12 | import com.aerospike.mapper.examples.model.accounts.Account; 13 | 14 | @AerospikeRecord(namespace = "test", set = "customer") 15 | public class Customer { 16 | @AerospikeKey 17 | @AerospikeBin(name = "id") 18 | private final String customerId; 19 | 20 | private String firstName; 21 | private String lastName; 22 | 23 | @AerospikeEmbed 24 | @AerospikeBin(name = "mail") 25 | private Address mailingAddress; 26 | 27 | private List accounts; 28 | 29 | @AerospikeBin(name = "dob") 30 | private Date dateOfBirth; 31 | private String phone; 32 | private Date joinedBank; 33 | private boolean vip; 34 | @AerospikeBin(name = "greet") 35 | private String preferredSalutation; 36 | 37 | public Customer(@ParamFrom("id") String customerId, @ParamFrom("firstName") String firstName, @ParamFrom("lastName") String lastName) { 38 | super(); 39 | this.customerId = customerId; 40 | this.firstName = firstName; 41 | this.lastName = lastName; 42 | this.accounts = new ArrayList<>(); 43 | } 44 | 45 | public String getFirstName() { 46 | return firstName; 47 | } 48 | 49 | public void setFirstName(String firstName) { 50 | this.firstName = firstName; 51 | } 52 | 53 | public String getLastName() { 54 | return lastName; 55 | } 56 | 57 | public void setLastName(String lastName) { 58 | this.lastName = lastName; 59 | } 60 | 61 | public Address getMailingAddress() { 62 | return mailingAddress; 63 | } 64 | 65 | public void setMailingAddress(Address mailingAddress) { 66 | this.mailingAddress = mailingAddress; 67 | } 68 | 69 | public List getAccounts() { 70 | return accounts; 71 | } 72 | 73 | public void setAccounts(List accounts) { 74 | this.accounts = accounts; 75 | } 76 | 77 | public String getCustomerId() { 78 | return customerId; 79 | } 80 | 81 | public Date getDateOfBirth() { 82 | return dateOfBirth; 83 | } 84 | 85 | public void setDateOfBirth(Date dateOfBirth) { 86 | this.dateOfBirth = dateOfBirth; 87 | } 88 | 89 | public String getPhone() { 90 | return phone; 91 | } 92 | 93 | public void setPhone(String phone) { 94 | this.phone = phone; 95 | } 96 | 97 | public Date getJoinedBank() { 98 | return joinedBank; 99 | } 100 | 101 | public void setJoinedBank(Date joinedBank) { 102 | this.joinedBank = joinedBank; 103 | } 104 | 105 | public boolean isVip() { 106 | return vip; 107 | } 108 | 109 | public void setVip(boolean vip) { 110 | this.vip = vip; 111 | } 112 | 113 | public String getPreferredSalutation() { 114 | return preferredSalutation; 115 | } 116 | 117 | public void setPreferredSalutation(String preferredSalutation) { 118 | this.preferredSalutation = preferredSalutation; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.aerospike.client.Bin; 8 | import com.aerospike.mapper.annotations.AerospikeKey; 9 | import com.aerospike.mapper.annotations.AerospikeRecord; 10 | import com.aerospike.mapper.annotations.AerospikeGeneration; 11 | import com.aerospike.mapper.tools.AeroMapper; 12 | import com.aerospike.mapper.tools.ClassCacheEntry; 13 | import com.aerospike.mapper.tools.utils.MapperUtils; 14 | 15 | public class GenerationNotStoredTest extends AeroMapperBaseTest { 16 | 17 | @AerospikeRecord(namespace = NAMESPACE, set = "generationNotStoredTest") 18 | public static class EntityWithGeneration { 19 | @AerospikeKey 20 | private int id; 21 | private String name; 22 | @AerospikeGeneration 23 | private Integer generation = 0; 24 | 25 | public EntityWithGeneration() {} 26 | 27 | public EntityWithGeneration(int id, String name) { 28 | this.id = id; 29 | this.name = name; 30 | } 31 | 32 | public int getId() { 33 | return id; 34 | } 35 | 36 | public void setId(int id) { 37 | this.id = id; 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public void setName(String name) { 45 | this.name = name; 46 | } 47 | 48 | public Integer getGeneration() { 49 | return generation; 50 | } 51 | 52 | public void setGeneration(Integer generation) { 53 | this.generation = generation; 54 | } 55 | } 56 | 57 | @Test 58 | public void testGenerationFieldNotStoredAsBin() { 59 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 60 | EntityWithGeneration entity = new EntityWithGeneration(1, "test"); 61 | entity.setGeneration(5); // Set some generation value 62 | 63 | // Get the ClassCacheEntry and the bins that would be stored 64 | ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(EntityWithGeneration.class, mapper); 65 | Bin[] bins = entry.getBins(entity, false, null); 66 | 67 | // Verify that the generation field should NOT be included 68 | // Should only have bins for regular fields (name and potentially id if stored as bin) 69 | 70 | boolean foundName = false; 71 | boolean foundId = false; 72 | boolean foundGeneration = false; 73 | 74 | for (Bin bin : bins) { 75 | String binName = bin.name; 76 | if ("name".equals(binName)) { 77 | foundName = true; 78 | assertEquals("test", bin.value.getObject()); 79 | } else if ("id".equals(binName)) { 80 | foundId = true; 81 | assertEquals(1, bin.value.getObject()); 82 | } else if ("generation".equals(binName)) { 83 | foundGeneration = true; 84 | } 85 | } 86 | 87 | assertTrue(foundName, "Should find 'name' bin"); 88 | assertFalse(foundGeneration, "Should NOT find 'generation' bin - it should not be stored"); 89 | 90 | // Log what bins we actually found for debugging 91 | System.out.println("Found " + bins.length + " bins:"); 92 | for (Bin bin : bins) { 93 | System.out.println(" - " + bin.name + " = " + bin.value.getObject()); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/VirtualListReferenceTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.aerospike.mapper.annotations.AerospikeEmbed; 9 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 10 | import com.aerospike.mapper.annotations.AerospikeKey; 11 | import com.aerospike.mapper.annotations.AerospikeRecord; 12 | import com.aerospike.mapper.tools.AeroMapper; 13 | import com.aerospike.mapper.tools.virtuallist.ReturnType; 14 | import com.aerospike.mapper.tools.virtuallist.VirtualList; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | 18 | public class VirtualListReferenceTest extends AeroMapperBaseTest { 19 | @AerospikeRecord(namespace = "test", set = "A") 20 | public static class A { 21 | @AerospikeKey 22 | public int id; 23 | public String name; 24 | @AerospikeEmbed(type = EmbedType.MAP) 25 | public List refs; 26 | } 27 | 28 | @AerospikeRecord(namespace = "test", set = "B") 29 | public static class B { 30 | @AerospikeKey 31 | public int id; 32 | public String name; 33 | public C c; 34 | public List cs; 35 | } 36 | 37 | @AerospikeRecord(namespace = "test", set = "C") 38 | public static class C { 39 | @AerospikeKey 40 | public int id; 41 | public String name; 42 | } 43 | 44 | @Test 45 | @SuppressWarnings("unchecked") 46 | public void testSingleOpList() { 47 | C c1 = new C(); 48 | c1.id = 1000; 49 | c1.name = "1000"; 50 | 51 | C c2 = new C(); 52 | c2.id = 1001; 53 | c2.name = "name"; 54 | 55 | B b = new B(); 56 | b.id = 10; 57 | b.name = "blah"; 58 | 59 | b.c = c1; 60 | b.cs = new ArrayList<>(); 61 | b.cs.add(c1); 62 | b.cs.add(c2); 63 | 64 | A a = new A(); 65 | a.id = 1; 66 | a.name = "fred"; 67 | a.refs = new ArrayList<>(); 68 | a.refs.add(b); 69 | 70 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 71 | mapper.save(a, b, c1, c2); 72 | VirtualList list = mapper.asBackedList(a, "refs", B.class); 73 | List results = list.getByKeyRange(b.id, b.id + 1, ReturnType.ELEMENTS); 74 | assertEquals(a.refs.get(0).id, results.get(0).id); 75 | assertEquals(a.refs.get(0).name, results.get(0).name); 76 | assertEquals(a.refs.get(0).c.id, results.get(0).c.id); 77 | assertEquals(a.refs.get(0).c.name, results.get(0).c.name); 78 | assertEquals(a.refs.get(0).cs.get(0).id, results.get(0).cs.get(0).id); 79 | assertEquals(a.refs.get(0).cs.get(0).name, results.get(0).cs.get(0).name); 80 | assertEquals(a.refs.get(0).cs.get(1).id, results.get(0).cs.get(1).id); 81 | assertEquals(a.refs.get(0).cs.get(1).name, results.get(0).cs.get(1).name); 82 | 83 | List results2 = (List) list.beginMultiOperation().getByKeyRange(b.id, b.id + 1).end(); 84 | assertEquals(a.refs.get(0).id, results2.get(0).id); 85 | assertEquals(a.refs.get(0).name, results2.get(0).name); 86 | assertEquals(a.refs.get(0).c.id, results2.get(0).c.id); 87 | assertEquals(a.refs.get(0).c.name, results2.get(0).c.name); 88 | assertEquals(a.refs.get(0).cs.get(0).id, results2.get(0).cs.get(0).id); 89 | assertEquals(a.refs.get(0).cs.get(0).name, results2.get(0).cs.get(0).name); 90 | assertEquals(a.refs.get(0).cs.get(1).id, results2.get(0).cs.get(1).id); 91 | assertEquals(a.refs.get(0).cs.get(1).name, results2.get(0).cs.get(1).name); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/SubclassMapTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.aerospike.mapper.annotations.AerospikeEmbed; 11 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 12 | import com.aerospike.mapper.annotations.AerospikeKey; 13 | import com.aerospike.mapper.annotations.AerospikeRecord; 14 | import com.aerospike.mapper.tools.AeroMapper; 15 | 16 | public class SubclassMapTest extends AeroMapperBaseTest { 17 | @AerospikeRecord(namespace = "test", set = "account") 18 | public static class Account { 19 | @AerospikeKey 20 | public int id; 21 | public String name; 22 | } 23 | 24 | @AerospikeRecord(namespace = "test", set = "account", shortName = "PF") 25 | public static class Portfolio extends Account { 26 | public List subAccounts; 27 | 28 | public Portfolio() { 29 | subAccounts = new ArrayList<>(); 30 | } 31 | } 32 | 33 | @AerospikeRecord(shortName = "SG") 34 | public static class Savings extends Account { 35 | public int balance; 36 | } 37 | 38 | @AerospikeRecord(namespace = "test", set = "accounts") 39 | public static class Container { 40 | @AerospikeKey 41 | public int id; 42 | @AerospikeEmbed(type = EmbedType.MAP) 43 | public Account account; 44 | @AerospikeEmbed(type = EmbedType.LIST, elementType = EmbedType.MAP) 45 | public List accounts; 46 | 47 | public Container() { 48 | this.accounts = new ArrayList<>(); 49 | } 50 | } 51 | 52 | @Test 53 | public void test() { 54 | Container container = new Container(); 55 | container.id = 1; 56 | 57 | Portfolio portfolio = new Portfolio(); 58 | portfolio.subAccounts.add("sub-1"); 59 | portfolio.subAccounts.add("sub-2"); 60 | portfolio.id = 222; 61 | container.account = portfolio; 62 | container.accounts.add(portfolio); 63 | Savings savings = new Savings(); 64 | savings.balance = 100; 65 | savings.name = "Bob"; 66 | savings.id = 33; 67 | 68 | container.accounts.add(savings); 69 | 70 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 71 | // mapper.save(container.account); 72 | mapper.save(container); 73 | 74 | Container object = mapper.read(Container.class, 1); 75 | assertEquals(container.id, object.id); 76 | assertEquals(container.account.id, object.account.id); 77 | assertEquals(container.account.name, object.account.name); 78 | assertEquals(((Portfolio) container.account).subAccounts.get(0), ((Portfolio) object.account).subAccounts.get(0)); 79 | assertEquals(((Portfolio) container.account).subAccounts.get(1), ((Portfolio) object.account).subAccounts.get(1)); 80 | 81 | assertEquals(container.accounts.get(0).id, object.accounts.get(0).id); 82 | assertEquals(container.accounts.get(0).name, object.accounts.get(0).name); 83 | assertEquals(((Portfolio) container.accounts.get(0)).subAccounts.get(0), ((Portfolio) object.accounts.get(0)).subAccounts.get(0)); 84 | assertEquals(((Portfolio) container.accounts.get(0)).subAccounts.get(1), ((Portfolio) object.accounts.get(0)).subAccounts.get(1)); 85 | 86 | assertEquals(container.accounts.get(1).id, object.accounts.get(1).id); 87 | assertEquals(container.accounts.get(1).name, object.accounts.get(1).name); 88 | assertEquals(((Savings) container.accounts.get(1)).balance, ((Savings) object.accounts.get(1)).balance); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/aerospike/mapper/SubclassListTest.java: -------------------------------------------------------------------------------- 1 | package com.aerospike.mapper; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.aerospike.mapper.annotations.AerospikeEmbed; 11 | import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; 12 | import com.aerospike.mapper.annotations.AerospikeKey; 13 | import com.aerospike.mapper.annotations.AerospikeRecord; 14 | import com.aerospike.mapper.tools.AeroMapper; 15 | 16 | public class SubclassListTest extends AeroMapperBaseTest { 17 | @AerospikeRecord(namespace = "test", set = "account") 18 | public static class Account { 19 | @AerospikeKey 20 | public int id; 21 | public String name; 22 | } 23 | 24 | @AerospikeRecord(namespace = "test", set = "account", shortName = "PFOLIO") 25 | public static class Portfolio extends Account { 26 | public List subAccounts; 27 | 28 | public Portfolio() { 29 | subAccounts = new ArrayList<>(); 30 | } 31 | } 32 | 33 | @AerospikeRecord(shortName = "SVNG") 34 | public static class Savings extends Account { 35 | public int balance; 36 | } 37 | 38 | @AerospikeRecord(namespace = "test", set = "accounts") 39 | public static class Container { 40 | @AerospikeKey 41 | public int id; 42 | @AerospikeEmbed(type = EmbedType.LIST) 43 | public Account account; 44 | @AerospikeEmbed(type = EmbedType.LIST, elementType = EmbedType.LIST) 45 | public List accounts; 46 | 47 | public Container() { 48 | this.accounts = new ArrayList<>(); 49 | } 50 | } 51 | 52 | @Test 53 | public void test() { 54 | Container container = new Container(); 55 | container.id = 1; 56 | 57 | Portfolio portfolio = new Portfolio(); 58 | portfolio.subAccounts.add("sub-1"); 59 | portfolio.subAccounts.add("sub-2"); 60 | portfolio.id = 222; 61 | container.account = portfolio; 62 | container.accounts.add(portfolio); 63 | Savings savings = new Savings(); 64 | savings.balance = 100; 65 | savings.name = "Bob"; 66 | savings.id = 33; 67 | 68 | container.accounts.add(savings); 69 | 70 | AeroMapper mapper = new AeroMapper.Builder(client).build(); 71 | // mapper.save(container.account); 72 | mapper.save(container); 73 | 74 | Container object = mapper.read(Container.class, 1); 75 | assertEquals(container.id, object.id); 76 | assertEquals(container.account.id, object.account.id); 77 | assertEquals(container.account.name, object.account.name); 78 | assertEquals(((Portfolio) container.account).subAccounts.get(0), ((Portfolio) object.account).subAccounts.get(0)); 79 | assertEquals(((Portfolio) container.account).subAccounts.get(1), ((Portfolio) object.account).subAccounts.get(1)); 80 | 81 | assertEquals(container.accounts.get(0).id, object.accounts.get(0).id); 82 | assertEquals(container.accounts.get(0).name, object.accounts.get(0).name); 83 | assertEquals(((Portfolio) container.accounts.get(0)).subAccounts.get(0), ((Portfolio) object.accounts.get(0)).subAccounts.get(0)); 84 | assertEquals(((Portfolio) container.accounts.get(0)).subAccounts.get(1), ((Portfolio) object.accounts.get(0)).subAccounts.get(1)); 85 | 86 | assertEquals(container.accounts.get(1).id, object.accounts.get(1).id); 87 | assertEquals(container.accounts.get(1).name, object.accounts.get(1).name); 88 | assertEquals(((Savings) container.accounts.get(1)).balance, ((Savings) object.accounts.get(1)).balance); 89 | } 90 | } 91 | --------------------------------------------------------------------------------