diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00bfcba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf + +[*.java] +indent_style = space +indent_size = 4 +insert_final_newline = true +max_line_length = 120 +ij_java_wrap_long_lines = true +ij_java_wrap_comments = true +ij_java_method_call_chain_wrap = normal +ij_java_blank_lines_after_class_header = 1 +ij_java_class_count_to_use_import_on_demand = 10 +ij_java_names_count_to_use_import_on_demand = 10 \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..cda66ea --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,132 @@ +# Copilot Instructions for Aerospike Java Object Mapper + +## Running Java commands +JAVA_HOME=/usr/lib/jvm/java-1.21.0-openjdk-arm64/ +PATH=/usr/lib/jvm/java-1.21.0-openjdk-arm64//bin:$PATH + +## Starting Aerospike Server in Docker for testing + +Command to run Aerospike Server 8.1.0.1: aerospike_server_8101 and wait for its finishing +Command to run aql utility to connect to the DB if needed: +docker run --rm -it aerospike/aerospike-tools:8.1.0 aql -h 172.17.0.2 -U tester -P psw + +## Build & Test + +```bash +# Compile (skip tests) +mvn compile -B + +# Run all tests (requires a running Aerospike server on localhost:3000) +mvn clean test -B -U + +# Run a single test class +mvn test -Dtest=AeroMapperTest -B + +# Run a single test method +mvn test -Dtest=AeroMapperTest#testSimpleSave -B + +# Run tests against a different Aerospike host +mvn test -Dtest.host=myhost:3000 -B +``` + +Java 8 source/target compatibility. No separate lint step. + +## Architecture + +This is an annotation-driven ORM that maps Java POJOs to Aerospike database records. +It has two parallel APIs: synchronous (`AeroMapper`) and reactive (`ReactiveAeroMapper`), +both built through the same `AbstractBuilder` pattern. + +### Core data flow (sync) + +``` +Write path (Use-case → Aerospike): + + save(obj) → ClassCacheEntry.getBins(obj) → IAerospikeClient.put(bins) + + 1. JavaMapperApplication → Entry point, calls mapper.save(customer) + 2. AeroMapper → API layer, creates WritePolicy, extracts key, converts to bins + 3. ClassCacheEntry → Reflection engine, iterates fields, extracts values + 4. TypeMapper → Converts Java types (Date, List) to Aerospike format (Long, Map) + 5. AerospikeClient → SDK handles network I/O + 6. Aerospike Server → Persists record in namespace "test", set "customer" + +Read path (Aerospike → Use-case): + + read(Class, key) → IAerospikeClient.get() → MappingConverter → ClassCacheEntry.hydrateFromRecord() + + 1. JavaMapperApplication → Calls mapper.read(Customer.class, "cust1") + 2. AeroMapper → Constructs Key, checks cache, calls client.get() + 3. AerospikeClient → Retrieves Record from server + 4. Aerospike Server → Returns Record with bin data + 5. MappingConverter → Orchestrates conversion + 6. ClassCacheEntry → Constructs Customer, iterates bins, populates fields (hydrateFromRecord()) + 7. TypeMapper → Converts Aerospike format back to Java types + 8. Customer Object → Fully hydrated and returned + +VirtualList append path (e.g. appending an Item to a Container's embedded list): + + mapper.asBackedList(container, "items", Item.class).append(new Item(500, new Date(), "Item5")) + + 1. AeroMapper.asBackedList(container, "items", Item.class) → Constructs VirtualList, resolves ClassCacheEntry for Container + Item, extracts ListMapper from the @AerospikeEmbed bin "items" + 2. VirtualList.append(item) → Delegates to VirtualListInteractors + 3. VirtualListInteractors.getAppendOperation() → ListMapper.toAerospikeInstanceFormat(item) converts Item to Aerospike-native format (Map/List depending on EmbedType) + 4. VirtualListInteractors → Builds CDT operation: MapOperation.put(binName, key, value) for MAP embed, or ListOperation.append(binName, value) for LIST embed + 5. AerospikeClient.operate(writePolicy, key, operation) → Sends CDT operation to server, atomically appends to the bin without reading the full list + 6. Aerospike Server → Appends element server-side, returns updated bin size + 7. VirtualList → Returns size (long); the in-memory container object is NOT updated + +VirtualList query path (e.g. getByKeyRange): + + list.getByKeyRange(100, 450) + + 1. VirtualList.getByKeyRange(start, end) → Sets return type, delegates to VirtualListInteractors + 2. VirtualListInteractors → Creates Interactor wrapping a DeferredOperation + 3. DeferredOperation.getOperation() → Translates keys via ClassCacheEntry.translateKeyToAerospikeKey(), builds MapOperation.getByKeyRange(binName, startValue, endValue, returnType) + 4. AerospikeClient.operate() → Sends CDT query to server + 5. Aerospike Server → Evaluates range server-side, returns matching entries + 6. Interactor.getResult() → Chains ResultsUnpackers (ArrayUnpacker iterates results, calls ListMapper.fromAerospikeInstanceFormat() per element) + 7. MappingConverter.resolveDependencies() → Resolves any nested @AerospikeReference objects + 8. VirtualList → Returns List of matched elements +``` + +### Key classes and their roles + +- **`AeroMapper` / `ReactiveAeroMapper`** — Public API entry points (sync returns objects, reactive returns `Mono`/`Flux`). Both are instantiated via their inner `Builder` class, never directly. +- **`AbstractBuilder`** — Shared builder logic: register custom type converters, preload classes, set policies, load YAML configuration. +- **`ClassCacheEntry`** — Parses annotations on a mapped class and caches the metadata (namespace, set, key field, bin names, policies). Lazily constructed. Used by the mapper to serialize/deserialize objects. +- **`ClassCache`** — Singleton cache of `ClassCacheEntry` instances. +- **`MappingConverter`** — Orchestrates type conversion during serialization/deserialization using registered `TypeMapper` instances. +- **`TypeMapper`** — Abstract base for custom type converters. Override `toAerospikeFormat()` and `fromAerospikeFormat()`. Register via `builder.addConverter()`. + +### Annotation system (`com.aerospike.mapper.annotations`) + +- `@AerospikeRecord` — Marks a class as mappable; defines namespace, set, TTL. +- `@AerospikeKey` — Designates the primary key field. +- `@AerospikeBin` — Customizes bin (column) name for a field. +- `@AerospikeEmbed` / `@AerospikeReference` — Relationship mapping (embedded vs. referenced sub-objects). +- `@AerospikeExclude` — Excludes a field from mapping. +- `@ToAerospike` / `@FromAerospike` — Custom per-field conversion methods. +- `@AerospikeVersion` / `@AerospikeGeneration` — Optimistic concurrency control. +- `@AerospikeConstructor` / `@ParamFrom` — Controls object construction from records. + +### Type mappers (`tools/mappers/`) + +Built-in mappers for Java primitives, `Date`, `Instant`, `LocalDate`, `LocalDateTime`, `BigDecimal`, `BigInteger`, +enums, arrays, lists, and maps. Custom mappers extend `TypeMapper`. + +### Virtual Lists (`tools/virtuallist/`) + +Aerospike CDT-backed lists that support lazy loading and server-side operations without retrieving entire collections. +Both sync (`VirtualList`) and reactive (`ReactiveVirtualList`) variants exist. + +### Configuration (`tools/configuration/`) + +Alternative to annotations: YAML-based configuration for class mappings via `builder.withConfiguration()`. + +## Conventions + +- **Package root**: `com.aerospike.mapper` — annotations, exceptions, and `tools` sub-packages. +- **Lombok**: Used in production code (provided scope). Ensure IDE/build has Lombok support. +- **Test base classes**: Sync tests extend `AeroMapperBaseTest`; reactive tests extend `ReactiveAeroMapperBaseTest`. Both handle client lifecycle and provide a `compare()` helper that uses Jackson for JSON-based object comparison. +- **Test setup**: Each test method gets a fresh `AeroMapper` via `Builder` and clears `ClassCache` in `@BeforeEach`. Tables are truncated before tests to ensure isolation. diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..f3e1eee --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,69 @@ + + 4.0.0 + + + com.aerospike + java-object-mapper-parent + 2.6.0 + + + java-object-mapper-core + jar + + Aerospike Object Mapper Core + Core annotation-processing and type-mapping engine with no Aerospike client dependency. + + + + javax.validation + validation-api + + + org.apache.commons + commons-lang3 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + org.projectlombok + lombok + + + org.junit.jupiter + junit-jupiter + + + org.mockito + mockito-core + + + + + + + maven-compiler-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + ${project.basedir}/src/main/java + + **/*.properties + **/*.xml + + + + + diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeBin.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeBin.java similarity index 100% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeBin.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeBin.java diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeConstructor.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeConstructor.java similarity index 100% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeConstructor.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeConstructor.java diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java similarity index 100% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeEnum.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeEnum.java similarity index 100% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeEnum.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeEnum.java diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeExclude.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeExclude.java similarity index 100% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeExclude.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeExclude.java diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java similarity index 81% rename from src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java index 472c165..a57ca76 100644 --- a/src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java +++ b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeGeneration.java @@ -1,8 +1,5 @@ package com.aerospike.mapper.annotations; -import com.aerospike.client.policy.GenerationPolicy; -import com.aerospike.client.policy.WritePolicy; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,8 +11,7 @@ * The field or property must be of Integer or int type. When reading an object which has a field marked * with @AerospikeGeneration, the returned record's generation field will be mapped into the @AerospikeGeneration field. * When writing the record, if the @AerospikeGeneration field is non-zero, the generation will be set in the - * {@link WritePolicy#generation} field and the {@link WritePolicy#generationPolicy} will be set to - * {@link GenerationPolicy#EXPECT_GEN_EQUAL}. + * WritePolicy generation field and the generationPolicy will be set to EXPECT_GEN_EQUAL. *

* Example usage: *

diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeGetter.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeGetter.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeGetter.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeGetter.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeKey.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeKey.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeKey.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeKey.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeOrdinal.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeOrdinal.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeOrdinal.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeOrdinal.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeRecord.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeRecord.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeRecord.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeRecord.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeReference.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeReference.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeReference.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeReference.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeSetter.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeSetter.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeSetter.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeSetter.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/AerospikeVersion.java b/core/src/main/java/com/aerospike/mapper/annotations/AerospikeVersion.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/AerospikeVersion.java
rename to core/src/main/java/com/aerospike/mapper/annotations/AerospikeVersion.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/FromAerospike.java b/core/src/main/java/com/aerospike/mapper/annotations/FromAerospike.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/FromAerospike.java
rename to core/src/main/java/com/aerospike/mapper/annotations/FromAerospike.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/ParamFrom.java b/core/src/main/java/com/aerospike/mapper/annotations/ParamFrom.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/ParamFrom.java
rename to core/src/main/java/com/aerospike/mapper/annotations/ParamFrom.java
diff --git a/src/main/java/com/aerospike/mapper/annotations/ToAerospike.java b/core/src/main/java/com/aerospike/mapper/annotations/ToAerospike.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/annotations/ToAerospike.java
rename to core/src/main/java/com/aerospike/mapper/annotations/ToAerospike.java
diff --git a/core/src/main/java/com/aerospike/mapper/exceptions/AerospikeMapperException.java b/core/src/main/java/com/aerospike/mapper/exceptions/AerospikeMapperException.java
new file mode 100644
index 0000000..7456e68
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/exceptions/AerospikeMapperException.java
@@ -0,0 +1,21 @@
+package com.aerospike.mapper.exceptions;
+
+/**
+ * Base unchecked exception for Aerospike Object Mapper errors.
+ */
+public class AerospikeMapperException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public AerospikeMapperException(String message) {
+        super(message);
+    }
+
+    public AerospikeMapperException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AerospikeMapperException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java b/core/src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java
similarity index 59%
rename from src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java
rename to core/src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java
index d8c6462..8292b5d 100644
--- a/src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java
+++ b/core/src/main/java/com/aerospike/mapper/exceptions/NotAnnotatedClass.java
@@ -1,13 +1,11 @@
 package com.aerospike.mapper.exceptions;
 
-import com.aerospike.client.AerospikeException;
-
-public class NotAnnotatedClass extends AerospikeException {
+public class NotAnnotatedClass extends AerospikeMapperException {
 
     private static final long serialVersionUID = -4781097961894057707L;
     public static final int REASON_CODE = -109;
 
     public NotAnnotatedClass(String description) {
-        super(REASON_CODE, description);
+        super(description);
     }
 }
diff --git a/core/src/main/java/com/aerospike/mapper/tools/ClassCache.java b/core/src/main/java/com/aerospike/mapper/tools/ClassCache.java
new file mode 100644
index 0000000..b9ea173
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/ClassCache.java
@@ -0,0 +1,113 @@
+package com.aerospike.mapper.tools;
+
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import com.aerospike.mapper.exceptions.NotAnnotatedClass;
+import com.aerospike.mapper.tools.configuration.ClassConfig;
+import com.aerospike.mapper.tools.configuration.Configuration;
+import com.aerospike.mapper.tools.utils.TypeUtils;
+import lombok.Getter;
+
+import javax.validation.constraints.NotNull;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ClassCache {
+
+    @Getter
+    private static final ClassCache instance = new ClassCache();
+    private final ConcurrentHashMap, ClassCacheEntry> cacheMap = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap classesConfig = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap> storedNameToCacheEntry = new ConcurrentHashMap<>();
+    private final Object lock = new Object();
+
+    private ClassCache() {
+    }
+
+    public  ClassCacheEntry loadClass(@NotNull Class clazz, IObjectMapper mapper) {
+        return loadClass(clazz, mapper, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public  ClassCacheEntry loadClass(@NotNull Class clazz, IObjectMapper mapper, boolean requireRecord) {
+        if (clazz == null || clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class)
+                || clazz.equals(Character.class) || Number.class.isAssignableFrom(clazz)) {
+            return null;
+        }
+
+        ClassCacheEntry entry = (ClassCacheEntry) cacheMap.get(clazz);
+        if (entry == null || entry.isNotConstructed()) {
+            synchronized (lock) {
+                entry = (ClassCacheEntry) cacheMap.get(clazz);
+                if (entry == null) {
+                    try {
+                        entry = new ClassCacheEntry<>(clazz, mapper, getClassConfig(clazz), requireRecord);
+                    } catch (NotAnnotatedClass nae) {
+                        return null;
+                    }
+                    cacheMap.put(clazz, entry);
+                    try {
+                        entry.construct();
+                    } catch (IllegalArgumentException iae) {
+                        cacheMap.remove(clazz);
+                        return null;
+                    } catch (Exception e) {
+                        cacheMap.remove(clazz);
+                        throw e;
+                    }
+                }
+            }
+        }
+        return entry;
+    }
+
+    void setStoredName(@NotNull ClassCacheEntry entry, @NotNull String name) {
+        ClassCacheEntry existingEntry = storedNameToCacheEntry.get(name);
+        if (existingEntry != null && !(existingEntry.equals(entry))) {
+            String errorMessage = String.format("Stored name of \"%s\" is used for both %s and %s",
+                    name, existingEntry.getUnderlyingClass().getName(), entry.getUnderlyingClass().getName());
+            throw new AerospikeMapperException(errorMessage);
+        } else {
+            storedNameToCacheEntry.put(name, entry);
+        }
+    }
+
+    public ClassCacheEntry getCacheEntryFromStoredName(@NotNull String name) {
+        return storedNameToCacheEntry.get(name);
+    }
+
+    public boolean hasClass(Class clazz) {
+        return cacheMap.containsKey(clazz);
+    }
+
+    public void clear() {
+        synchronized (lock) {
+            this.cacheMap.clear();
+            this.classesConfig.clear();
+            TypeUtils.clear();
+            this.storedNameToCacheEntry.clear();
+        }
+    }
+
+    public void addConfiguration(@NotNull Configuration configuration) {
+        for (ClassConfig thisConfig : configuration.getClasses()) {
+            classesConfig.put(thisConfig.getClassName(), thisConfig);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public ClassConfig getClassConfig(String className) {
+        return classesConfig.get(className);
+    }
+
+    public ClassConfig getClassConfig(Class clazz) {
+        return classesConfig.get(clazz.getName());
+    }
+
+    @SuppressWarnings("unused")
+    public boolean hasClassConfig(String className) {
+        return classesConfig.containsKey(className);
+    }
+
+    public boolean hasClassConfig(Class clazz) {
+        return classesConfig.containsKey(clazz.getName());
+    }
+}
diff --git a/src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java b/core/src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java
similarity index 83%
rename from src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java
rename to core/src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java
index b8ca94a..d32f852 100644
--- a/src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java
@@ -6,30 +6,14 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Parameter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
+import java.util.*;
 
 import javax.validation.constraints.NotNull;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 
+import lombok.Getter;
 import org.apache.commons.lang3.StringUtils;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.Bin;
-import com.aerospike.client.Key;
-import com.aerospike.client.Record;
-import com.aerospike.client.Value;
-import com.aerospike.client.cdt.MapOrder;
-import com.aerospike.client.policy.BatchPolicy;
-import com.aerospike.client.policy.Policy;
-import com.aerospike.client.policy.QueryPolicy;
-import com.aerospike.client.policy.ScanPolicy;
-import com.aerospike.client.policy.WritePolicy;
 import com.aerospike.mapper.annotations.AerospikeBin;
 import com.aerospike.mapper.annotations.AerospikeConstructor;
 import com.aerospike.mapper.annotations.AerospikeExclude;
@@ -54,34 +38,40 @@ public class ClassCacheEntry {
     public static final String TYPE_PREFIX = "@T:";
     public static final String TYPE_NAME = ".type";
 
+    @Getter
     private String namespace;
+    @Getter
     private String setName;
     private Integer ttl = null;
     private boolean mapAll = true;
+    @Getter
     private Boolean sendKey = null;
+    @Getter
     private Boolean durableDelete = null;
     private int version = 1;
 
     private final Class clazz;
     private ValueType key;
+    /**
+     * -- GETTER --
+     * Returns the key field name for this class, or null if not declared on this class.
+     */
+    @Getter
     private String keyName = null;
     private boolean keyAsBin = true;
+    @Getter
     private ValueType generationField = null;
     private final TreeMap values = new TreeMap<>();
     private ClassCacheEntry superClazz;
     private int binCount;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
     private Map ordinals = null;
     private Set fieldsWithOrdinals = null;
-    private final ClassConfig classConfig;
-    private final Policy readPolicy;
-    private final WritePolicy writePolicy;
-    private final BatchPolicy batchPolicy;
-    private final QueryPolicy queryPolicy;
-    private final ScanPolicy scanPolicy;
     private String[] constructorParamBins;
     private Object[] constructorParamDefaults;
     private Constructor constructor;
+    @Getter
+    private final ClassConfig classConfig;
     private final ClassConfig config;
 
     private String factoryMethod;
@@ -100,23 +90,18 @@ private enum FactoryMethodType {
      * we provide the ability to set a string representing the class name. This
      * string must be unique for all classes.
      */
+    @Getter
     private String shortenedClassName;
+    @Getter
     private boolean isChildClass = false;
 
     private volatile boolean constructed;
 
     // package visibility only.
-    ClassCacheEntry(@NotNull Class clazz, IBaseAeroMapper mapper, ClassConfig config, boolean requireRecord,
-            @NotNull Policy readPolicy, @NotNull WritePolicy writePolicy, @NotNull BatchPolicy batchPolicy,
-            @NotNull QueryPolicy queryPolicy, @NotNull ScanPolicy scanPolicy) {
+    ClassCacheEntry(@NotNull Class clazz, IObjectMapper mapper, ClassConfig config, boolean requireRecord) {
         this.clazz = clazz;
         this.mapper = mapper;
         this.classConfig = config;
-        this.readPolicy = readPolicy;
-        this.writePolicy = writePolicy;
-        this.batchPolicy = batchPolicy;
-        this.scanPolicy = scanPolicy;
-        this.queryPolicy = queryPolicy;
 
         AerospikeRecord recordDescription = clazz.getAnnotation(AerospikeRecord.class);
         if (requireRecord && recordDescription == null && config == null) {
@@ -128,7 +113,7 @@ private enum FactoryMethodType {
         this.config = config;
     }
 
-    public ClassCacheEntry construct() {
+    public void construct() {
         if (config != null) {
             config.validate();
             this.overrideSettings(config);
@@ -161,45 +146,16 @@ public ClassCacheEntry construct() {
 
         this.checkRecordSettingsAgainstSuperClasses();
         constructed = true;
-        return this;
     }
 
     public boolean isNotConstructed() {
         return !constructed;
     }
 
-    public Policy getReadPolicy() {
-        return readPolicy;
-    }
-
-    public WritePolicy getWritePolicy() {
-        return writePolicy;
-    }
-
-    public BatchPolicy getBatchPolicy() {
-        return batchPolicy;
-    }
-
-    public QueryPolicy getQueryPolicy() {
-        return queryPolicy;
-    }
-
-    public ScanPolicy getScanPolicy() {
-        return scanPolicy;
-    }
-
     public Class getUnderlyingClass() {
         return this.clazz;
     }
 
-    public ClassConfig getClassConfig() {
-        return this.classConfig;
-    }
-
-    public String getShortenedClassName() {
-        return this.shortenedClassName;
-    }
-
     private void overrideSettings(ClassConfig config) {
         if (!StringUtils.isBlank(config.getNamespace())) {
             this.namespace = config.getNamespace();
@@ -233,19 +189,12 @@ private void overrideSettings(ClassConfig config) {
         }
     }
 
-    public boolean isChildClass() {
-        return isChildClass;
-    }
-
     private List loadAerospikeRecordsFromInterfaces(Class clazz) {
         List results = new ArrayList<>();
         Class[] interfaces = clazz.getInterfaces();
-        for (int i = 0; i < interfaces.length; i++) {
-            Class thisInterface = interfaces[i];
+        for (Class thisInterface : interfaces) {
             AerospikeRecord[] aerospikeRecords = thisInterface.getAnnotationsByType(AerospikeRecord.class);
-            for (int j = 0; j < aerospikeRecords.length; j++) {
-                results.add(aerospikeRecords[j]);
-            }
+            Collections.addAll(results, aerospikeRecords);
             results.addAll(loadAerospikeRecordsFromInterfaces(thisInterface));
         }
         return results;
@@ -383,7 +332,7 @@ private void formOrdinalsFromValues() {
                     fieldsWithOrdinals = new HashSet<>();
                 }
                 if (ordinals.containsKey(ordinal)) {
-                    throw new AerospikeException(String.format("Class %s has multiple values with the ordinal of %d",
+                    throw new AerospikeMapperException(String.format("Class %s has multiple values with the ordinal of %d",
                             clazz.getSimpleName(), ordinal));
                 }
                 ordinals.put(ordinal, thisValueName);
@@ -395,7 +344,7 @@ private void formOrdinalsFromValues() {
             // The ordinals need to be valued from 1..
             for (int i = 1; i <= ordinals.size(); i++) {
                 if (!ordinals.containsKey(i)) {
-                    throw new AerospikeException(String.format(
+                    throw new AerospikeMapperException(String.format(
                             "Class %s has %d values specifying ordinals." + " These should be 1..%d, but %d is missing",
                             clazz.getSimpleName(), ordinals.size(), ordinals.size(), i));
                 }
@@ -414,10 +363,8 @@ private boolean validateFactoryMethod(Method method) {
                     || Map.class.isAssignableFrom(params[0].getType()))) {
                 return true;
             }
-            if (params.length == 2 && Class.class.isAssignableFrom(params[0].getType())
-                    && Map.class.isAssignableFrom(params[1].getType())) {
-                return true;
-            }
+            return params.length == 2 && Class.class.isAssignableFrom(params[0].getType())
+                    && Map.class.isAssignableFrom(params[1].getType());
         }
         return false;
     }
@@ -426,12 +373,12 @@ private Method findConstructorFactoryMethod() {
         if (!StringUtils.isBlank(this.factoryClass) || !StringUtils.isBlank(this.factoryMethod)) {
             // Both must be specified
             if (StringUtils.isBlank(this.factoryClass)) {
-                throw new AerospikeException(
+                throw new AerospikeMapperException(
                         String.format("Missing factoryClass definition when factoryMethod is specified on class %s",
                                 clazz.getSimpleName()));
             }
-            if (StringUtils.isBlank(this.factoryClass)) {
-                throw new AerospikeException(
+            if (StringUtils.isBlank(this.factoryMethod)) {
+                throw new AerospikeMapperException(
                         String.format("Missing factoryMethod definition when factoryClass is specified on class %s",
                                 clazz.getSimpleName()));
             }
@@ -442,7 +389,7 @@ private Method findConstructorFactoryMethod() {
                 for (Method method : factoryClazzType.getDeclaredMethods()) {
                     if (validateFactoryMethod(method)) {
                         if (foundMethod != null) {
-                            throw new AerospikeException(String.format(
+                            throw new AerospikeMapperException(String.format(
                                     "Factory Class %s defines at least 2 valid "
                                             + "factory methods (%s, %s) as a factory for class %s",
                                     this.factoryClass, foundMethod, method, this.clazz.getSimpleName()));
@@ -451,7 +398,7 @@ private Method findConstructorFactoryMethod() {
                     }
                 }
                 if (foundMethod == null) {
-                    throw new AerospikeException(String.format("Class %s specified a factory class of %s and a factory"
+                    throw new AerospikeMapperException(String.format("Class %s specified a factory class of %s and a factory"
                             + " method of %s, but no valid method with that name exists on the class. A valid"
                             + " method must be static, can take no parameters, a single Class parameter, a single"
                             + " Map parameter, or a Class and a Map parameter, and must return an object which is"
@@ -460,7 +407,7 @@ private Method findConstructorFactoryMethod() {
                 }
                 return foundMethod;
             } catch (ClassNotFoundException cnfe) {
-                throw new AerospikeException(String.format("Factory class %s for class %s cannot be loaded",
+                throw new AerospikeMapperException(String.format("Factory class %s for class %s cannot be loaded",
                         this.factoryClass, clazz.getSimpleName()));
             }
         }
@@ -493,7 +440,7 @@ private void setConstructorFactoryMethod(Method method) {
     private void findConstructor() {
         Constructor[] constructors = clazz.getDeclaredConstructors();
         if (constructors.length == 0) {
-            throw new AerospikeException(String.format(
+            throw new AerospikeMapperException(String.format(
                     "Class %s has no constructors and hence cannot be mapped to Aerospike", clazz.getSimpleName()));
         }
         Constructor desiredConstructor = null;
@@ -508,7 +455,7 @@ private void findConstructor() {
                 AerospikeConstructor aerospikeConstructor = thisConstructor.getAnnotation(AerospikeConstructor.class);
                 if (aerospikeConstructor != null) {
                     if (desiredConstructor != null) {
-                        throw new AerospikeException(String
+                        throw new AerospikeMapperException(String
                                 .format("Class %s" + " has multiple constructors annotated with @AerospikeConstructor."
                                         + " Only one constructor can be so annotated.", clazz.getSimpleName()));
                     } else {
@@ -523,7 +470,7 @@ private void findConstructor() {
         }
 
         if (desiredConstructor == null) {
-            throw new AerospikeException(String.format("Class %s has neither a no-arg constructor, "
+            throw new AerospikeMapperException(String.format("Class %s has neither a no-arg constructor, "
                     + "nor a constructor annotated with @AerospikeConstructor so cannot be mapped to Aerospike.",
                     clazz.getSimpleName()));
         }
@@ -564,11 +511,11 @@ private void findConstructor() {
                         (!isFromAnnotation && binName.startsWith("arg"))
                                 ? ". Did you forget to specify '-parameters' to javac when building?"
                                 : "");
-                throw new AerospikeException(message);
+                throw new AerospikeMapperException(message);
             }
             Class type = thisParam.getType();
             if (!type.isAssignableFrom(allValues.get(binName).getType())) {
-                throw new AerospikeException(String.format(
+                throw new AerospikeMapperException(String.format(
                         "Class %s has a preferred constructor of" + " %s. However, parameter %s"
                                 + " is of type %s but assigned from bin \"%s\" of type %s."
                                 + " These types are incompatible.",
@@ -635,7 +582,7 @@ private void loadPropertiesFromClass() {
                 if (methodName.startsWith("get")) {
                     Class returnType = thisMethod.getReturnType();
                     if (!returnType.equals(Integer.class) && !returnType.equals(int.class)) {
-                        throw new AerospikeException(String.format(
+                        throw new AerospikeMapperException(String.format(
                                 "@AerospikeGeneration getter %s in class %s must return Integer or int type, but returned %s",
                                 methodName, clazz.getName(), returnType.getSimpleName()));
                     }
@@ -645,13 +592,13 @@ private void loadPropertiesFromClass() {
                 // For setter methods, validate parameter type
                 else if (methodName.startsWith("set")) {
                     if (thisMethod.getParameterCount() != 1) {
-                        throw new AerospikeException(String.format(
+                        throw new AerospikeMapperException(String.format(
                                 "@AerospikeGeneration setter %s in class %s must accept a single parameter, but %d were found",
                                 methodName, clazz.getName(), thisMethod.getParameterCount()));
                     }
                     Class paramType = thisMethod.getParameterTypes()[0];
                     if (!paramType.equals(Integer.class) && !paramType.equals(int.class)) {
-                        throw new AerospikeException(String.format(
+                        throw new AerospikeMapperException(String.format(
                                 "@AerospikeGeneration setter %s in class %s must accept Integer or int type, but accepted %s",
                                 methodName, clazz.getName(), paramType.getSimpleName()));
                     }
@@ -685,7 +632,7 @@ else if (methodName.startsWith("set")) {
 
         if (keyProperty != null) {
             if (key != null) {
-                throw new AerospikeException(String.format("Class %s cannot have more than one key", clazz.getName()));
+                throw new AerospikeMapperException(String.format("Class %s cannot have more than one key", clazz.getName()));
             }
 
             keyProperty.validate(clazz.getName(), config, true);
@@ -697,7 +644,7 @@ else if (methodName.startsWith("set")) {
 
         if (generationProperty != null) {
             if (generationField != null) {
-                throw new AerospikeException(
+                throw new AerospikeMapperException(
                         String.format("Class %s cannot have more than one @AerospikeGeneration field", clazz.getName()));
             }
 
@@ -713,7 +660,7 @@ else if (methodName.startsWith("set")) {
             thisProperty.validate(clazz.getName(), config, false);
 
             if (this.values.get(thisPropertyName) != null) {
-                throw new AerospikeException(String.format("Class %s cannot define the mapped name %s more than once",
+                throw new AerospikeMapperException(String.format("Class %s cannot define the mapped name %s more than once",
                         clazz.getName(), thisPropertyName));
             }
 
@@ -761,12 +708,12 @@ private boolean handleKeyField(Field thisField, BinConfig thisBin) {
                 || (!StringUtils.isBlank(keyField) && keyField.equals(thisField.getName()))) {
             if (thisField.isAnnotationPresent(AerospikeExclude.class)
                     || (thisBin != null && thisBin.isExclude() != null && thisBin.isExclude())) {
-                throw new AerospikeException(String
+                throw new AerospikeMapperException(String
                         .format("Class %s cannot have a field which is both a key and excluded.", clazz.getName()));
             }
 
             if (key != null) {
-                throw new AerospikeException(String.format("Class %s cannot have more than one key", clazz.getName()));
+                throw new AerospikeMapperException(String.format("Class %s cannot have more than one key", clazz.getName()));
             }
 
             AerospikeKey keyAnnotation = thisField.getAnnotation(AerospikeKey.class);
@@ -777,7 +724,7 @@ private boolean handleKeyField(Field thisField, BinConfig thisBin) {
             }
 
             if (!storeAsBin && (this.sendKey == null || !this.sendKey)) {
-                throw new AerospikeException(String.format(
+                throw new AerospikeMapperException(String.format(
                         "Class %s attempts to store primary key information"
                                 + " inside the aerospike key, but sendKey is not true at the record level",
                         clazz.getName()));
@@ -800,19 +747,19 @@ private boolean handleGenerationField(Field thisField, BinConfig thisBin) {
         if (isGenerationField) {
             if (thisField.isAnnotationPresent(AerospikeExclude.class)
                     || (thisBin != null && thisBin.isExclude() != null && thisBin.isExclude())) {
-                throw new AerospikeException(String.format(
+                throw new AerospikeMapperException(String.format(
                         "Class %s cannot have a field which is both a generation field and excluded.", clazz.getName()));
             }
 
             if (generationField != null) {
-                throw new AerospikeException(
+                throw new AerospikeMapperException(
                         String.format("Class %s cannot have more than one @AerospikeGeneration field", clazz.getName()));
             }
 
             // Validate field type is Integer or int
             Class fieldType = thisField.getType();
             if (!fieldType.equals(Integer.class) && !fieldType.equals(int.class)) {
-                throw new AerospikeException(
+                throw new AerospikeMapperException(
                         String.format("@AerospikeGeneration field %s in class %s must be of Integer or int type, but was %s",
                                 thisField.getName(), clazz.getName(), fieldType.getSimpleName()));
             }
@@ -841,7 +788,7 @@ private void mapField(Field thisField, BinConfig thisBin, boolean isKey) {
         }
 
         if (this.values.get(name) != null) {
-            throw new AerospikeException(
+            throw new AerospikeMapperException(
                     String.format("Class %s cannot define the mapped name %s more than once", clazz.getName(), name));
         }
         if ((bin != null && bin.useAccessors())
@@ -858,14 +805,32 @@ private void mapField(Field thisField, BinConfig thisBin, boolean isKey) {
 
     private Method findMethodWithNameAndParams(String name, Class... params) {
         try {
-            Method method = this.clazz.getDeclaredMethod(name, params);
             // TODO: Should this ascend the inheritance hierarchy using getMethod on superclasses?
-            return method;
+            return this.clazz.getDeclaredMethod(name, params);
         } catch (NoSuchMethodException nsme) {
             return null;
         }
     }
 
+    /**
+     * Searches for a 2-param setter whose second parameter is a recognized client Key or Value type, as determined by
+     * the mapper's {@link SetterParamTypeResolver}.
+     */
+    private Method findSetterWithClientParamType(String setterName, Class firstParam) {
+        SetterParamTypeResolver resolver = this.mapper.getSetterParamTypeResolver();
+        for (Method m : this.clazz.getDeclaredMethods()) {
+            if (m.getName().equals(setterName)) {
+                Class[] paramTypes = m.getParameterTypes();
+                if (paramTypes.length == 2
+                        && paramTypes[0].isAssignableFrom(firstParam)
+                    && resolver.resolve(paramTypes[1].getName()) != PropertyDefinition.SetterParamType.NONE) {
+                    return m;
+                }
+            }
+        }
+        return null;
+    }
+
     private void validateAccessorsForField(String binName, Field thisField) {
         String fieldName = thisField.getName();
         String methodNameBase = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
@@ -874,7 +839,7 @@ private void validateAccessorsForField(String binName, Field thisField) {
 
         Method getter = findMethodWithNameAndParams(getterName);
         if (getter == null) {
-            throw new AerospikeException(String.format(
+            throw new AerospikeMapperException(String.format(
                     "Expected to find getter for field %s on class %s due to it being configured to useAccessors," +
                             " but no method with the signature \"%s %s()\" was found",
                     fieldName, this.clazz.getSimpleName(), thisField.getType().getSimpleName(), getterName));
@@ -882,13 +847,10 @@ private void validateAccessorsForField(String binName, Field thisField) {
 
         Method setter = findMethodWithNameAndParams(setterName, thisField.getType());
         if (setter == null) {
-            setter = findMethodWithNameAndParams(setterName, thisField.getType(), Key.class);
+            setter = findSetterWithClientParamType(setterName, thisField.getType());
         }
         if (setter == null) {
-            setter = findMethodWithNameAndParams(setterName, thisField.getType(), Value.class);
-        }
-        if (setter == null) {
-            throw new AerospikeException(String.format(
+            throw new AerospikeMapperException(String.format(
                     "Expected to find setter for field %s on class %s due to it being configured to useAccessors," +
                             " but no method with the name \"%s\" was found",
                     fieldName, this.clazz.getSimpleName(), setterName));
@@ -922,13 +884,13 @@ public Object getKey(Object object) {
         try {
             Object key = this._getKey(object);
             if (key == null) {
-                throw new AerospikeException(String.format(
+                throw new AerospikeMapperException(String.format(
                         "Null key from annotated object of class %s. Did you forget an @AerospikeKey annotation?",
                         this.clazz.getSimpleName()));
             }
             return key;
         } catch (ReflectiveOperationException re) {
-            throw new AerospikeException(re);
+            throw new AerospikeMapperException(re);
         }
     }
 
@@ -944,18 +906,10 @@ public void setKey(Object object, Object value) {
         try {
             this._setKey(object, value);
         } catch (ReflectiveOperationException re) {
-            throw new AerospikeException(re);
+            throw new AerospikeMapperException(re);
         }
     }
 
-    public String getNamespace() {
-        return namespace;
-    }
-
-    public String getSetName() {
-        return setName;
-    }
-
     public Integer getTtl() {
         if (ttl == null || ttl == Integer.MIN_VALUE) {
             return null;
@@ -963,16 +917,41 @@ public Integer getTtl() {
         return ttl;
     }
 
-    public Boolean getSendKey() {
-        return sendKey;
+    /**
+     * Traverses the class hierarchy to find the field name of the key (may be in a superclass).
+     * Returns null if no key field is found.
+     */
+    public String getKeyFieldName() {
+        ClassCacheEntry thisClass = this;
+        while (thisClass != null) {
+            if (thisClass.keyName != null) return thisClass.keyName;
+            thisClass = thisClass.superClazz;
+        }
+        return null;
     }
 
-    public Boolean getDurableDelete() {
-        return durableDelete;
+    /**
+     * Returns true if the key field (wherever it is in the hierarchy) is stored as a bin.
+     */
+    public boolean isKeyFieldStoredAsBin() {
+        ClassCacheEntry thisClass = this;
+        while (thisClass != null) {
+            if (thisClass.keyName != null) return thisClass.keyAsBin;
+            thisClass = thisClass.superClazz;
+        }
+        return true;
     }
 
-    public ValueType getGenerationField() {
-        return generationField;
+    /**
+     * Sets the @AerospikeGeneration field value from a record's generation counter.
+     */
+    public void setGenerationValue(Object instance, int generation) {
+        if (generationField == null) return;
+        try {
+            generationField.set(instance, generation);
+        } catch (ReflectiveOperationException e) {
+            throw new AerospikeMapperException("Failed to set generation field value", e);
+        }
     }
 
     public Integer getGenerationValue(Object instance) {
@@ -988,60 +967,7 @@ public Integer getGenerationValue(Object instance) {
             }
             return null;
         } catch (ReflectiveOperationException e) {
-            throw new AerospikeException("Failed to get generation field value", e);
-        }
-    }
-
-    private boolean contains(String[] names, String thisName) {
-        if (names == null || names.length == 0) {
-            return true;
-        }
-        if (thisName == null) {
-            return false;
-        }
-        for (String aName : names) {
-            if (thisName.equals(aName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public Bin[] getBins(Object instance, boolean allowNullBins, String[] binNames) {
-        try {
-            Bin[] bins = new Bin[this.binCount];
-            int index = 0;
-            ClassCacheEntry thisClass = this;
-            while (thisClass != null) {
-                Set keys = thisClass.values.keySet();
-                for (String name : keys) {
-                    if (name.equals(thisClass.keyName) && !thisClass.keyAsBin) {
-                        // Do not explicitly write the key to the bin
-                        continue;
-                    }
-                    if (contains(binNames, name)) {
-                        ValueType value = (ValueType) thisClass.values.get(name);
-                        Object javaValue = value.get(instance);
-                        Object aerospikeValue = value.getTypeMapper().toAerospikeFormat(javaValue);
-                        if (aerospikeValue != null || allowNullBins) {
-                            if (aerospikeValue instanceof TreeMap) {
-                                TreeMap treeMap = (TreeMap) aerospikeValue;
-                                bins[index++] = new Bin(name, new ArrayList(treeMap.entrySet()), MapOrder.KEY_ORDERED);
-                            } else {
-                                bins[index++] = new Bin(name, Value.get(aerospikeValue));
-                            }
-                        }
-                    }
-                }
-                thisClass = thisClass.superClazz;
-            }
-            if (index != this.binCount) {
-                bins = Arrays.copyOf(bins, index);
-            }
-            return bins;
-        } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException("Failed to get generation field value", e);
         }
     }
 
@@ -1063,7 +989,7 @@ public Map getMap(Object instance, boolean needsType) {
             }
             return results;
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
@@ -1077,8 +1003,8 @@ private void addDataFromValueName(String name, Object instance, ClassCacheEntry<
         }
     }
 
-    private boolean isKeyField(String name) {
-        return keyName != null && keyName.equals(name);
+    private boolean isNotKeyField(String name) {
+        return keyName == null || !keyName.equals(name);
     }
 
     public List getList(Object instance, boolean skipKey, boolean needsType) {
@@ -1093,14 +1019,14 @@ public List getList(Object instance, boolean skipKey, boolean needsType)
                 if (thisClass.ordinals != null) {
                     for (int i = 1; i <= thisClass.ordinals.size(); i++) {
                         String name = thisClass.ordinals.get(i);
-                        if (!skipKey || !isKeyField(name)) {
+                        if (!skipKey || isNotKeyField(name)) {
                             addDataFromValueName(name, instance, thisClass, results);
                         }
                     }
                 }
                 for (String name : thisClass.values.keySet()) {
                     if (thisClass.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
-                        if (!skipKey || !isKeyField(name)) {
+                        if (!skipKey || isNotKeyField(name)) {
                             addDataFromValueName(name, instance, thisClass, results);
                         }
                     }
@@ -1113,28 +1039,18 @@ public List getList(Object instance, boolean skipKey, boolean needsType)
             }
             return results;
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
-    public T constructAndHydrate(Map map) {
-        return constructAndHydrate(null, null, map);
-    }
-
-    public T constructAndHydrate(Key key, Record record) {
-        return constructAndHydrate(key, record, null);
-    }
-
     @SuppressWarnings("unchecked")
-    private T constructAndHydrate(Key key, Record record, Map map) {
+    public T constructAndHydrate(Map map) {
         Map valueMap = new HashMap<>();
         try {
             ClassCacheEntry thisClass = this;
 
-            // If the object saved in the list was a subclass of the declared type, it must
-            // have the type name in the map
-            // Note that there is a performance implication of using subclasses.
-            String className = map == null ? record.getString(TYPE_NAME) : (String) map.get(TYPE_NAME);
+            // If the object saved was a subclass of the declared type, it will have TYPE_NAME in the map
+            String className = (String) map.get(TYPE_NAME);
             if (className != null) {
                 thisClass = ClassCache.getInstance().getCacheEntryFromStoredName(className);
                 if (thisClass == null) {
@@ -1147,19 +1063,7 @@ private T constructAndHydrate(Key key, Record record, Map map) {
             while (thisClass != null) {
                 for (String name : thisClass.values.keySet()) {
                     ValueType value = thisClass.values.get(name);
-                    Object aerospikeValue;
-                    if (record == null) {
-                        aerospikeValue = map.get(name);
-                    } else if (name.equals(thisClass.keyName) && !thisClass.keyAsBin) {
-                        if (key.userKey != null) {
-                            aerospikeValue = key.userKey.getObject();
-                        } else {
-                            throw new AerospikeException(String.format("Key field on class %s was  for key %s."
-                                    + " Was the record saved passing 'sendKey = true'? ", className, key));
-                        }
-                    } else {
-                        aerospikeValue = record.getValue(name);
-                    }
+                    Object aerospikeValue = map.get(name);
                     valueMap.put(name, value.getTypeMapper().fromAerospikeFormat(aerospikeValue));
                 }
                 if (result == null) {
@@ -1173,43 +1077,25 @@ private T constructAndHydrate(Key key, Record record, Map map) {
                 valueMap.clear();
                 thisClass = thisClass.superClazz;
             }
-
-            // Set the generation value from the record to the @AerospikeGeneration field if present
-            if (result != null && generationField != null && record != null) {
-                try {
-                    generationField.set(result, record.generation);
-                } catch (ReflectiveOperationException e) {
-                    throw new AerospikeException("Failed to set generation field from record generation", e);
-                }
-            }
-
             return result;
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
-    public void hydrateFromRecord(Record record, Object instance) {
-        this.hydrateFromRecordOrMap(record, null, instance);
-    }
-
     public void hydrateFromMap(Map map, Object instance) {
-        this.hydrateFromRecordOrMap(null, map, instance);
-    }
-
-    private void hydrateFromRecordOrMap(Record record, Map map, Object instance) {
         try {
             ClassCacheEntry thisClass = this;
             while (thisClass != null) {
                 for (String name : this.values.keySet()) {
                     ValueType value = this.values.get(name);
-                    Object aerospikeValue = record == null ? map.get(name) : record.getValue(name);
+                    Object aerospikeValue = map.get(name);
                     value.set(instance, value.getTypeMapper().fromAerospikeFormat(aerospikeValue));
                 }
                 thisClass = thisClass.superClazz;
             }
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
@@ -1331,7 +1217,7 @@ public T constructAndHydrate(List list, boolean skipKey) {
                     if (thisClass.ordinals != null) {
                         for (int i = 1; i <= thisClass.ordinals.size(); i++) {
                             String name = thisClass.ordinals.get(i);
-                            if (!skipKey || !isKeyField(name)) {
+                            if (!skipKey || isNotKeyField(name)) {
                                 index = thisClass.setValueByField(name, objectVersion, recordVersion, null, index, list,
                                         valueMap);
                             }
@@ -1339,7 +1225,7 @@ public T constructAndHydrate(List list, boolean skipKey) {
                     }
                     for (String name : thisClass.values.keySet()) {
                         if (thisClass.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
-                            if (!skipKey || !isKeyField(name)) {
+                            if (!skipKey || isNotKeyField(name)) {
                                 index = thisClass.setValueByField(name, objectVersion, recordVersion, null, index, list,
                                         valueMap);
                             }
@@ -1359,7 +1245,7 @@ public T constructAndHydrate(List list, boolean skipKey) {
             }
             return result;
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
@@ -1380,7 +1266,7 @@ public void hydrateFromList(List list, Object instance, boolean skipKey)
                     if (ordinals != null) {
                         for (int i = 1; i <= ordinals.size(); i++) {
                             String name = ordinals.get(i);
-                            if (!skipKey || !isKeyField(name)) {
+                            if (!skipKey || isNotKeyField(name)) {
                                 index = setValueByField(name, objectVersion, recordVersion, instance, index, list,
                                         null);
                             }
@@ -1388,7 +1274,7 @@ public void hydrateFromList(List list, Object instance, boolean skipKey)
                     }
                     for (String name : this.values.keySet()) {
                         if (this.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
-                            if (!skipKey || !isKeyField(name)) {
+                            if (!skipKey || isNotKeyField(name)) {
                                 index = setValueByField(name, objectVersion, recordVersion, instance, index, list,
                                         null);
                             }
@@ -1398,7 +1284,7 @@ public void hydrateFromList(List list, Object instance, boolean skipKey)
                 }
             }
         } catch (ReflectiveOperationException ref) {
-            throw new AerospikeException(ref);
+            throw new AerospikeMapperException(ref);
         }
     }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/ConfigurationUtils.java b/core/src/main/java/com/aerospike/mapper/tools/ConfigurationUtils.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/ConfigurationUtils.java
rename to core/src/main/java/com/aerospike/mapper/tools/ConfigurationUtils.java
diff --git a/src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java b/core/src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java
similarity index 81%
rename from src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java
rename to core/src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java
index cf10334..ff908fb 100644
--- a/src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/DeferredObjectLoader.java
@@ -1,5 +1,7 @@
 package com.aerospike.mapper.tools;
 
+import lombok.Getter;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -8,6 +10,7 @@ public interface DeferredSetter {
         void setValue(Object object);
     }
 
+    @Getter
     public static class DeferredObject {
         private final Object key;
         private final Class type;
@@ -20,19 +23,9 @@ public DeferredObject(Object key, Class type, boolean isDigest) {
             this.isDigest = isDigest;
         }
 
-        public Object getKey() {
-            return key;
-        }
-
-        public Class getType() {
-            return type;
-        }
-
-        public boolean isDigest() {
-            return isDigest;
-        }
     }
 
+    @Getter
     public static class DeferredObjectSetter {
         private final DeferredSetter setter;
         private final DeferredObject object;
@@ -43,13 +36,6 @@ public DeferredObjectSetter(DeferredSetter setter, DeferredObject object) {
             this.object = object;
         }
 
-        public DeferredSetter getSetter() {
-            return setter;
-        }
-
-        public DeferredObject getObject() {
-            return object;
-        }
     }
 
 
diff --git a/src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java b/core/src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java
similarity index 63%
rename from src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java
index 1a8bcb2..6697390 100644
--- a/src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/GenericTypeMapper.java
@@ -1,14 +1,16 @@
 package com.aerospike.mapper.tools;
 
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
-import com.aerospike.client.AerospikeException;
 import com.aerospike.mapper.annotations.FromAerospike;
 import com.aerospike.mapper.annotations.ToAerospike;
 import com.aerospike.mapper.tools.utils.TypeUtils;
+import lombok.Getter;
 
 public class GenericTypeMapper extends TypeMapper {
+    @Getter
     private final Class mappedClass;
     private final Object converter;
     private Method toAerospike;
@@ -18,14 +20,14 @@ public GenericTypeMapper(Object converter) {
         for (Method method : converter.getClass().getMethods()) {
             if (method.isAnnotationPresent(ToAerospike.class)) {
                 if (toAerospike != null) {
-                    throw new AerospikeException(String.format("Multiple methods annotated with @ToAerospike: %s, %s",
+                    throw new AerospikeMapperException(String.format("Multiple methods annotated with @ToAerospike: %s, %s",
                             toAerospike.toGenericString(), method.toGenericString()));
                 }
                 toAerospike = method;
             }
             if (method.isAnnotationPresent(FromAerospike.class)) {
                 if (fromAerospike != null) {
-                    throw new AerospikeException(String.format("Multiple methods annotated with @FromAerospike: %s, %s",
+                    throw new AerospikeMapperException(String.format("Multiple methods annotated with @FromAerospike: %s, %s",
                             fromAerospike.toGenericString(), method.toGenericString()));
                 }
                 fromAerospike = method;
@@ -35,16 +37,12 @@ public GenericTypeMapper(Object converter) {
         mappedClass = validateAndGetClass();
     }
 
-    public Class getMappedClass() {
-        return mappedClass;
-    }
-
     @Override
     public Object toAerospikeFormat(Object value) {
         try {
             return this.toAerospike.invoke(converter, value);
         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-            throw new AerospikeException(e);
+            throw new AerospikeMapperException(e);
         }
     }
 
@@ -53,39 +51,39 @@ public Object fromAerospikeFormat(Object value) {
         try {
             return this.fromAerospike.invoke(converter, value);
         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-            throw new AerospikeException(e);
+            throw new AerospikeMapperException(e);
         }
     }
 
     public Class validateAndGetClass() {
         if (this.toAerospike == null) {
-            throw new AerospikeException(String.format("Converter class %s must have a @ToAerospike annotated method.", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("Converter class %s must have a @ToAerospike annotated method.", this.converter.getClass()));
         }
         if (this.toAerospike.getParameterCount() != 1) {
-            throw new AerospikeException(String.format("@ToAerospike method on Converter class %s must take 1 argument", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("@ToAerospike method on Converter class %s must take 1 argument", this.converter.getClass()));
         }
         if (TypeUtils.isVoidType(this.toAerospike.getReturnType())) {
-            throw new AerospikeException(String.format("@ToAerospike method on Converter class %s cannot return void", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("@ToAerospike method on Converter class %s cannot return void", this.converter.getClass()));
         }
         this.toAerospike.setAccessible(true);
 
         if (this.fromAerospike == null) {
-            throw new AerospikeException(String.format("Converter class %s must have a @FromAerospike annotated method.", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("Converter class %s must have a @FromAerospike annotated method.", this.converter.getClass()));
         }
         if (this.fromAerospike.getParameterCount() != 1) {
-            throw new AerospikeException(String.format("@FromAerospike method on Converter class %s must take 1 argument", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("@FromAerospike method on Converter class %s must take 1 argument", this.converter.getClass()));
         }
         if (TypeUtils.isVoidType(this.fromAerospike.getReturnType())) {
-            throw new AerospikeException(String.format("@FromAerospike method on Converter class %s cannot return void", this.converter.getClass()));
+            throw new AerospikeMapperException(String.format("@FromAerospike method on Converter class %s cannot return void", this.converter.getClass()));
         }
         this.fromAerospike.setAccessible(true);
 
         if (!this.toAerospike.getParameters()[0].getType().equals(this.fromAerospike.getReturnType())) {
-            throw new AerospikeException(String.format("@FromAerospike method on Converter class %s returns %s, but the @ToAerospike method takes %s. These should be the same class",
+            throw new AerospikeMapperException(String.format("@FromAerospike method on Converter class %s returns %s, but the @ToAerospike method takes %s. These should be the same class",
                     this.converter.getClass().getSimpleName(), this.fromAerospike.getReturnType().getSimpleName(), this.toAerospike.getParameters()[0].getType().getSimpleName()));
         }
         if (!this.fromAerospike.getParameters()[0].getType().equals(this.toAerospike.getReturnType())) {
-            throw new AerospikeException(String.format("@ToAerospike method on Converter class %s returns %s, but the @FromAerospike method takes %s. These should be the same class",
+            throw new AerospikeMapperException(String.format("@ToAerospike method on Converter class %s returns %s, but the @FromAerospike method takes %s. These should be the same class",
                     this.converter.getClass().getSimpleName(), this.toAerospike.getReturnType().getSimpleName(), this.fromAerospike.getParameters()[0].getType().getSimpleName()));
         }
         // We need to return the Java type, which is the result of the FromAerospike
diff --git a/core/src/main/java/com/aerospike/mapper/tools/IObjectMapper.java b/core/src/main/java/com/aerospike/mapper/tools/IObjectMapper.java
new file mode 100644
index 0000000..358c7fa
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/IObjectMapper.java
@@ -0,0 +1,19 @@
+package com.aerospike.mapper.tools;
+
+/**
+ * Minimal contract that core-bound classes use to interact with the enclosing mapper.
+ * Implemented by both the legacy AeroMapper and the fluent FluentAeroMapper.
+ */
+public interface IObjectMapper {
+    IRecordConverter getMappingConverter();
+
+    RecordLoader getRecordLoader();
+
+    /**
+     * Returns a resolver that maps setter parameter type names to {@link PropertyDefinition.SetterParamType} values for
+     * the client library used by this mapper.
+     */
+    default SetterParamTypeResolver getSetterParamTypeResolver() {
+        return SetterParamTypeResolver.DEFAULT;
+    }
+}
diff --git a/core/src/main/java/com/aerospike/mapper/tools/IRecordConverter.java b/core/src/main/java/com/aerospike/mapper/tools/IRecordConverter.java
new file mode 100644
index 0000000..8a5b684
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/IRecordConverter.java
@@ -0,0 +1,13 @@
+package com.aerospike.mapper.tools;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Core-level abstraction for converting Aerospike bin data to Java objects.
+ * Implemented by MappingConverter in the legacy module.
+ */
+public interface IRecordConverter {
+     T convertToObject(Class clazz, Map record);
+     T convertToObject(Class clazz, List record);
+}
diff --git a/src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java b/core/src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java
similarity index 84%
rename from src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java
rename to core/src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java
index 5a46bc3..45ac9d0 100644
--- a/src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/LoadedObjectResolver.java
@@ -1,7 +1,5 @@
 package com.aerospike.mapper.tools;
 
-import com.aerospike.client.Key;
-
 import java.util.HashMap;
 import java.util.Map;
 
@@ -26,20 +24,20 @@ public static void end() {
     }
 
     public static void setObjectForCurrentKey(Object object) {
-        Key currentKey = ThreadLocalKeySaver.get();
+        Object currentKey = ThreadLocalKeySaver.getKeyContext();
         LoadedObjectMap map = threadLocalObjects.get();
         if (currentKey != null) {
             map.objectMap.put(currentKey, object);
         }
     }
 
-    public static Object get(Key key) {
+    public static Object get(Object key) {
         LoadedObjectMap map = threadLocalObjects.get();
         return map.objectMap.get(key);
     }
 
     private static class LoadedObjectMap {
-        private final Map objectMap = new HashMap<>();
+        private final Map objectMap = new HashMap<>();
         private int referenceCount = 0;
     }
 }
diff --git a/src/main/java/com/aerospike/mapper/tools/PrimitiveDefaults.java b/core/src/main/java/com/aerospike/mapper/tools/PrimitiveDefaults.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/PrimitiveDefaults.java
rename to core/src/main/java/com/aerospike/mapper/tools/PrimitiveDefaults.java
diff --git a/src/main/java/com/aerospike/mapper/tools/Processor.java b/core/src/main/java/com/aerospike/mapper/tools/Processor.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/Processor.java
rename to core/src/main/java/com/aerospike/mapper/tools/Processor.java
diff --git a/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java b/core/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java
similarity index 55%
rename from src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java
rename to core/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java
index 763b85e..56acdac 100644
--- a/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java
@@ -1,16 +1,16 @@
 package com.aerospike.mapper.tools;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.Key;
-import com.aerospike.client.Value;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.configuration.ClassConfig;
 import com.aerospike.mapper.tools.utils.TypeUtils;
 import com.aerospike.mapper.tools.utils.TypeUtils.AnnotatedType;
 
+import lombok.Getter;
+import lombok.Setter;
+
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
-import java.lang.reflect.Type;
 
 public class PropertyDefinition {
 
@@ -21,38 +21,22 @@ public enum SetterParamType {
     }
 
     private final String name;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
+    @Getter @Setter
     private Method getter;
+    @Getter @Setter
     private Method setter;
     private Class clazz;
+    @Getter
     private TypeMapper typeMapper;
+    @Getter
     private SetterParamType setterParamType = SetterParamType.NONE;
 
-    public PropertyDefinition(String name, IBaseAeroMapper mapper) {
+    public PropertyDefinition(String name, IObjectMapper mapper) {
         this.name = name;
         this.mapper = mapper;
     }
 
-    public Method getGetter() {
-        return getter;
-    }
-
-    public void setGetter(Method getter) {
-        this.getter = getter;
-    }
-
-    public Method getSetter() {
-        return setter;
-    }
-
-    public void setSetter(Method setter) {
-        this.setter = setter;
-    }
-
-    public SetterParamType getSetterParamType() {
-        return setterParamType;
-    }
-
     /**
      * Get the type of this property. The getter and setter must agree on the property and this method
      * is only valid after the validate method has been called.
@@ -61,52 +45,43 @@ public Class getType() {
         return this.clazz;
     }
 
-    public TypeMapper getTypeMapper() {
-        return typeMapper;
-    }
-
     public Annotation[] getAnnotations() {
         return getter != null ? getter.getAnnotations() : setter.getAnnotations();
     }
 
-    public Type getGenericType() {
-        return this.getter.getGenericReturnType();
-    }
-
     /**
      * Validate that this is a valid property
      */
     public void validate(String className, ClassConfig config, boolean allowNoSetter) {
         if (this.getter == null) {
-            throw new AerospikeException(String.format("Property %s on class %s must have a getter", this.name, className));
+            throw new AerospikeMapperException(String.format("Property %s on class %s must have a getter", this.name, className));
         }
         if (getter.getParameterCount() != 0) {
-            throw new AerospikeException(String.format("Getter for property %s on class %s must take 0 arguments", this.name, className));
+            throw new AerospikeMapperException(String.format("Getter for property %s on class %s must take 0 arguments", this.name, className));
         }
         Class getterClazz = getter.getReturnType();
         if (TypeUtils.isVoidType(getterClazz)) {
-            throw new AerospikeException(String.format("Getter for property %s on class %s cannot return void", this.name, className));
+            throw new AerospikeMapperException(String.format("Getter for property %s on class %s cannot return void", this.name, className));
         }
         this.getter.setAccessible(true);
 
         Class setterClazz = null;
         if (this.setter != null || !allowNoSetter) {
             if (this.setter == null) {
-                throw new AerospikeException(String.format("Property %s on class %s must have a setter", this.name, className));
+                throw new AerospikeMapperException(String.format("Property %s on class %s must have a setter", this.name, className));
             }
 
             if (setter.getParameterCount() == 2) {
                 Parameter param = setter.getParameters()[1];
-                if (param.getType().isAssignableFrom(Key.class)) {
-                    this.setterParamType = SetterParamType.KEY;
-                } else if (param.getType().isAssignableFrom(Value.class)) {
-                    this.setterParamType = SetterParamType.VALUE;
-                } else {
-                    throw new AerospikeException(String.format("Property %s on class %s has a setter with 2 arguments," +
+                String paramTypeName = param.getType().getName();
+                SetterParamTypeResolver resolver = mapper.getSetterParamTypeResolver();
+                this.setterParamType = resolver.resolve(paramTypeName);
+                if (this.setterParamType == SetterParamType.NONE) {
+                    throw new AerospikeMapperException(String.format("Property %s on class %s has a setter with 2 arguments," +
                             " but the second one is neither a Key nor a Value", this.name, className));
                 }
             } else if (setter.getParameterCount() != 1) {
-                throw new AerospikeException(String.format("Setter for property %s on class %s must take 1 or 2 arguments",
+                throw new AerospikeMapperException(String.format("Setter for property %s on class %s must take 1 or 2 arguments",
                         this.name, className));
             }
             setterClazz = setter.getParameterTypes()[0];
@@ -114,7 +89,7 @@ public void validate(String className, ClassConfig config, boolean allowNoSetter
         }
 
         if (setterClazz != null && !getterClazz.equals(setterClazz)) {
-            throw new AerospikeException(String.format("Getter (%s) and setter (%s) for property %s on class %s differ in type",
+            throw new AerospikeMapperException(String.format("Getter (%s) and setter (%s) for property %s on class %s differ in type",
                     getterClazz.getName(), setterClazz.getName(), this.name, className));
         }
         this.clazz = getterClazz;
diff --git a/core/src/main/java/com/aerospike/mapper/tools/RecordKey.java b/core/src/main/java/com/aerospike/mapper/tools/RecordKey.java
new file mode 100644
index 0000000..1e9eae8
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/RecordKey.java
@@ -0,0 +1,47 @@
+package com.aerospike.mapper.tools;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Client-agnostic identifier for an Aerospike record.
+ * Used by RecordLoader for batch operations.
+ */
+public class RecordKey {
+    public final String namespace;
+    public final String setName;
+    /** User-key value; null when the record is identified by digest only. */
+    public final Object keyValue;
+    /** Pre-computed digest; null when the record is identified by keyValue. */
+    public final byte[] digest;
+
+    public RecordKey(String namespace, String setName, Object keyValue) {
+        this.namespace = namespace;
+        this.setName = setName;
+        this.keyValue = keyValue;
+        this.digest = null;
+    }
+
+    public RecordKey(String namespace, String setName, byte[] digest) {
+        this.namespace = namespace;
+        this.setName = setName;
+        this.keyValue = null;
+        this.digest = digest;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RecordKey)) return false;
+        RecordKey that = (RecordKey) o;
+        return Objects.equals(namespace, that.namespace)
+                && Objects.equals(setName, that.setName)
+                && Objects.equals(keyValue, that.keyValue)
+                && Arrays.equals(digest, that.digest);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(namespace, setName, keyValue, Arrays.hashCode(digest));
+    }
+}
diff --git a/core/src/main/java/com/aerospike/mapper/tools/RecordLoader.java b/core/src/main/java/com/aerospike/mapper/tools/RecordLoader.java
new file mode 100644
index 0000000..7df4c45
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/RecordLoader.java
@@ -0,0 +1,32 @@
+package com.aerospike.mapper.tools;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstraction over the Aerospike client's record-fetch operations.
+ * Implemented by the legacy module (via IAerospikeClient) and the fluent module (via Session).
+ * Used by MappingConverter to resolve @AerospikeReference objects without a direct client dependency.
+ */
+public interface RecordLoader {
+    /**
+     * Fetch a single record by its user-key value. Returns null if not found.
+     */
+    Map getRecord(String namespace, String setName, Object keyValue);
+
+    /**
+     * Fetch a single record by its pre-computed digest. Returns null if not found.
+     */
+    Map getRecordByDigest(String namespace, String setName, byte[] digest);
+
+    /**
+     * Batch-fetch records. Entries with no matching record are returned as null in the list.
+     */
+    List> getBatchRecords(List keys);
+
+    /**
+     * Compute the Aerospike record digest for the given set and user key.
+     * Used for lazy-loaded @AerospikeReference objects stored by digest.
+     */
+    byte[] computeDigest(String setName, Object userKey);
+}
diff --git a/core/src/main/java/com/aerospike/mapper/tools/SetterParamTypeResolver.java b/core/src/main/java/com/aerospike/mapper/tools/SetterParamTypeResolver.java
new file mode 100644
index 0000000..d5074f2
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/SetterParamTypeResolver.java
@@ -0,0 +1,25 @@
+package com.aerospike.mapper.tools;
+
+import com.aerospike.mapper.tools.PropertyDefinition.SetterParamType;
+
+/**
+ * Resolves the {@link SetterParamType} for a setter's second parameter based on the parameter's fully-qualified type
+ * name. This decouples core from specific client libraries.
+ */
+@FunctionalInterface
+public interface SetterParamTypeResolver {
+
+    /**
+     * Determines if the given parameter type name corresponds to a Key or Value type in the client library used by this
+     * mapper.
+     *
+     * @param paramTypeName fully-qualified class name of the setter's second parameter
+     * @return the resolved {@link SetterParamType}, or {@link SetterParamType#NONE} if unrecognized
+     */
+    SetterParamType resolve(String paramTypeName);
+
+    /**
+     * Default resolver that does not recognize any client-specific parameter types.
+     */
+    SetterParamTypeResolver DEFAULT = paramTypeName -> SetterParamType.NONE;
+}
diff --git a/core/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java b/core/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java
new file mode 100644
index 0000000..2f5eda5
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java
@@ -0,0 +1,51 @@
+package com.aerospike.mapper.tools;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * 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,
+ * not A's.
+ */
+public class ThreadLocalKeySaver {
+
+    private static final ThreadLocal> threadLocalKeys = ThreadLocal.withInitial(ArrayDeque::new);
+
+    private ThreadLocalKeySaver() {
+    }
+
+    /**
+     * @param keyContext   the full key context (e.g. a legacy Key object or RecordKey)
+     * @param userKeyValue the user key value (e.g. key.userKey for legacy, or RecordKey.keyValue)
+     */
+    public static void save(Object keyContext, Object userKeyValue) {
+        threadLocalKeys.get().addLast(new Object[]{keyContext, userKeyValue});
+        LoadedObjectResolver.begin();
+    }
+
+    public static void clear() {
+        LoadedObjectResolver.end();
+        threadLocalKeys.get().removeLast();
+        if (threadLocalKeys.get().isEmpty()) {
+            threadLocalKeys.remove();
+        }
+    }
+
+    /** Returns the full key context stored for the current load (e.g. a legacy Key or RecordKey). */
+    public static Object getKeyContext() {
+        Deque keys = threadLocalKeys.get();
+        if (keys.isEmpty()) {
+            return null;
+        }
+        return keys.getLast()[0];
+    }
+
+    /** Returns the user-key value stored for the current load (e.g. a String or Integer). */
+    public static Object getUserKeyValue() {
+        Deque keys = threadLocalKeys.get();
+        if (keys.isEmpty()) {
+            return null;
+        }
+        return keys.getLast()[1];
+    }
+}
diff --git a/src/main/java/com/aerospike/mapper/tools/TypeMapper.java b/core/src/main/java/com/aerospike/mapper/tools/TypeMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/TypeMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/TypeMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/ValueType.java b/core/src/main/java/com/aerospike/mapper/tools/ValueType.java
similarity index 78%
rename from src/main/java/com/aerospike/mapper/tools/ValueType.java
rename to core/src/main/java/com/aerospike/mapper/tools/ValueType.java
index 6331f69..094ec59 100644
--- a/src/main/java/com/aerospike/mapper/tools/ValueType.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/ValueType.java
@@ -1,12 +1,12 @@
 package com.aerospike.mapper.tools;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.Key;
 import com.aerospike.mapper.annotations.AerospikeVersion;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObject;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObjectSetter;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredSetter;
 import com.aerospike.mapper.tools.utils.TypeUtils.AnnotatedType;
+import lombok.Getter;
 
 import javax.validation.constraints.NotNull;
 import java.lang.annotation.Annotation;
@@ -18,9 +18,12 @@
  * @author timfaulkes
  */
 public abstract class ValueType {
+    @Getter
     private int minimumVersion = 1;
+    @Getter
     private int maximumVersion = Integer.MAX_VALUE;
     private final TypeMapper mapper;
+    @Getter
     private final AnnotatedType annotatedType;
 
     public ValueType(@NotNull final TypeMapper mapper, final AnnotatedType annotatedType) {
@@ -36,23 +39,15 @@ public ValueType(@NotNull final TypeMapper mapper, final AnnotatedType annotated
 
     public abstract Annotation[] getAnnotations();
 
-    public int getMinimumVersion() {
-        return minimumVersion;
-    }
-
-    public int getMaximumVersion() {
-        return maximumVersion;
-    }
-
     protected void setVersion(AerospikeVersion version) {
         if (version.min() <= 0) {
-            throw new AerospikeException("Minimum version must be greater than or equal to 1, not " + version.min());
+            throw new AerospikeMapperException("Minimum version must be greater than or equal to 1, not " + version.min());
         }
         if (version.max() <= 0) {
-            throw new AerospikeException("Maximum version must be greater than or equal to 1, not " + version.max());
+            throw new AerospikeMapperException("Maximum version must be greater than or equal to 1, not " + version.max());
         }
         if (version.min() > version.max()) {
-            throw new AerospikeException("Maximum version must be greater than or equal to the minumum version, not " + version.max());
+            throw new AerospikeMapperException("Maximum version must be greater than or equal to the minumum version, not " + version.max());
         }
         this.maximumVersion = version.max();
         this.minimumVersion = version.min();
@@ -62,10 +57,6 @@ public TypeMapper getTypeMapper() {
         return this.mapper;
     }
 
-    public AnnotatedType getAnnotatedType() {
-        return annotatedType;
-    }
-
     public static class FieldValue extends ValueType {
         private final Field field;
 
@@ -91,7 +82,7 @@ public void set(final Object obj, final Object value) throws ReflectiveOperation
                     try {
                         field.set(obj, object);
                     } catch (IllegalArgumentException | IllegalAccessException e) {
-                        throw new AerospikeException(String.format("Could not set field %s on %s to %s. Error is %s (%s)", field, obj, value, e.getMessage(), e.getClass()));
+                        throw new AerospikeMapperException(String.format("Could not set field %s on %s to %s. Error is %s (%s)", field, obj, value, e.getMessage(), e.getClass()));
                     }
                 };
                 DeferredObjectSetter objectSetter = new DeferredObjectSetter(setter, (DeferredObject) value);
@@ -136,17 +127,18 @@ public Object get(Object obj) throws ReflectiveOperationException {
         @Override
         public void set(final Object obj, final Object value) throws ReflectiveOperationException {
             if (this.property.getSetter() == null) {
-                throw new AerospikeException("Lazy loading cannot be used on objects with a property key type and no annotated key setter method");
+                throw new AerospikeMapperException("Lazy loading cannot be used on objects with a property key type and no annotated key setter method");
             } else {
                 switch (this.property.getSetterParamType()) {
                     case KEY: {
-                        final Key key = ThreadLocalKeySaver.get();
+                        final Object key = ThreadLocalKeySaver.getKeyContext();
                         if (value instanceof DeferredObject) {
-                            DeferredSetter setter = object -> {
+                            DeferredSetter setter = resolved -> {
                                 try {
-                                    property.getSetter().invoke(obj, value, key);
+                                    property.getSetter().invoke(obj, resolved, key);
                                 } catch (ReflectiveOperationException e) {
-                                    throw new AerospikeException(String.format("Could not set field %s on %s to %s", property, obj, value));
+                                    throw new AerospikeMapperException(String.format("Could not set field %s on %s to" +
+                                        " %s", property, obj, resolved));
                                 }
                             };
                             DeferredObjectSetter objectSetter = new DeferredObjectSetter(setter, (DeferredObject) value);
@@ -158,30 +150,32 @@ public void set(final Object obj, final Object value) throws ReflectiveOperation
                     }
 
                     case VALUE: {
-                        final Key key = ThreadLocalKeySaver.get();
+                        final Object userKeyValue = ThreadLocalKeySaver.getUserKeyValue();
                         if (value instanceof DeferredObject) {
-                            DeferredSetter setter = object -> {
+                            DeferredSetter setter = resolved -> {
                                 try {
-                                    property.getSetter().invoke(obj, value, key.userKey);
+                                    property.getSetter().invoke(obj, resolved, userKeyValue);
                                 } catch (ReflectiveOperationException e) {
-                                    throw new AerospikeException(String.format("Could not set field %s on %s to %s", property, obj, value));
+                                    throw new AerospikeMapperException(String.format("Could not set field %s on %s to" +
+                                        " %s", property, obj, resolved));
                                 }
                             };
                             DeferredObjectSetter objectSetter = new DeferredObjectSetter(setter, (DeferredObject) value);
                             DeferredObjectLoader.add(objectSetter);
                         } else {
-                            this.property.getSetter().invoke(obj, value, key.userKey);
+                            this.property.getSetter().invoke(obj, value, ThreadLocalKeySaver.getUserKeyValue());
                         }
                         break;
                     }
 
                     default:
                         if (value instanceof DeferredObject) {
-                            DeferredSetter setter = object -> {
+                            DeferredSetter setter = resolved -> {
                                 try {
-                                    property.getSetter().invoke(obj, value);
+                                    property.getSetter().invoke(obj, resolved);
                                 } catch (ReflectiveOperationException e) {
-                                    throw new AerospikeException(String.format("Could not set field %s on %s to %s", property, obj, value));
+                                    throw new AerospikeMapperException(String.format("Could not set field %s on %s to" +
+                                        " %s", property, obj, resolved));
                                 }
                             };
                             DeferredObjectSetter objectSetter = new DeferredObjectSetter(setter, (DeferredObject) value);
diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java
similarity index 56%
rename from src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java
rename to core/src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java
index f067a9c..f0ae2eb 100644
--- a/src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java
@@ -1,104 +1,38 @@
 package com.aerospike.mapper.tools.configuration;
 
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
 
-import com.aerospike.client.AerospikeException;
-
+@Getter
+@Setter
 public class BinConfig {
     private String name;
     private String field;
     private Boolean useAccessors;
     private String getter;
     private String setter;
+    @Getter(AccessLevel.NONE)
     private Boolean exclude;
     private Integer ordinal;
     private EmbedConfig embed;
     private ReferenceConfig reference;
+    @Getter(AccessLevel.NONE)
     private Boolean generation;
 
-    public String getName() {
-        return name;
-    }
-
-    public String getField() {
-        return field;
-    }
-
-    public Boolean getUseAccessors() {
-        return useAccessors;
-    }
-
-    public String getGetter() {
-        return getter;
-    }
-
-    public String getSetter() {
-        return setter;
-    }
-
     public Boolean isExclude() {
         return exclude;
     }
 
-    public Integer getOrdinal() {
-        return ordinal;
-    }
-
-    public EmbedConfig getEmbed() {
-        return embed;
-    }
-
-    public ReferenceConfig getReference() {
-        return reference;
-    }
-
     public Boolean isGeneration() {
         return generation;
     }
 
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public void setField(String field) {
-        this.field = field;
-    }
-
-    public void setUseAccessors(Boolean useAccessors) {
-        this.useAccessors = useAccessors;
-    }
-
-    public void setGetter(String getter) {
-        this.getter = getter;
-    }
-
-    public void setSetter(String setter) {
-        this.setter = setter;
-    }
-
-    public void setExclude(Boolean exclude) {
-        this.exclude = exclude;
-    }
-
-    public void setOrdinal(Integer ordinal) {
-        this.ordinal = ordinal;
-    }
-
-    public void setEmbed(EmbedConfig embed) {
-        this.embed = embed;
-    }
-
-    public void setReference(ReferenceConfig reference) {
-        this.reference = reference;
-    }
-
-    public void setGeneration(Boolean generation) {
-        this.generation = generation;
-    }
-
     public void validate(String className) {
         if (StringUtils.isBlank(this.name) && StringUtils.isBlank(this.field)) {
-            throw new AerospikeException("Configuration for class " + className + " defines a bin which contains neither a name nor a field");
+            throw new AerospikeMapperException("Configuration for class " + className + " defines a bin which contains neither a name nor a field");
         }
     }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java
similarity index 86%
rename from src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java
rename to core/src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java
index 4075294..b540835 100644
--- a/src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java
@@ -5,12 +5,15 @@
 
 import javax.validation.constraints.NotNull;
 
-import com.aerospike.client.AerospikeException;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.annotations.AerospikeEmbed;
 import com.aerospike.mapper.annotations.AerospikeReference;
 import com.aerospike.mapper.tools.ConfigurationUtils;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.Setter;
 
+@Getter
 public class ClassConfig {
     @JsonProperty(value = "class")
     private String className;
@@ -23,7 +26,9 @@ public class ClassConfig {
     private Boolean durableDelete;
     private KeyConfig key;
     private String shortName;
+    @Setter
     private String factoryClass;
+    @Setter
     private String factoryMethod;
     private final List bins;
 
@@ -31,66 +36,7 @@ public ClassConfig() {
         bins = new ArrayList<>();
     }
 
-    public String getClassName() {
-        return className;
-    }
-
-    public String getNamespace() {
-        return namespace;
-    }
-
-    public String getSet() {
-        return set;
-    }
-
-    public Integer getTtl() {
-        return ttl;
-    }
-
-    public Integer getVersion() {
-        return version;
-    }
-
-    public Boolean getSendKey() {
-        return sendKey;
-    }
-
-    public Boolean getMapAll() {
-        return mapAll;
-    }
-
-    public Boolean getDurableDelete() {
-        return durableDelete;
-    }
-
-    public String getShortName() {
-        return shortName;
-    }
-
-    public KeyConfig getKey() {
-        return key;
-    }
-
-    public List getBins() {
-        return bins;
-    }
-
-    public String getFactoryClass() {
-        return factoryClass;
-    }
-
-    public void setFactoryClass(String factoryClass) {
-        this.factoryClass = factoryClass;
-    }
-
-    public String getFactoryMethod() {
-        return factoryMethod;
-    }
-
-    public void setFactoryMethod(String factoryMethod) {
-        this.factoryMethod = factoryMethod;
-    }
-
+    @SuppressWarnings("unused")
     public BinConfig getBinByName(@NotNull String name) {
         for (BinConfig thisBin : bins) {
             if (name.equals(thisBin.getName())) {
@@ -163,7 +109,8 @@ private void setKey(KeyConfig key) {
     private void setShortName(String shortName) {
         this.shortName = shortName;
     }
-    
+
+    @SuppressWarnings("unused")
     public static class Builder {
         private final Class clazz;
         private final ClassConfig classConfig;
@@ -175,7 +122,7 @@ public Builder(final Class clazz) {
         
         private void validateFieldExists(String fieldName) {
             if (!ConfigurationUtils.validateFieldOnClass(this.clazz, fieldName)) {
-                throw new AerospikeException(String.format("Field %s does not exist on class %s or its superclasses", fieldName, this.clazz));
+                throw new AerospikeMapperException(String.format("Field %s does not exist on class %s or its superclasses", fieldName, this.clazz));
             }
         }
 
@@ -219,11 +166,6 @@ public Builder withDurableDelete(boolean durableDelete) {
             return this;
         }
 
-        public Builder withShortName(boolean sendKey) {
-            this.classConfig.setSendKey(sendKey);
-            return this;
-        }
-        
         public Builder withFactoryClassAndMethod(@NotNull Class factoryClass, @NotNull String factoryMethod) {
             this.classConfig.setFactoryClass(factoryClass.getName());
             this.classConfig.setFactoryMethod(factoryMethod);
@@ -280,6 +222,7 @@ public ClassConfig build() {
         }
     }
 
+    @SuppressWarnings("unused")
     public static class AeroBinConfig {
         private final Builder builder;
         private final BinConfig binConfig;
diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java
similarity index 81%
rename from src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java
rename to core/src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java
index 8e3a0cd..01d5b05 100644
--- a/src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/Configuration.java
@@ -1,8 +1,11 @@
 package com.aerospike.mapper.tools.configuration;
 
+import lombok.Getter;
+
 import java.util.ArrayList;
 import java.util.List;
 
+@Getter
 public class Configuration {
     private final List classes;
 
@@ -10,10 +13,6 @@ public Configuration() {
         this.classes = new ArrayList<>();
     }
 
-    public List getClasses() {
-        return classes;
-    }
-    
     public void add(ClassConfig config) {
         this.classes.add(config);
     }
diff --git a/core/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java
new file mode 100644
index 0000000..863228b
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java
@@ -0,0 +1,13 @@
+package com.aerospike.mapper.tools.configuration;
+
+import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class EmbedConfig {
+    private EmbedType type;
+    private EmbedType elementType;
+    private Boolean saveKey;
+}
diff --git a/core/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java
new file mode 100644
index 0000000..8a6c630
--- /dev/null
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java
@@ -0,0 +1,22 @@
+package com.aerospike.mapper.tools.configuration;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+
+@Getter
+@Setter
+public class KeyConfig {
+    private String field;
+    private String getter;
+    private String setter;
+    private Boolean storeAsBin;
+
+    public boolean isGetter(String methodName) {
+        return (!StringUtils.isBlank(this.getter)) && this.getter.equals(methodName);
+    }
+
+    public boolean isSetter(String methodName) {
+        return (!StringUtils.isBlank(this.setter)) && this.setter.equals(methodName);
+    }
+}
diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java b/core/src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java
similarity index 70%
rename from src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java
rename to core/src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java
index da4365f..4b51290 100644
--- a/src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/configuration/ReferenceConfig.java
@@ -1,7 +1,9 @@
 package com.aerospike.mapper.tools.configuration;
 
 import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType;
+import lombok.Getter;
 
+@Getter
 public class ReferenceConfig {
     private ReferenceType type;
     private Boolean lazy;
@@ -15,16 +17,4 @@ public ReferenceConfig(ReferenceType type, boolean lazy, boolean batchLoad) {
         this.lazy = lazy;
         this.batchLoad = batchLoad;
     }
-
-    public ReferenceType getType() {
-        return type;
-    }
-
-    public Boolean getLazy() {
-        return lazy;
-    }
-
-    public Boolean getBatchLoad() {
-        return batchLoad;
-    }
 }
diff --git a/src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java b/core/src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java
similarity index 57%
rename from src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java
rename to core/src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java
index 082e7cd..4cd709d 100644
--- a/src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/converters/MappingConverter.java
@@ -1,17 +1,15 @@
 package com.aerospike.mapper.tools.converters;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.IAerospikeClient;
-import com.aerospike.client.Key;
-import com.aerospike.client.Record;
-import com.aerospike.client.Value;
-import com.aerospike.client.policy.BatchPolicy;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.ClassCache;
 import com.aerospike.mapper.tools.ClassCacheEntry;
 import com.aerospike.mapper.tools.DeferredObjectLoader;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObjectSetter;
-import com.aerospike.mapper.tools.IBaseAeroMapper;
+import com.aerospike.mapper.tools.IObjectMapper;
+import com.aerospike.mapper.tools.IRecordConverter;
 import com.aerospike.mapper.tools.LoadedObjectResolver;
+import com.aerospike.mapper.tools.RecordKey;
+import com.aerospike.mapper.tools.RecordLoader;
 import com.aerospike.mapper.tools.ThreadLocalKeySaver;
 import com.aerospike.mapper.tools.TypeMapper;
 import com.aerospike.mapper.tools.utils.MapperUtils;
@@ -19,18 +17,19 @@
 
 import javax.validation.constraints.NotNull;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
-public class MappingConverter {
+public class MappingConverter implements IRecordConverter {
 
-    private final IBaseAeroMapper mapper;
-    private final IAerospikeClient aerospikeClient;
+    private final IObjectMapper mapper;
+    private final RecordLoader recordLoader;
 
-    public MappingConverter(IBaseAeroMapper mapper, IAerospikeClient aerospikeClient) {
+    public MappingConverter(IObjectMapper mapper, RecordLoader recordLoader) {
         this.mapper = mapper;
-        this.aerospikeClient = aerospikeClient;
+        this.recordLoader = recordLoader;
     }
 
     /**
@@ -69,38 +68,24 @@ public  T translateFromAerospike(@NotNull Object obj, @NotNull Class expec
     // --------------------------------------------------------------------------------------------------
 
     /**
-     * Given a record loaded from Aerospike and a class type, attempt to convert the record to
-     * an instance of the passed class.
-     *
-     * @param clazz  The class type to convert the Aerospike record to.
-     * @param record The Aerospike record to convert.
-     * @return A virtual list.
-     */
-    public  T convertToObject(Class clazz, Key key, Record record) {
-        return convertToObject(clazz, key, record, null);
-    }
-
-    /**
-     * Given a record loaded from Aerospike and a class type, attempt to convert the record to
+     * Given a map of records loaded from Aerospike and a class type, attempt to convert the records to
      * an instance of the passed class.
      *
      * @param clazz  The class type to convert the Aerospike record to.
-     * @param record The Aerospike record to convert.
-     * @param entry  The entry that holds information on how to store the provided class.
+     * @param record The Aerospike records to convert.
      * @return A virtual list.
      */
-    public  T convertToObject(Class clazz, Key key, Record record, ClassCacheEntry entry) {
-        return this.convertToObject(clazz, key, record, entry, true);
+    @Override
+    public  T convertToObject(Class clazz, Map record) {
+        return this.convertToObject(clazz, record, true);
     }
 
     /**
-     * This method should not be used, it is public only to allow mappers to see it.
+     * This method should not be used; it is public only to allow mappers to see it.
      */
-    public  T convertToObject(Class clazz, Key key, Record record, ClassCacheEntry entry, boolean resolveDependencies) {
-        if (entry == null) {
-            entry = ClassCache.getInstance().loadClass(clazz, mapper);
-        }
-        T result = entry.constructAndHydrate(key, record);
+    public  T convertToObject(Class clazz, Map record, boolean resolveDependencies) {
+        ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, mapper);
+        T result = entry.constructAndHydrate(record);
         if (resolveDependencies) {
             resolveDependencies(entry);
         }
@@ -114,44 +99,31 @@ public  T convertToObject(Class clazz, Key key, Record record, ClassCacheE
      * @param clazz  The class type to convert the Aerospike record to.
      * @param record The Aerospike records to convert.
      * @return A virtual list.
-     * @throws AerospikeException an AerospikeException will be thrown in case of an encountering a ReflectiveOperationException.
+     * @throws AerospikeMapperException an AerospikeMapperException will be thrown in case of an encountering
+     * a ReflectiveOperationException.
      */
+    @Override
     public  T convertToObject(Class clazz, List record) {
         return this.convertToObject(clazz, record, true);
     }
 
     /**
-     * This method should not be used, it is public only to allow mappers to see it.
+     * This method should not be used; it is public only to allow mappers to see it.
      */
     public  T convertToObject(Class clazz, List record, boolean resolveDependencies) {
         try {
             ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, mapper);
-            T result;
-            result = clazz.getConstructor().newInstance();
+            T result = clazz.getConstructor().newInstance();
             entry.hydrateFromList(record, result);
             if (resolveDependencies) {
                 resolveDependencies(entry);
             }
             return result;
         } catch (ReflectiveOperationException e) {
-            throw new AerospikeException(e);
+            throw new AerospikeMapperException(e);
         }
     }
 
-    /**
-     * Given a map of records loaded from Aerospike and a class type, attempt to convert the records to
-     * an instance of the passed class.
-     *
-     * @param clazz  The class type to convert the Aerospike record to.
-     * @param record The Aerospike records to convert.
-     * @return A virtual list.
-     * @throws AerospikeException an AerospikeException will be thrown in case of an encountering a ReflectiveOperationException.
-     */
-    public  T convertToObject(Class clazz, Map record) {
-        ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, mapper);
-        return entry.constructAndHydrate(record);
-    }
-
     /**
      * Given an instance of a class (of any type), convert its properties to a list
      *
@@ -177,23 +149,19 @@ public  Map convertToMap(@NotNull T instance) {
         return entry.getMap(instance, false);
     }
 
-    private Key createKey(ClassCacheEntry entry, DeferredObjectLoader.DeferredObject deferredObject) {
+    private RecordKey toRecordKey(ClassCacheEntry entry, DeferredObjectLoader.DeferredObject deferredObject) {
         if (deferredObject.isDigest()) {
-            return new Key(entry.getNamespace(), (byte[]) deferredObject.getKey(), entry.getSetName(), null);
+            return new RecordKey(entry.getNamespace(), entry.getSetName(), (byte[]) deferredObject.getKey());
         } else {
-            return new Key(entry.getNamespace(), entry.getSetName(), Value.get(entry.translateKeyToAerospikeKey(deferredObject.getKey())));
+            Object translatedKey = entry.translateKeyToAerospikeKey(deferredObject.getKey());
+            return new RecordKey(entry.getNamespace(), entry.getSetName(), translatedKey);
         }
     }
 
     /**
-     * If an object refers to other objects (eg A has a list of B via references), then reading the object will populate the
-     * ids. If configured to do so, these objects can be loaded via a batch load and populated back into the references which
-     * contain them. This method performs this batch load, translating the records to objects and mapping them back to the
-     * references.
-     * 

- * These loaded child objects can themselves have other references to other objects, so we iterate through this until - * the list of deferred objects is empty. The deferred objects are stored in a

ThreadLocalData
 list, so are thread safe
-     * @param parentEntity - the ClassCacheEntry of the parent entity. This is used to get the batch policy to use.
+     * If an object refers to other objects via @AerospikeReference, reading the object will populate the ids.
+     * This method batch-loads those referenced objects and wires them back into the parent.
+     * @param parentEntity the ClassCacheEntry of the parent entity (may be null).
      */
     @SuppressWarnings("unchecked")
     public void resolveDependencies(ClassCacheEntry parentEntity) {
@@ -203,56 +171,52 @@ public void resolveDependencies(ClassCacheEntry parentEntity) {
             return;
         }
 
-        BatchPolicy batchPolicy = parentEntity == null ? aerospikeClient.getBatchPolicyDefault() : parentEntity.getBatchPolicy();
-        BatchPolicy batchPolicyClone = new BatchPolicy(batchPolicy);
-
         while (!deferredObjects.isEmpty()) {
-            List keyList = new ArrayList<>();
+            List recordKeyList = new ArrayList<>();
             List> classCacheEntryList = new ArrayList<>();
 
-            // Resolve any objects which have been seen before
+            // Resolve any objects which have been loaded before (cache hit)
             for (Iterator iterator = deferredObjects.iterator(); iterator.hasNext(); ) {
                 DeferredObjectSetter thisObjectSetter = iterator.next();
                 DeferredObjectLoader.DeferredObject deferredObject = thisObjectSetter.getObject();
                 Class clazz = deferredObject.getType();
                 ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, mapper);
 
-                Key aKey = createKey(entry, deferredObject);
-                Object result = LoadedObjectResolver.get(aKey);
+                RecordKey rk = toRecordKey(entry, deferredObject);
+                Object result = LoadedObjectResolver.get(rk);
                 if (result != null) {
                     thisObjectSetter.getSetter().setValue(result);
                     iterator.remove();
                 } else {
-                    keyList.add(aKey);
+                    recordKeyList.add(rk);
                     classCacheEntryList.add(entry);
                 }
             }
 
-            int size = keyList.size();
+            int size = recordKeyList.size();
             if (size > 0) {
-                Key[] keys = keyList.toArray(new Key[0]);
-
-                // Load the data
-                if (keys.length <= 2) {
-                    // Just single-thread these keys for speed
-                    batchPolicyClone.maxConcurrentThreads = 1;
-                } else {
-                    batchPolicyClone.maxConcurrentThreads = batchPolicy.maxConcurrentThreads;
-                }
-                Record[] records = aerospikeClient.get(batchPolicyClone, keys);
+                List> maps = recordLoader.getBatchRecords(recordKeyList);
 
                 for (int i = 0; i < size; i++) {
                     DeferredObjectLoader.DeferredObjectSetter thisObjectSetter = deferredObjects.get(i);
+                    ClassCacheEntry entry = classCacheEntryList.get(i);
+                    Map bins = maps.get(i);
+
+                    // Inject user key into map if key is not stored as a bin (sendKey scenario)
+                    RecordKey rk = recordKeyList.get(i);
+                    if (bins != null && !entry.isKeyFieldStoredAsBin()) {
+                        String keyFieldName = entry.getKeyFieldName();
+                        if (keyFieldName != null && rk.keyValue != null) {
+                            bins = new HashMap<>(bins);
+                            bins.put(keyFieldName, rk.keyValue);
+                        }
+                    }
+
                     try {
-                        ThreadLocalKeySaver.save(keys[i]);
-                        Object obj = convertToObject(
-                                (Class) thisObjectSetter.getObject().getType(),
-                                keys[i],
-                                records[i],
-                                (ClassCacheEntry) classCacheEntryList.get(i),
-                                false);
-                        Object result = records[i] == null ? null : obj;
-                        thisObjectSetter.getSetter().setValue(result);
+                        ThreadLocalKeySaver.save(rk, rk.keyValue);
+                        Object obj = bins == null ? null : convertToObject(
+                                (Class) thisObjectSetter.getObject().getType(), bins, false);
+                        thisObjectSetter.getSetter().setValue(obj);
                     } finally {
                         ThreadLocalKeySaver.clear();
                     }
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ArrayMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ArrayMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ArrayMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ArrayMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/BigDecimalMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/BigDecimalMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/BigDecimalMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/BigDecimalMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/BigIntegerMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/BigIntegerMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/BigIntegerMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/BigIntegerMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java
similarity index 63%
rename from src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java
index 55958d7..bf3de76 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/BooleanMapper.java
@@ -2,19 +2,11 @@
 
 import com.aerospike.mapper.tools.TypeMapper;
 
-import static com.aerospike.client.Value.UseBoolBin;
-
 public class BooleanMapper extends TypeMapper {
 
     @Override
     public Object toAerospikeFormat(Object value) {
-        if (value == null) {
-            return null;
-        }
-        if (UseBoolBin) {
-            return value;
-        }
-        return ((Boolean) value) ? 1 : 0;
+        return value;
     }
 
     @Override
@@ -22,9 +14,10 @@ public Object fromAerospikeFormat(Object value) {
         if (value == null) {
             return null;
         }
-        if (UseBoolBin) {
+        if (value instanceof Boolean) {
             return value;
         }
+        // Backward compat: boolean stored as integer (0/1) on older Aerospike servers
         return !Long.valueOf(0).equals(value);
     }
 }
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ByteMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ByteMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ByteMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ByteMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/CharacterMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/CharacterMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/CharacterMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/CharacterMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/DateMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/DateMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/DateMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/DateMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/DefaultMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/DefaultMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/DefaultMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/DefaultMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/DoubleMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/DoubleMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/DoubleMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/DoubleMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java
similarity index 85%
rename from src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java
index 6bbfcb6..7188034 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/EnumMapper.java
@@ -1,6 +1,6 @@
 package com.aerospike.mapper.tools.mappers;
 
-import com.aerospike.client.AerospikeException;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.TypeMapper;
 
 import java.lang.reflect.Field;
@@ -72,10 +72,10 @@ public Object fromAerospikeFormat(Object value) {
                 }
             }
         }
-        throw new AerospikeException(String.format("Enum value of \"%s\" not found in type %s", stringValue, clazz));
+        throw new AerospikeMapperException(String.format("Enum value of \"%s\" not found in type %s", stringValue, clazz));
     }
 
-    private AerospikeException toAerospikeException(Exception e) {
-        return new AerospikeException("Cannot Map requested enum, issue with the requested enumField.", e);
+    private AerospikeMapperException toAerospikeException(Exception e) {
+        return new AerospikeMapperException("Cannot Map requested enum, issue with the requested enumField.", e);
     }
 }
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/FloatMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/FloatMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/FloatMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/FloatMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/InstantMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/InstantMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/InstantMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/InstantMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/IntMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/IntMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/IntMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/IntMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java
similarity index 92%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java
index 43e037b..8d888bd 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java
@@ -1,6 +1,6 @@
 package com.aerospike.mapper.tools.mappers;
 
-import com.aerospike.client.AerospikeException;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
 import com.aerospike.mapper.tools.*;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObject;
@@ -14,24 +14,19 @@
 
 public class ListMapper extends TypeMapper {
 
-    @SuppressWarnings("unused")
-    private final Class referencedClass;
     private final Class instanceClass;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
     private final boolean supportedWithoutTranslation;
     private final TypeMapper instanceClassMapper;
     private final EmbedType embedType;
     private final ClassCacheEntry subTypeEntry;
-    private final boolean saveKey;
     private final boolean allowBatchLoad;
 
-    public ListMapper(final Class clazz, final Class instanceClass, final TypeMapper instanceClassMapper, final IBaseAeroMapper mapper, final EmbedType embedType, final boolean saveKey, boolean allowBatchLoad) {
-        this.referencedClass = clazz;
+    public ListMapper(final Class clazz, final Class instanceClass, final TypeMapper instanceClassMapper, final IObjectMapper mapper, final EmbedType embedType, final boolean saveKey, boolean allowBatchLoad) {
         this.mapper = mapper;
         this.instanceClass = instanceClass;
         this.supportedWithoutTranslation = TypeUtils.isAerospikeNativeType(instanceClass);
         this.instanceClassMapper = instanceClassMapper;
-        this.saveKey = saveKey;
         this.allowBatchLoad = allowBatchLoad;
 
         if (embedType == EmbedType.DEFAULT) {
@@ -42,7 +37,7 @@ public ListMapper(final Class clazz, final Class instanceClass, final Type
         if (this.embedType == EmbedType.MAP && (instanceClassMapper == null || (!ObjectMapper.class.isAssignableFrom(instanceClassMapper.getClass())))) {
             subTypeEntry = null;
             // TODO: Should this throw an exception or just change the embedType back to LIST?
-            throw new AerospikeException("Annotations embedding lists of objects can only map those objects to maps instead of lists if the object is an AerospikeRecord on instance of class " + clazz.getSimpleName());
+            throw new AerospikeMapperException("Annotations embedding lists of objects can only map those objects to maps instead of lists if the object is an AerospikeRecord on instance of class " + clazz.getSimpleName());
         } else {
             if (instanceClass != null) {
                 subTypeEntry = ClassCache.getInstance().loadClass(instanceClass, mapper);
@@ -161,7 +156,7 @@ public Object fromAerospikeFormat(Object value) {
         List results = new ArrayList<>();
         if (embedType == null || embedType == EmbedType.LIST) {
             List list = (List) value;
-            if (list.size() == 0 || this.supportedWithoutTranslation) {
+            if (list.isEmpty() || this.supportedWithoutTranslation) {
                 return value;
             }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/LocalDateMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateTimeMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateTimeMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/LocalDateTimeMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/LocalDateTimeMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/LocalTimeMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/LocalTimeMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/LocalTimeMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/LocalTimeMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/LongMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/LongMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/LongMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/LongMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java
similarity index 85%
rename from src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java
index e8e48d6..17452f5 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/MapMapper.java
@@ -12,25 +12,16 @@
 
 public class MapMapper extends TypeMapper {
 
-    @SuppressWarnings("unused")
-    private final Class referencedClass;
-    @SuppressWarnings("unused")
-    private final Class itemClass;
-    @SuppressWarnings("unused")
-    private final Class keyClass;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
     private final boolean supportedWithoutTranslation;
     private final TypeMapper itemMapper;
     private final TypeMapper keyMapper;
 
     public MapMapper(final Class clazz, final Class keyClass, final Class itemClass,
-                     final TypeMapper keyMapper, final TypeMapper itemMapper, final IBaseAeroMapper mapper) {
-        this.referencedClass = clazz;
+                     final TypeMapper keyMapper, final TypeMapper itemMapper, final IObjectMapper mapper) {
         this.mapper = mapper;
         this.keyMapper = keyMapper;
-        this.keyClass = keyClass;
         this.itemMapper = itemMapper;
-        this.itemClass = itemClass;
         this.supportedWithoutTranslation = TypeUtils.isAerospikeNativeType(itemClass) && TypeUtils.isAerospikeNativeType(keyClass);
     }
 
@@ -40,7 +31,7 @@ public Object toAerospikeFormat(Object value) {
             return null;
         }
         Map map = (Map) value;
-        if (map.size() == 0 || this.supportedWithoutTranslation) {
+        if (map.isEmpty() || this.supportedWithoutTranslation) {
             return value;
         }
 
@@ -61,7 +52,7 @@ public Object fromAerospikeFormat(Object value) {
             return null;
         }
         Map map = (Map) value;
-        if (map.size() == 0 || this.supportedWithoutTranslation) {
+        if (map.isEmpty() || this.supportedWithoutTranslation) {
             return value;
         }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java
similarity index 86%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java
index 35876fe..a9bb8ff 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java
@@ -1,10 +1,10 @@
 package com.aerospike.mapper.tools.mappers;
 
-import com.aerospike.client.AerospikeException;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
 import com.aerospike.mapper.tools.ClassCache;
 import com.aerospike.mapper.tools.ClassCacheEntry;
-import com.aerospike.mapper.tools.IBaseAeroMapper;
+import com.aerospike.mapper.tools.IObjectMapper;
 
 import java.util.List;
 import java.util.Map;
@@ -12,11 +12,11 @@
 public class ObjectEmbedMapper extends ObjectMapper {
 
     private final Class referencedClass;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
     private final EmbedType type;
     private final boolean skipKey;
 
-    public ObjectEmbedMapper(final Class clazz, final EmbedType type, final IBaseAeroMapper mapper, boolean skipKey) {
+    public ObjectEmbedMapper(final Class clazz, final EmbedType type, final IObjectMapper mapper, boolean skipKey) {
         this.referencedClass = clazz;
         this.mapper = mapper;
         this.type = type;
@@ -43,7 +43,7 @@ public Object toAerospikeFormat(Object value) {
             case DEFAULT:
                 return entry.getMap(value, needsType);
             default:
-                throw new AerospikeException("Unspecified EmbedType");
+                throw new AerospikeMapperException("Unspecified EmbedType");
         }
     }
 
@@ -66,10 +66,10 @@ public Object fromAerospikeFormat(Object value) {
                 case DEFAULT:
                     return entry.constructAndHydrate((Map) value);
                 default:
-                    throw new AerospikeException("Unspecified EmbedType");
+                    throw new AerospikeMapperException("Unspecified EmbedType");
             }
         } catch (Exception e) {
-            throw new AerospikeException(e);
+            throw new AerospikeMapperException(e);
         }
     }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ObjectMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ObjectMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java
similarity index 68%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java
index 9461186..8c99aa5 100644
--- a/src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/mappers/ObjectReferenceMapper.java
@@ -5,10 +5,8 @@
 import java.util.List;
 import java.util.Map;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.Value;
-import com.aerospike.client.util.Crypto;
 import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.*;
 import com.aerospike.mapper.tools.DeferredObjectLoader.DeferredObject;
 
@@ -16,13 +14,13 @@ public class ObjectReferenceMapper extends ObjectMapper {
 
     // Package visibility
     private final ClassCacheEntry referencedClass;
-    private final IBaseAeroMapper mapper;
+    private final IObjectMapper mapper;
     private final boolean lazy;
     private final boolean allowBatch;
     private final ReferenceType type;
 
     public ObjectReferenceMapper(ClassCacheEntry entry, boolean lazy, boolean allowBatch,
-                                 ReferenceType type, IBaseAeroMapper mapper) {
+                                 ReferenceType type, IObjectMapper mapper) {
         this.referencedClass = entry;
         this.mapper = mapper;
         this.lazy = lazy;
@@ -30,7 +28,7 @@ public ObjectReferenceMapper(ClassCacheEntry entry, boolean lazy, boolean all
         this.allowBatch = allowBatch;
 
         if (ReferenceType.DIGEST.equals(this.type) && this.lazy) {
-            throw new AerospikeException("An object reference to a " + entry.getClass().getSimpleName()
+            throw new AerospikeMapperException("An object reference to a " + entry.getClass().getSimpleName()
                     + " cannot be both lazy and map to a digest");
         }
     }
@@ -55,7 +53,7 @@ public Object toAerospikeFormat(Object value, boolean isUnknownType, boolean isS
         }
         Object key = classToUse.getKey(value);
         if (ReferenceType.DIGEST.equals(type)) {
-            key = Crypto.computeDigest(classToUse.getSetName(), Value.get(key));
+            key = mapper.getRecordLoader().computeDigest(classToUse.getSetName(), key);
         }
         if (isSubclassOfKnownType || isUnknownType) {
             // Need to put the class name in the key so we can recreate the class
@@ -101,9 +99,32 @@ public Object fromAerospikeFormat(Object value) {
         } else if (allowBatch) {
             return new DeferredObject(key, classToUse.getUnderlyingClass(), ReferenceType.DIGEST.equals(type));
         } else if (ReferenceType.DIGEST.equals(type)) {
-            return mapper.asMapper().readFromDigest(classToUse.getUnderlyingClass(), (byte[]) key, false);
+            return resolveFromDigest(classToUse, (byte[]) key);
         } else {
-            return mapper.asMapper().read(classToUse.getUnderlyingClass(), key, false);
+            return resolveByKey(classToUse, key);
         }
     }
+
+    private Object resolveFromDigest(ClassCacheEntry entry, byte[] digest) {
+        Map bins = mapper.getRecordLoader().getRecordByDigest(
+                entry.getNamespace(), entry.getSetName(), digest);
+        if (bins == null) return null;
+        return mapper.getMappingConverter().convertToObject(entry.getUnderlyingClass(), bins);
+    }
+
+    private Object resolveByKey(ClassCacheEntry entry, Object keyValue) {
+        Object translatedKey = entry.translateKeyToAerospikeKey(keyValue);
+        Map bins = mapper.getRecordLoader().getRecord(
+                entry.getNamespace(), entry.getSetName(), translatedKey);
+        if (bins == null) return null;
+        // Inject user key into map if key is not stored as a bin (sendKey scenario)
+        if (!entry.isKeyFieldStoredAsBin()) {
+            String keyFieldName = entry.getKeyFieldName();
+            if (keyFieldName != null) {
+                bins = new HashMap<>(bins);
+                bins.put(keyFieldName, translatedKey);
+            }
+        }
+        return mapper.getMappingConverter().convertToObject(entry.getUnderlyingClass(), bins);
+    }
 }
diff --git a/src/main/java/com/aerospike/mapper/tools/mappers/ShortMapper.java b/core/src/main/java/com/aerospike/mapper/tools/mappers/ShortMapper.java
similarity index 100%
rename from src/main/java/com/aerospike/mapper/tools/mappers/ShortMapper.java
rename to core/src/main/java/com/aerospike/mapper/tools/mappers/ShortMapper.java
diff --git a/src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java b/core/src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java
similarity index 66%
rename from src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java
rename to core/src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java
index ed1d005..39be501 100644
--- a/src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/utils/MapperUtils.java
@@ -1,9 +1,10 @@
 package com.aerospike.mapper.tools.utils;
 
-import com.aerospike.client.AerospikeException;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.tools.ClassCache;
 import com.aerospike.mapper.tools.ClassCacheEntry;
-import com.aerospike.mapper.tools.IBaseAeroMapper;
+import com.aerospike.mapper.tools.IObjectMapper;
+
 import org.apache.commons.lang3.StringUtils;
 
 public class MapperUtils {
@@ -11,14 +12,14 @@ public class MapperUtils {
     private MapperUtils() {
     }
 
-    public static  ClassCacheEntry getEntryAndValidateNamespace(Class clazz, IBaseAeroMapper mapper) {
+    public static  ClassCacheEntry getEntryAndValidateNamespace(Class clazz, IObjectMapper mapper) {
         ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, mapper);
         String namespace = null;
         if (entry != null) {
             namespace = entry.getNamespace();
         }
         if (StringUtils.isBlank(namespace)) {
-            throw new AerospikeException("Namespace not specified to perform database operation on a record of type " + clazz.getName());
+            throw new AerospikeMapperException("Namespace not specified to perform database operation on a record of type " + clazz.getName());
         }
         return entry;
     }
diff --git a/src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java b/core/src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java
similarity index 94%
rename from src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java
rename to core/src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java
index 1930384..59caf9c 100644
--- a/src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/utils/ParserUtils.java
@@ -1,12 +1,11 @@
 package com.aerospike.mapper.tools.utils;
 
+import lombok.Getter;
+
 public class ParserUtils {
+    @Getter
     private static final ParserUtils instance = new ParserUtils();
 
-    public static ParserUtils getInstance() {
-        return instance;
-    }
-
     private ParserUtils() {
     }
 
diff --git a/src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java b/core/src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java
similarity index 90%
rename from src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java
rename to core/src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java
index b5d84fb..f2e4489 100644
--- a/src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java
+++ b/core/src/main/java/com/aerospike/mapper/tools/utils/TypeUtils.java
@@ -17,9 +17,7 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import com.aerospike.client.AerospikeException;
-import com.aerospike.client.cdt.ListReturnType;
-import com.aerospike.client.cdt.MapReturnType;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
 import com.aerospike.mapper.annotations.AerospikeEmbed;
 import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
 import com.aerospike.mapper.annotations.AerospikeEnum;
@@ -27,7 +25,7 @@
 import com.aerospike.mapper.annotations.AerospikeReference;
 import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType;
 import com.aerospike.mapper.tools.ClassCache;
-import com.aerospike.mapper.tools.IBaseAeroMapper;
+import com.aerospike.mapper.tools.IObjectMapper;
 import com.aerospike.mapper.tools.TypeMapper;
 import com.aerospike.mapper.tools.configuration.BinConfig;
 import com.aerospike.mapper.tools.configuration.ClassConfig;
@@ -53,7 +51,6 @@
 import com.aerospike.mapper.tools.mappers.ObjectEmbedMapper;
 import com.aerospike.mapper.tools.mappers.ObjectReferenceMapper;
 import com.aerospike.mapper.tools.mappers.ShortMapper;
-import com.aerospike.mapper.tools.virtuallist.ReturnType;
 
 public class TypeUtils {
 
@@ -78,7 +75,7 @@ public static TypeMapper addTypeMapper(Class clazz, TypeMapper mapper) {
     }
 
     @SuppressWarnings("unchecked")
-    private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAeroMapper mapper, boolean isForSubType) {
+    private static TypeMapper getMapper(Class clazz, AnnotatedType type, IObjectMapper mapper, boolean isForSubType) {
         if (clazz == null) {
             return null;
         }
@@ -149,7 +146,7 @@ private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAer
                     ParameterizedType paramType = type.getParameterizedType();
                     Type[] types = paramType.getActualTypeArguments();
                     if (types.length != 2) {
-                        throw new AerospikeException(String.format("Type %s is a parameterized type as expected, but has %d type parameters, not the expected 2",
+                        throw new AerospikeMapperException(String.format("Type %s is a parameterized type as expected, but has %d type parameters, not the expected 2",
                                 clazz.getName(), types.length));
                     }
 
@@ -197,7 +194,7 @@ private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAer
                     ParameterizedType paramType = type.getParameterizedType();
                     Type[] types = paramType.getActualTypeArguments();
                     if (types.length != 1) {
-                        throw new AerospikeException(String.format("Type %s is a parameterized type as expected, but has %d type parameters, not the expected 1",
+                        throw new AerospikeMapperException(String.format("Type %s is a parameterized type as expected, but has %d type parameters, not the expected 1",
                                 clazz.getName(), types.length));
                     }
 
@@ -231,8 +228,8 @@ private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAer
                             ReferenceConfig ref = binConfig.getReference();
                             typeMapper = new ObjectReferenceMapper(
                                     ClassCache.getInstance().loadClass(clazz, mapper),
-                                    ref.getLazy() == null ? false : ref.getLazy(),
-                                    ref.getBatchLoad() == null ? true : ref.getBatchLoad(),
+                                    ref.getLazy() != null && ref.getLazy(),
+                                    ref.getBatchLoad() == null || ref.getBatchLoad(),
                                     ref.getType(), mapper);
                             addToMap = false;
                         }
@@ -266,7 +263,7 @@ private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAer
                     }
                 }
                 if (throwError) {
-                    throw new AerospikeException(String.format("A class with a reference to %s specifies multiple annotations for storing the reference", clazz.getName()));
+                    throw new AerospikeMapperException(String.format("A class with a reference to %s specifies multiple annotations for storing the reference", clazz.getName()));
                 }
                 if (typeMapper == null) {
                     // No annotations were specified, so use the ObjectReferenceMapper with non-lazy references
@@ -284,7 +281,7 @@ private static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAer
         return typeMapper;
     }
 
-    public static TypeMapper getMapper(Class clazz, AnnotatedType type, IBaseAeroMapper mapper) {
+    public static TypeMapper getMapper(Class clazz, AnnotatedType type, IObjectMapper mapper) {
         return getMapper(clazz, type, mapper, false);
     }
 
@@ -315,36 +312,6 @@ public static void clear() {
         mappers.clear();
     }
 
-    public static int returnTypeToListReturnType(ReturnType returnType) {
-        switch (returnType) {
-            case DEFAULT:
-            case ELEMENTS:
-                return ListReturnType.VALUE;
-            case COUNT:
-                return ListReturnType.COUNT;
-            case INDEX:
-                return ListReturnType.INDEX;
-            case NONE:
-            default:
-                return ListReturnType.NONE;
-        }
-    }
-
-    public static int returnTypeToMapReturnType(ReturnType returnType) {
-        switch (returnType) {
-            case DEFAULT:
-            case ELEMENTS:
-                return MapReturnType.KEY_VALUE;
-            case COUNT:
-                return MapReturnType.COUNT;
-            case INDEX:
-                return MapReturnType.INDEX;
-            case NONE:
-            default:
-                return MapReturnType.NONE;
-        }
-    }
-
     public static class AnnotatedType {
 
         private static final AnnotatedType defaultAnnotatedType = new AnnotatedType(null, null, null);
diff --git a/core/src/test/java/com/aerospike/mapper/tools/ClassCacheEntryFactoryTest.java b/core/src/test/java/com/aerospike/mapper/tools/ClassCacheEntryFactoryTest.java
new file mode 100644
index 0000000..0949113
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/ClassCacheEntryFactoryTest.java
@@ -0,0 +1,115 @@
+package com.aerospike.mapper.tools;
+
+import com.aerospike.mapper.annotations.AerospikeKey;
+import com.aerospike.mapper.annotations.AerospikeRecord;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import com.aerospike.mapper.tools.configuration.ClassConfig;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for ClassCacheEntry factory method validation (C1 bug fix) and ClassConfig.Builder (C2 bug fix).
+ */
+public class ClassCacheEntryFactoryTest {
+
+    @SuppressWarnings("unused")
+    @AerospikeRecord(namespace = "test", set = "factory_test",
+        factoryClass = "com.aerospike.mapper.tools.ClassCacheEntryFactoryTest$TestFactory")
+    static class OnlyFactoryClassPojo {
+
+        @AerospikeKey
+        int id;
+    }
+
+    @SuppressWarnings("unused")
+    @AerospikeRecord(namespace = "test", set = "factory_test",
+        factoryMethod = "create")
+    static class OnlyFactoryMethodPojo {
+
+        @AerospikeKey
+        int id;
+    }
+
+    @SuppressWarnings("unused")
+    static class TestFactory {
+
+        public static OnlyFactoryClassPojo create() {
+            return new OnlyFactoryClassPojo();
+        }
+    }
+
+    private IObjectMapper mapper;
+
+    @BeforeEach
+    public void setUp() {
+        ClassCache.getInstance().clear();
+        mapper = mock(IObjectMapper.class);
+    }
+
+    @AfterEach
+    public void tearDown() {
+        ClassCache.getInstance().clear();
+    }
+
+    @Test
+    public void missingFactoryMethod_throwsWithMessage() {
+        AerospikeMapperException ex = assertThrows(
+            AerospikeMapperException.class,
+            () -> ClassCache.getInstance().loadClass(OnlyFactoryClassPojo.class, mapper));
+        assertTrue(ex.getMessage().contains("Missing factoryMethod"),
+            "Expected message about missing factoryMethod, got: " + ex.getMessage());
+    }
+
+    @Test
+    public void missingFactoryClass_throwsWithMessage() {
+        AerospikeMapperException ex = assertThrows(
+            AerospikeMapperException.class,
+            () -> ClassCache.getInstance().loadClass(OnlyFactoryMethodPojo.class, mapper));
+        assertTrue(ex.getMessage().contains("Missing factoryClass"),
+            "Expected message about missing factoryClass, got: " + ex.getMessage());
+    }
+
+    // ── C2: ClassConfig.Builder.withShortName overload removed ────────
+
+    @Test
+    public void builderWithShortName_setsShortName() {
+        @SuppressWarnings("unused")
+        @AerospikeRecord(namespace = "test", set = "test")
+        class SimpleClass {
+
+            @AerospikeKey
+            int id;
+        }
+
+        ClassConfig config = new ClassConfig.Builder(SimpleClass.class)
+            .withNamespace("test")
+            .withSet("test")
+            .withShortName("MyShort")
+            .build();
+        assertEquals("MyShort", config.getShortName());
+    }
+
+    @Test
+    public void builderWithSendKey_doesNotAffectShortName() {
+        @SuppressWarnings("unused")
+        @AerospikeRecord(namespace = "test", set = "test")
+        class SimpleClass {
+
+            @AerospikeKey
+            int id;
+        }
+
+        ClassConfig config = new ClassConfig.Builder(SimpleClass.class)
+            .withNamespace("test")
+            .withSet("test")
+            .withSendKey(true)
+            .withShortName("Alias")
+            .build();
+        assertTrue(config.getSendKey());
+        assertEquals("Alias", config.getShortName());
+    }
+}
diff --git a/core/src/test/java/com/aerospike/mapper/tools/SetterParamTypeResolverTest.java b/core/src/test/java/com/aerospike/mapper/tools/SetterParamTypeResolverTest.java
new file mode 100644
index 0000000..9d4cade
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/SetterParamTypeResolverTest.java
@@ -0,0 +1,32 @@
+package com.aerospike.mapper.tools;
+
+import com.aerospike.mapper.tools.PropertyDefinition.SetterParamType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class SetterParamTypeResolverTest {
+
+    @Test
+    public void defaultResolver_alwaysReturnsNone() {
+        assertEquals(SetterParamType.NONE, SetterParamTypeResolver.DEFAULT.resolve("any.type"));
+        assertEquals(SetterParamType.NONE, SetterParamTypeResolver.DEFAULT.resolve(null));
+        assertEquals(SetterParamType.NONE, SetterParamTypeResolver.DEFAULT.resolve(""));
+    }
+
+    @Test
+    public void customResolver_canReturnKey() {
+        SetterParamTypeResolver custom = name ->
+            "my.Key".equals(name) ? SetterParamType.KEY : SetterParamType.NONE;
+        assertEquals(SetterParamType.KEY, custom.resolve("my.Key"));
+        assertEquals(SetterParamType.NONE, custom.resolve("other"));
+    }
+
+    @Test
+    public void customResolver_canReturnValue() {
+        SetterParamTypeResolver custom = name ->
+            "my.Value".equals(name) ? SetterParamType.VALUE : SetterParamType.NONE;
+        assertEquals(SetterParamType.VALUE, custom.resolve("my.Value"));
+        assertEquals(SetterParamType.NONE, custom.resolve("other"));
+    }
+}
diff --git a/core/src/test/java/com/aerospike/mapper/tools/mappers/MapperUnitTests.java b/core/src/test/java/com/aerospike/mapper/tools/mappers/MapperUnitTests.java
new file mode 100644
index 0000000..615c292
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/mappers/MapperUnitTests.java
@@ -0,0 +1,432 @@
+package com.aerospike.mapper.tools.mappers;
+
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for all individual TypeMapper implementations.
+ * No Aerospike server or mocking required - these are pure Java function tests.
+ */
+public class MapperUnitTests {
+
+    // ── DateMapper ────────────────────────────────────────────────────────────
+
+    @Test
+    public void dateMapper_nullRoundTrip() {
+        DateMapper m = new DateMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void dateMapper_roundTrip() {
+        DateMapper m = new DateMapper();
+        Date original = new Date(1_700_000_000_000L);
+        Long serialized = (Long) m.toAerospikeFormat(original);
+        assertEquals(original.getTime(), serialized);
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void dateMapper_epochZero() {
+        DateMapper m = new DateMapper();
+        Date epoch = new Date(0L);
+        assertEquals(new Date(0L), m.fromAerospikeFormat(m.toAerospikeFormat(epoch)));
+    }
+
+    // ── InstantMapper ─────────────────────────────────────────────────────────
+
+    @Test
+    public void instantMapper_nullRoundTrip() {
+        InstantMapper m = new InstantMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void instantMapper_roundTrip() {
+        InstantMapper m = new InstantMapper();
+        Instant original = Instant.parse("2024-06-15T12:34:56.123456789Z");
+        Long serialized = (Long) m.toAerospikeFormat(original);
+        Instant restored = (Instant) m.fromAerospikeFormat(serialized);
+        assertEquals(original, restored);
+    }
+
+    @Test
+    public void instantMapper_epochZero() {
+        InstantMapper m = new InstantMapper();
+        Instant epoch = Instant.EPOCH;
+        assertEquals(epoch, m.fromAerospikeFormat(m.toAerospikeFormat(epoch)));
+    }
+
+    @Test
+    public void instantMapper_nanosecondPrecision() {
+        InstantMapper m = new InstantMapper();
+        // Verify that nanosecond component is preserved exactly
+        Instant original = Instant.ofEpochSecond(1_000_000L, 999_999_999L);
+        assertEquals(original, m.fromAerospikeFormat(m.toAerospikeFormat(original)));
+    }
+
+    // ── LocalDateMapper ───────────────────────────────────────────────────────
+
+    @Test
+    public void localDateMapper_nullRoundTrip() {
+        LocalDateMapper m = new LocalDateMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void localDateMapper_roundTrip() {
+        LocalDateMapper m = new LocalDateMapper();
+        LocalDate original = LocalDate.of(2024, Month.NOVEMBER, 5);
+        Long serialized = (Long) m.toAerospikeFormat(original);
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void localDateMapper_epochDay() {
+        LocalDateMapper m = new LocalDateMapper();
+        LocalDate epoch = LocalDate.ofEpochDay(0);
+        assertEquals(epoch, m.fromAerospikeFormat(m.toAerospikeFormat(epoch)));
+    }
+
+    // ── LocalTimeMapper ───────────────────────────────────────────────────────
+
+    @Test
+    public void localTimeMapper_nullRoundTrip() {
+        LocalTimeMapper m = new LocalTimeMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void localTimeMapper_roundTrip() {
+        LocalTimeMapper m = new LocalTimeMapper();
+        LocalTime original = LocalTime.of(13, 45, 22, 123_456_789);
+        Long serialized = (Long) m.toAerospikeFormat(original);
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void localTimeMapper_midnight() {
+        LocalTimeMapper m = new LocalTimeMapper();
+        LocalTime midnight = LocalTime.MIDNIGHT;
+        assertEquals(midnight, m.fromAerospikeFormat(m.toAerospikeFormat(midnight)));
+    }
+
+    // ── LocalDateTimeMapper ───────────────────────────────────────────────────
+
+    @Test
+    public void localDateTimeMapper_nullRoundTrip() {
+        LocalDateTimeMapper m = new LocalDateTimeMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void localDateTimeMapper_roundTrip() {
+        LocalDateTimeMapper m = new LocalDateTimeMapper();
+        LocalDateTime original = LocalDateTime.of(2024, 6, 15, 10, 30, 0, 987_654_321);
+        @SuppressWarnings("unchecked")
+        List serialized = (List) m.toAerospikeFormat(original);
+        assertEquals(2, serialized.size());
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void localDateTimeMapper_storesDateAndTimeSeparately() {
+        LocalDateTimeMapper m = new LocalDateTimeMapper();
+        LocalDateTime dt = LocalDateTime.of(2000, 1, 1, 0, 0, 0, 0);
+        @SuppressWarnings("unchecked")
+        List serialized = (List) m.toAerospikeFormat(dt);
+        // First element = epoch days, second = nano of day
+        assertEquals(LocalDate.of(2000, 1, 1).toEpochDay(), (long) serialized.get(0));
+        assertEquals(LocalTime.MIDNIGHT.toNanoOfDay(), (long) serialized.get(1));
+    }
+
+    // ── BigDecimalMapper ──────────────────────────────────────────────────────
+
+    @Test
+    public void bigDecimalMapper_nullRoundTrip() {
+        BigDecimalMapper m = new BigDecimalMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void bigDecimalMapper_roundTrip() {
+        BigDecimalMapper m = new BigDecimalMapper();
+        BigDecimal original = new BigDecimal("123456789.987654321");
+        String serialized = (String) m.toAerospikeFormat(original);
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void bigDecimalMapper_preservesScale() {
+        BigDecimalMapper m = new BigDecimalMapper();
+        BigDecimal value = new BigDecimal("1.000");
+        BigDecimal restored = (BigDecimal) m.fromAerospikeFormat(m.toAerospikeFormat(value));
+        assertEquals(0, value.compareTo(restored));
+        assertEquals(value.scale(), restored.scale());
+    }
+
+    @Test
+    public void bigDecimalMapper_negative() {
+        BigDecimalMapper m = new BigDecimalMapper();
+        BigDecimal value = new BigDecimal("-9876543210.123456789");
+        assertEquals(value, m.fromAerospikeFormat(m.toAerospikeFormat(value)));
+    }
+
+    // ── BigIntegerMapper ──────────────────────────────────────────────────────
+
+    @Test
+    public void bigIntegerMapper_nullRoundTrip() {
+        BigIntegerMapper m = new BigIntegerMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void bigIntegerMapper_roundTrip() {
+        BigIntegerMapper m = new BigIntegerMapper();
+        BigInteger original = new BigInteger("99999999999999999999999999999");
+        String serialized = (String) m.toAerospikeFormat(original);
+        assertEquals(original, m.fromAerospikeFormat(serialized));
+    }
+
+    @Test
+    public void bigIntegerMapper_negative() {
+        BigIntegerMapper m = new BigIntegerMapper();
+        BigInteger value = new BigInteger("-12345678901234567890");
+        assertEquals(value, m.fromAerospikeFormat(m.toAerospikeFormat(value)));
+    }
+
+    // ── CharacterMapper ───────────────────────────────────────────────────────
+
+    @Test
+    public void characterMapper_nullRoundTrip() {
+        CharacterMapper m = new CharacterMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void characterMapper_roundTrip() {
+        CharacterMapper m = new CharacterMapper();
+        for (char c : new char[]{'A', 'z', '0', ' ', '\t', '\u00e9'}) {
+            Long serialized = (Long) m.toAerospikeFormat(c);
+            assertEquals(c, (long) serialized);
+            assertEquals(c, m.fromAerospikeFormat(serialized));
+        }
+    }
+
+    @Test
+    public void characterMapper_fromLong() {
+        CharacterMapper m = new CharacterMapper();
+        // fromAerospikeFormat should accept any Number
+        assertEquals('A', m.fromAerospikeFormat(65L));
+        assertEquals('a', m.fromAerospikeFormat(97L));
+    }
+
+    // ── ByteMapper ────────────────────────────────────────────────────────────
+
+    @Test
+    public void byteMapper_nullRoundTrip() {
+        ByteMapper m = new ByteMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void byteMapper_roundTrip() {
+        ByteMapper m = new ByteMapper();
+        for (byte b : new byte[]{Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE}) {
+            Long serialized = (Long) m.toAerospikeFormat(b);
+            assertEquals(b, (long) serialized);
+            assertEquals(b, m.fromAerospikeFormat(serialized));
+        }
+    }
+
+    // ── ShortMapper ───────────────────────────────────────────────────────────
+
+    @Test
+    public void shortMapper_nullRoundTrip() {
+        ShortMapper m = new ShortMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void shortMapper_roundTrip() {
+        ShortMapper m = new ShortMapper();
+        for (short s : new short[]{Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE}) {
+            Long serialized = (Long) m.toAerospikeFormat(s);
+            assertEquals(s, (long) serialized);
+            assertEquals(s, m.fromAerospikeFormat(serialized));
+        }
+    }
+
+    // ── IntMapper ─────────────────────────────────────────────────────────────
+
+    @Test
+    public void intMapper_nullFromAerospike() {
+        IntMapper m = new IntMapper();
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void intMapper_toAerospikePassthrough() {
+        IntMapper m = new IntMapper();
+        // toAerospikeFormat returns the value as-is
+        assertEquals(42, m.toAerospikeFormat(42));
+        assertNull(m.toAerospikeFormat(null));
+    }
+
+    @Test
+    public void intMapper_fromLong() {
+        IntMapper m = new IntMapper();
+        // Aerospike returns longs; fromAerospikeFormat must convert to int
+        assertEquals(100, m.fromAerospikeFormat(100L));
+        assertEquals(Integer.MAX_VALUE, m.fromAerospikeFormat((long) Integer.MAX_VALUE));
+    }
+
+    // ── LongMapper ────────────────────────────────────────────────────────────
+
+    @Test
+    public void longMapper_passthrough() {
+        LongMapper m = new LongMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+        assertEquals(Long.MAX_VALUE, m.toAerospikeFormat(Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, m.fromAerospikeFormat(Long.MIN_VALUE));
+    }
+
+    // ── FloatMapper ───────────────────────────────────────────────────────────
+
+    @Test
+    public void floatMapper_nullFromAerospike() {
+        FloatMapper m = new FloatMapper();
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void floatMapper_toAerospikePassthrough() {
+        FloatMapper m = new FloatMapper();
+        assertEquals(3.14f, m.toAerospikeFormat(3.14f));
+        assertNull(m.toAerospikeFormat(null));
+    }
+
+    @Test
+    public void floatMapper_fromDouble() {
+        FloatMapper m = new FloatMapper();
+        // Aerospike may return Double; fromAerospikeFormat must return float
+        float result = (float) m.fromAerospikeFormat(3.14);
+        assertEquals(3.14f, result, 0.001f);
+    }
+
+    // ── DoubleMapper ──────────────────────────────────────────────────────────
+
+    @Test
+    public void doubleMapper_passthrough() {
+        DoubleMapper m = new DoubleMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+        assertEquals(Math.PI, m.toAerospikeFormat(Math.PI));
+        assertEquals(Math.E, m.fromAerospikeFormat(Math.E));
+    }
+
+    // ── BooleanMapper ─────────────────────────────────────────────────────────
+
+    @Test
+    public void booleanMapper_nullRoundTrip() {
+        BooleanMapper m = new BooleanMapper();
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void booleanMapper_toAerospikePassthrough() {
+        BooleanMapper m = new BooleanMapper();
+        assertEquals(Boolean.TRUE, m.toAerospikeFormat(true));
+        assertEquals(Boolean.FALSE, m.toAerospikeFormat(false));
+    }
+
+    @Test
+    public void booleanMapper_fromNativeBoolean() {
+        BooleanMapper m = new BooleanMapper();
+        assertTrue((Boolean) m.fromAerospikeFormat(Boolean.TRUE));
+        assertFalse((Boolean) m.fromAerospikeFormat(Boolean.FALSE));
+    }
+
+    @Test
+    public void booleanMapper_backwardCompatibleFromLong() {
+        // Older Aerospike servers stored booleans as 0/1 longs
+        BooleanMapper m = new BooleanMapper();
+        assertFalse((Boolean) m.fromAerospikeFormat(0L));
+        assertTrue((Boolean) m.fromAerospikeFormat(1L));
+        assertTrue((Boolean) m.fromAerospikeFormat(-1L));
+    }
+
+    // ── EnumMapper ────────────────────────────────────────────────────────────
+
+    private enum Color { RED, GREEN, BLUE }
+
+    private enum Status {
+        ACTIVE("active"), INACTIVE("inactive");
+
+        private final String code;
+
+        Status(String code) {
+            this.code = code;
+        }
+    }
+
+    @Test
+    public void enumMapper_nullRoundTrip() {
+        EnumMapper m = new EnumMapper(Color.class, "");
+        assertNull(m.toAerospikeFormat(null));
+        assertNull(m.fromAerospikeFormat(null));
+    }
+
+    @Test
+    public void enumMapper_byName_roundTrip() {
+        EnumMapper m = new EnumMapper(Color.class, "");
+        assertEquals("RED", m.toAerospikeFormat(Color.RED));
+        assertEquals("BLUE", m.toAerospikeFormat(Color.BLUE));
+        assertEquals(Color.GREEN, m.fromAerospikeFormat("GREEN"));
+    }
+
+    @Test
+    public void enumMapper_byField_roundTrip() {
+        EnumMapper m = new EnumMapper(Status.class, "code");
+        assertEquals("active", m.toAerospikeFormat(Status.ACTIVE));
+        assertEquals("inactive", m.toAerospikeFormat(Status.INACTIVE));
+        assertEquals(Status.ACTIVE, m.fromAerospikeFormat("active"));
+        assertEquals(Status.INACTIVE, m.fromAerospikeFormat("inactive"));
+    }
+
+    @Test
+    public void enumMapper_unknownValue_throwsException() {
+        EnumMapper m = new EnumMapper(Color.class, "");
+        assertThrows(AerospikeMapperException.class, () -> m.fromAerospikeFormat("PURPLE"));
+    }
+
+    @Test
+    public void enumMapper_invalidField_throwsOnConstruction() {
+        assertThrows(AerospikeMapperException.class,
+                () -> new EnumMapper(Color.class, "nonExistentField"));
+    }
+}
diff --git a/core/src/test/java/com/aerospike/mapper/tools/utils/MapperUtilsTest.java b/core/src/test/java/com/aerospike/mapper/tools/utils/MapperUtilsTest.java
new file mode 100644
index 0000000..3e83e79
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/utils/MapperUtilsTest.java
@@ -0,0 +1,75 @@
+package com.aerospike.mapper.tools.utils;
+
+import com.aerospike.mapper.annotations.AerospikeKey;
+import com.aerospike.mapper.annotations.AerospikeRecord;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import com.aerospike.mapper.tools.ClassCache;
+import com.aerospike.mapper.tools.ClassCacheEntry;
+import com.aerospike.mapper.tools.IObjectMapper;
+import com.aerospike.mapper.tools.IRecordConverter;
+import com.aerospike.mapper.tools.RecordLoader;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link MapperUtils}.
+ */
+public class MapperUtilsTest {
+
+    /** A correctly configured class - has a namespace. */
+    @AerospikeRecord(namespace = "test", set = "mu_valid")
+    private static class ValidClass {
+        @AerospikeKey
+        int id;
+    }
+
+    /** A misconfigured class - namespace is intentionally blank. */
+    @AerospikeRecord(set = "mu_bad")
+    private static class BlankNamespaceClass {
+        @AerospikeKey
+        int id;
+    }
+
+    private IObjectMapper mockMapper;
+
+    @BeforeEach
+    public void setUp() {
+        ClassCache.getInstance().clear();
+        mockMapper = new IObjectMapper() {
+            @Override public IRecordConverter getMappingConverter() { return mock(IRecordConverter.class); }
+            @Override public RecordLoader getRecordLoader() { return mock(RecordLoader.class); }
+        };
+    }
+
+    @AfterEach
+    public void tearDown() {
+        ClassCache.getInstance().clear();
+    }
+
+    @Test
+    public void validClass_returnsEntry() {
+        ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(ValidClass.class, mockMapper);
+        assertNotNull(entry);
+        assertEquals("test", entry.getNamespace());
+    }
+
+    @Test
+    public void blankNamespace_throwsAerospikeMapperException() {
+        AerospikeMapperException ex = assertThrows(
+                AerospikeMapperException.class,
+                () -> MapperUtils.getEntryAndValidateNamespace(BlankNamespaceClass.class, mockMapper));
+        assertTrue(ex.getMessage().contains("Namespace not specified"));
+    }
+
+    @Test
+    public void exceptionMessage_containsClassName() {
+        AerospikeMapperException ex = assertThrows(
+                AerospikeMapperException.class,
+                () -> MapperUtils.getEntryAndValidateNamespace(BlankNamespaceClass.class, mockMapper));
+        assertTrue(ex.getMessage().contains(BlankNamespaceClass.class.getName()));
+    }
+}
diff --git a/core/src/test/java/com/aerospike/mapper/tools/utils/ParserUtilsTest.java b/core/src/test/java/com/aerospike/mapper/tools/utils/ParserUtilsTest.java
new file mode 100644
index 0000000..f589145
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/utils/ParserUtilsTest.java
@@ -0,0 +1,108 @@
+package com.aerospike.mapper.tools.utils;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link ParserUtils} placeholder resolution.
+ * Tests ${prop} system-property syntax and #{ENV} environment-variable syntax.
+ */
+public class ParserUtilsTest {
+
+    private static final String TEST_PROP = "aerospike.test.namespace";
+
+    @AfterEach
+    public void cleanup() {
+        System.clearProperty(TEST_PROP);
+    }
+
+    // ── Plain string passthrough ──────────────────────────────────────────────
+
+    @Test
+    public void plainString_returnedAsIs() {
+        assertEquals("test", ParserUtils.getInstance().get("test"));
+    }
+
+    @Test
+    public void longPlainString_returnedAsIs() {
+        String value = "some-namespace";
+        assertEquals(value, ParserUtils.getInstance().get(value));
+    }
+
+    // ── Short strings bypass (≤3 chars) ──────────────────────────────────────
+
+    @Test
+    public void nullInput_returnsNull() {
+        assertNull(ParserUtils.getInstance().get(null));
+    }
+
+    @Test
+    public void emptyString_returnedAsIs() {
+        assertEquals("", ParserUtils.getInstance().get(""));
+    }
+
+    @Test
+    public void threeCharString_returnedAsIs() {
+        assertEquals("abc", ParserUtils.getInstance().get("abc"));
+    }
+
+    // ── System property resolution (${...}) ───────────────────────────────────
+
+    @Test
+    public void systemProperty_resolved() {
+        System.setProperty(TEST_PROP, "mynamespace");
+        String result = ParserUtils.getInstance().get("${" + TEST_PROP + "}");
+        assertEquals("mynamespace", result);
+    }
+
+    @Test
+    public void systemProperty_missing_noDefault_returnsNull() {
+        // Ensure property is not set
+        System.clearProperty(TEST_PROP);
+        assertNull(ParserUtils.getInstance().get("${" + TEST_PROP + "}"));
+    }
+
+    @Test
+    public void systemProperty_missing_withDefault_returnsDefault() {
+        System.clearProperty(TEST_PROP);
+        String result = ParserUtils.getInstance().get("${" + TEST_PROP + ":defaultNs}");
+        assertEquals("defaultNs", result);
+    }
+
+    @Test
+    public void systemProperty_present_defaultIgnored() {
+        System.setProperty(TEST_PROP, "realValue");
+        String result = ParserUtils.getInstance().get("${" + TEST_PROP + ":defaultValue}");
+        assertEquals("realValue", result);
+    }
+
+    // ── Environment variable resolution (#{...}) ──────────────────────────────
+
+    @Test
+    public void envVariable_missing_noDefault_returnsNull() {
+        // Use a variable name that is virtually guaranteed to not exist
+        assertNull(ParserUtils.getInstance().get("#{__AEROSPIKE_MAPPER_TEST_VAR_THAT_DOES_NOT_EXIST__}"));
+    }
+
+    @Test
+    public void envVariable_missing_withDefault_returnsDefault() {
+        String result = ParserUtils.getInstance().get("#{__AEROSPIKE_MAPPER_ABSENT__:fallback}");
+        assertEquals("fallback", result);
+    }
+
+    // ── Edge cases ────────────────────────────────────────────────────────────
+
+    @Test
+    public void incompleteOpenBrace_returnedAsIs() {
+        // Starts with ${ but doesn't end with }
+        String value = "${no-closing-brace";
+        assertEquals(value, ParserUtils.getInstance().get(value));
+    }
+
+    @Test
+    public void singletonAlwaysReturnsSameInstance() {
+        assertSame(ParserUtils.getInstance(), ParserUtils.getInstance());
+    }
+}
diff --git a/core/src/test/java/com/aerospike/mapper/tools/utils/TypeUtilsTest.java b/core/src/test/java/com/aerospike/mapper/tools/utils/TypeUtilsTest.java
new file mode 100644
index 0000000..2e062aa
--- /dev/null
+++ b/core/src/test/java/com/aerospike/mapper/tools/utils/TypeUtilsTest.java
@@ -0,0 +1,174 @@
+package com.aerospike.mapper.tools.utils;
+
+import com.aerospike.mapper.annotations.AerospikeBin;
+import com.aerospike.mapper.annotations.AerospikeEmbed;
+import com.aerospike.mapper.annotations.AerospikeKey;
+import com.aerospike.mapper.annotations.AerospikeRecord;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link TypeUtils} static helpers and the inner {@link TypeUtils.AnnotatedType}.
+ */
+public class TypeUtilsTest {
+
+    // ── isVoidType ────────────────────────────────────────────────────────────
+
+    @Test
+    public void isVoidType_null_returnsTrue() {
+        assertTrue(TypeUtils.isVoidType(null));
+    }
+
+    @Test
+    public void isVoidType_voidClass_returnsTrue() {
+        assertTrue(TypeUtils.isVoidType(Void.class));
+    }
+
+    @Test
+    public void isVoidType_voidPrimitive_returnsTrue() {
+        assertTrue(TypeUtils.isVoidType(void.class));
+    }
+
+    @Test
+    public void isVoidType_string_returnsFalse() {
+        assertFalse(TypeUtils.isVoidType(String.class));
+    }
+
+    @Test
+    public void isVoidType_int_returnsFalse() {
+        assertFalse(TypeUtils.isVoidType(int.class));
+    }
+
+    @Test
+    public void isVoidType_object_returnsFalse() {
+        assertFalse(TypeUtils.isVoidType(Object.class));
+    }
+
+    // ── isByteType ────────────────────────────────────────────────────────────
+
+    @Test
+    public void isByteType_byteBoxed_returnsTrue() {
+        assertTrue(TypeUtils.isByteType(Byte.class));
+    }
+
+    @Test
+    public void isByteType_bytePrimitive_returnsTrue() {
+        assertTrue(TypeUtils.isByteType(byte.class));
+    }
+
+    @Test
+    public void isByteType_integer_returnsFalse() {
+        assertFalse(TypeUtils.isByteType(Integer.class));
+    }
+
+    @Test
+    public void isByteType_string_returnsFalse() {
+        assertFalse(TypeUtils.isByteType(String.class));
+    }
+
+    @Test
+    public void isByteType_null_returnsFalse() {
+        assertFalse(TypeUtils.isByteType(null));
+    }
+
+    // ── isAerospikeNativeType ─────────────────────────────────────────────────
+
+    @Test
+    public void isAerospikeNativeType_null_returnsFalse() {
+        assertFalse(TypeUtils.isAerospikeNativeType(null));
+    }
+
+    @Test
+    public void isAerospikeNativeType_longBoxed_returnsTrue() {
+        assertTrue(TypeUtils.isAerospikeNativeType(Long.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_longPrimitive_returnsTrue() {
+        assertTrue(TypeUtils.isAerospikeNativeType(long.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_doubleBoxed_returnsTrue() {
+        assertTrue(TypeUtils.isAerospikeNativeType(Double.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_doublePrimitive_returnsTrue() {
+        assertTrue(TypeUtils.isAerospikeNativeType(double.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_string_returnsTrue() {
+        assertTrue(TypeUtils.isAerospikeNativeType(String.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_integer_returnsFalse() {
+        assertFalse(TypeUtils.isAerospikeNativeType(Integer.class));
+    }
+
+    @Test
+    public void isAerospikeNativeType_bigDecimal_returnsFalse() {
+        assertFalse(TypeUtils.isAerospikeNativeType(BigDecimal.class));
+    }
+
+    // ── TypeUtils.AnnotatedType ───────────────────────────────────────────────
+
+    @AerospikeRecord(namespace = "test", set = "tut")
+    private static class SampleClass {
+        @AerospikeKey
+        int id;
+
+        @AerospikeEmbed
+        @AerospikeBin(name = "embeddedField")
+        SampleClass embedded;
+
+        String plain;
+    }
+
+    @Test
+    public void annotatedType_defaultInstance_isNotParameterized() {
+        TypeUtils.AnnotatedType at = TypeUtils.AnnotatedType.getDefaultAnnotateType();
+        assertFalse(at.isParameterizedType());
+        assertNull(at.getParameterizedType());
+        assertNull(at.getBinConfig());
+    }
+
+    @Test
+    public void annotatedType_fromField_withAnnotations() throws NoSuchFieldException {
+        Field field = SampleClass.class.getDeclaredField("embedded");
+        TypeUtils.AnnotatedType at = new TypeUtils.AnnotatedType(null, field);
+        // Field has @AerospikeEmbed and @AerospikeBin annotations
+        assertNotNull(at.getAnnotations());
+        assertTrue(at.getAnnotations().length >= 2);
+        assertNotNull(at.getAnnotation(AerospikeEmbed.class));
+        assertNotNull(at.getAnnotation(AerospikeBin.class));
+    }
+
+    @Test
+    public void annotatedType_fromField_noAnnotations() throws NoSuchFieldException {
+        Field field = SampleClass.class.getDeclaredField("plain");
+        TypeUtils.AnnotatedType at = new TypeUtils.AnnotatedType(null, field);
+        // No specific annotation to retrieve
+        assertNull(at.getAnnotation(AerospikeEmbed.class));
+    }
+
+    @Test
+    public void annotatedType_getAnnotation_unknownReturnsNull() throws NoSuchFieldException {
+        Field field = SampleClass.class.getDeclaredField("embedded");
+        TypeUtils.AnnotatedType at = new TypeUtils.AnnotatedType(null, field);
+        // AerospikeKey is not present on this field
+        assertNull(at.getAnnotation(AerospikeKey.class));
+    }
+
+    @Test
+    public void annotatedType_defaultInstance_getAnnotationReturnsNull() {
+        TypeUtils.AnnotatedType at = TypeUtils.AnnotatedType.getDefaultAnnotateType();
+        assertNull(at.getAnnotation(AerospikeEmbed.class));
+    }
+}
diff --git a/fluentapi/pom.xml b/fluentapi/pom.xml
new file mode 100644
index 0000000..2a70428
--- /dev/null
+++ b/fluentapi/pom.xml
@@ -0,0 +1,69 @@
+
+	4.0.0
+
+	
+		com.aerospike
+		java-object-mapper-parent
+		2.6.0
+	
+
+	java-object-mapper-fluent
+	jar
+
+	Aerospike Object Mapper Fluent
+	Aerospike Object Mapper using the new fluent Aerospike Java client. Implements RecordMapper and RecordMappingFactory.
+
+	
+		
+			com.aerospike
+			java-object-mapper-core
+		
+		
+			com.aerospike
+			aerospike-client-fluent
+		
+		
+			org.projectlombok
+			lombok
+		
+		
+			org.junit.jupiter
+			junit-jupiter
+		
+		
+			org.mockito
+			mockito-core
+		
+	
+
+	
+		
+			
+				maven-compiler-plugin
+				
+					21
+					21
+				
+			
+			
+				org.apache.maven.plugins
+				maven-dependency-plugin
+			
+			
+				org.apache.maven.plugins
+				maven-surefire-plugin
+			
+		
+		
+			
+				${project.basedir}/src/main/java
+				
+					**/*.properties
+					**/*.xml
+				
+			
+		
+	
+
diff --git a/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentAeroMapper.java b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentAeroMapper.java
new file mode 100644
index 0000000..0f176d7
--- /dev/null
+++ b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentAeroMapper.java
@@ -0,0 +1,76 @@
+package com.aerospike.mapper.tools.fluent;
+
+import com.aerospike.client.fluent.RecordMapper;
+import com.aerospike.client.fluent.RecordMappingFactory;
+import com.aerospike.client.fluent.Session;
+import com.aerospike.mapper.exceptions.AerospikeMapperException;
+import com.aerospike.mapper.tools.ClassCache;
+import com.aerospike.mapper.tools.ClassCacheEntry;
+import com.aerospike.mapper.tools.IObjectMapper;
+import com.aerospike.mapper.tools.IRecordConverter;
+import com.aerospike.mapper.tools.RecordLoader;
+import com.aerospike.mapper.tools.converters.MappingConverter;
+import lombok.Getter;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Entry point for the fluent Aerospike Object Mapper.
+ * Implements the fluent client's {@link RecordMappingFactory} so it can be registered
+ * directly with the fluent client session.
+ *
+ * 

Usage: + *

+ *   FluentAeroMapper mapper = new FluentAeroMapper(session);
+ *   RecordMapper<Customer> customerMapper = mapper.getMapper(Customer.class);
+ * 
+ */ +public class FluentAeroMapper implements RecordMappingFactory, IObjectMapper { + + @Getter + private final Session session; + private final FluentRecordLoader recordLoader; + private final MappingConverter mappingConverter; + private final ConcurrentHashMap, FluentRecordMapper> mapperCache = new ConcurrentHashMap<>(); + + public FluentAeroMapper(Session session) { + if (session == null) { + throw new AerospikeMapperException("Session must not be null"); + } + this.session = session; + this.recordLoader = new FluentRecordLoader(session); + this.mappingConverter = new MappingConverter(this, this.recordLoader); + } + + /** + * Returns a {@link RecordMapper} for the given class. Implements {@link RecordMappingFactory}. + */ + @Override + public RecordMapper getMapper(Class clazz) { + return getRecordMapper(clazz); + } + + /** + * Returns a {@link FluentRecordMapper} for the given class, using the core + * {@link ClassCacheEntry} for annotation-driven serialization/deserialization. + * Instances are cached per class. + */ + @SuppressWarnings("unchecked") + public FluentRecordMapper getRecordMapper(Class clazz) { + return (FluentRecordMapper) mapperCache.computeIfAbsent(clazz, c -> { + ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this); + return new FluentRecordMapper<>(entry, mappingConverter); + }); + } + + @Override + public IRecordConverter getMappingConverter() { + return mappingConverter; + } + + @Override + public RecordLoader getRecordLoader() { + return recordLoader; + } + +} diff --git a/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordLoader.java b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordLoader.java new file mode 100644 index 0000000..4146a0c --- /dev/null +++ b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordLoader.java @@ -0,0 +1,90 @@ +package com.aerospike.mapper.tools.fluent; + +import com.aerospike.client.fluent.DataSet; +import com.aerospike.client.fluent.Key; +import com.aerospike.client.fluent.Record; +import com.aerospike.client.fluent.RecordStream; +import com.aerospike.client.fluent.Session; +import com.aerospike.client.fluent.Value; +import com.aerospike.mapper.exceptions.AerospikeMapperException; +import com.aerospike.mapper.tools.RecordKey; +import com.aerospike.mapper.tools.RecordLoader; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements {@link RecordLoader} using the fluent client's {@link Session}. + */ +public class FluentRecordLoader implements RecordLoader { + + private final Session session; + + public FluentRecordLoader(Session session) { + this.session = session; + } + + @Override + public Map getRecord(String namespace, String setName, Object keyValue) { + Key key = DataSet.of(namespace, setName).idForObject(keyValue); + try (RecordStream rs = session.query(key).executeSync()) { + Record record = rs.getFirstRecord(); + return record == null ? null : new HashMap<>(record.bins); + } + } + + @Override + public Map getRecordByDigest(String namespace, String setName, byte[] digest) { + Key key = DataSet.of(namespace, setName).idFromDigest(digest); + try (RecordStream rs = session.query(key).executeSync()) { + Record record = rs.getFirstRecord(); + return record == null ? null : new HashMap<>(record.bins); + } + } + + @Override + public List> getBatchRecords(List keys) { + if (keys.isEmpty()) { + return Collections.emptyList(); + } + + List fluentKeys = new ArrayList<>(keys.size()); + for (RecordKey rk : keys) { + fluentKeys.add(rk.digest != null + ? DataSet.of(rk.namespace, rk.setName).idFromDigest(rk.digest) + : DataSet.of(rk.namespace, rk.setName).idForObject(rk.keyValue)); + } + + // Index-based ordering: map each Key identity to its position + Map keyIndexMap = new HashMap<>(fluentKeys.size()); + for (int i = 0; i < fluentKeys.size(); i++) { + keyIndexMap.put(fluentKeys.get(i), i); + } + + List> results = new ArrayList<>(Collections.nCopies(keys.size(), null)); + + try (RecordStream rs = session.query(fluentKeys).executeSync()) { + while (rs.hasNext()) { + var result = rs.next(); + Integer idx = keyIndexMap.get(result.key()); + if (idx != null && result.isOk() && result.recordOrNull() != null) { + results.set(idx, new HashMap<>(result.recordOrNull().bins)); + } + } + } catch (AerospikeMapperException e) { + throw e; + } catch (Exception e) { + throw new AerospikeMapperException("Batch record fetch failed", e); + } + + return results; + } + + @Override + public byte[] computeDigest(String setName, Object userKey) { + return Key.computeDigest(setName, Value.get(userKey)); + } +} diff --git a/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordMapper.java b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordMapper.java new file mode 100644 index 0000000..0115699 --- /dev/null +++ b/fluentapi/src/main/java/com/aerospike/mapper/tools/fluent/FluentRecordMapper.java @@ -0,0 +1,58 @@ +package com.aerospike.mapper.tools.fluent; + +import com.aerospike.client.fluent.Key; +import com.aerospike.client.fluent.RecordMapper; +import com.aerospike.client.fluent.Value; +import com.aerospike.mapper.tools.ClassCacheEntry; +import com.aerospike.mapper.tools.converters.MappingConverter; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implements the fluent client's {@link RecordMapper} using the core {@link ClassCacheEntry} + * for map-based serialization and deserialization. + * + * @param the mapped POJO type + */ +public class FluentRecordMapper implements RecordMapper { + + private final ClassCacheEntry entry; + private final MappingConverter converter; + + FluentRecordMapper(ClassCacheEntry entry, MappingConverter converter) { + this.entry = entry; + this.converter = converter; + } + + @SuppressWarnings("unchecked") + @Override + public T fromMap(Map bins, Key key, int generation) { + Map binsWithKey = bins; + // Inject key field if stored separately (sendKey scenario) + String keyFieldName = entry.getKeyFieldName(); + if (keyFieldName != null && !entry.isKeyFieldStoredAsBin() && key.userKey != null) { + binsWithKey = new HashMap<>(bins); + binsWithKey.put(keyFieldName, key.userKey.getObject()); + } + T result = converter.convertToObject((Class) entry.getUnderlyingClass(), binsWithKey); + entry.setGenerationValue(result, generation); + return result; + } + + @Override + public Map toMap(T obj) { + Map rawMap = entry.getMap(obj, false); + return rawMap.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> Value.get(e.getValue()) + )); + } + + @Override + public Object id(T obj) { + return entry.getKey(obj); + } +} diff --git a/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentAeroMapperTest.java b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentAeroMapperTest.java new file mode 100644 index 0000000..8888a27 --- /dev/null +++ b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentAeroMapperTest.java @@ -0,0 +1,108 @@ +package com.aerospike.mapper.tools.fluent; + +import com.aerospike.client.fluent.RecordMapper; +import com.aerospike.client.fluent.Session; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.tools.ClassCache; +import com.aerospike.mapper.tools.IRecordConverter; +import com.aerospike.mapper.tools.RecordLoader; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.aerospike.mapper.exceptions.AerospikeMapperException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link FluentAeroMapper}. + * Uses a mocked {@link Session} — no Aerospike server needed. + */ +public class FluentAeroMapperTest { + + @SuppressWarnings("unused") + @AerospikeRecord(namespace = "test", set = "fam_pojo") + private static class SamplePojo { + @AerospikeKey + int id; + String name; + } + + private Session mockSession; + private FluentAeroMapper fluentMapper; + + @BeforeEach + public void setUp() { + ClassCache.getInstance().clear(); + mockSession = mock(Session.class); + fluentMapper = new FluentAeroMapper(mockSession); + } + + @AfterEach + public void tearDown() { + ClassCache.getInstance().clear(); + } + + // ── Construction ────────────────────────────────────────────────────────── + + @Test + public void getSession_returnsMockedSession() { + assertSame(mockSession, fluentMapper.getSession()); + } + + @Test + public void getMappingConverter_returnsNonNull() { + IRecordConverter converter = fluentMapper.getMappingConverter(); + assertNotNull(converter); + } + + @Test + public void getRecordLoader_returnsNonNull() { + RecordLoader loader = fluentMapper.getRecordLoader(); + assertNotNull(loader); + } + + // ── getRecordMapper ─────────────────────────────────────────────────────── + + @Test + public void getRecordMapper_returnsFluentRecordMapper() { + FluentRecordMapper recordMapper = fluentMapper.getRecordMapper(SamplePojo.class); + assertNotNull(recordMapper); + } + + @Test + public void getRecordMapper_cachedAcrossCalls() { + FluentRecordMapper first = fluentMapper.getRecordMapper(SamplePojo.class); + FluentRecordMapper second = fluentMapper.getRecordMapper(SamplePojo.class); + assertSame(first, second, "getRecordMapper should return cached instance"); + } + + // ── getMapper (RecordMappingFactory interface) ───────────────────────────── + + @Test + public void getMapper_returnsRecordMapper() { + RecordMapper rm = fluentMapper.getMapper(SamplePojo.class); + assertNotNull(rm); + assertInstanceOf(FluentRecordMapper.class, rm); + } + + @Test + public void getMapper_isConsistentWithGetRecordMapper() { + RecordMapper viaInterface = fluentMapper.getMapper(SamplePojo.class); + FluentRecordMapper viaDirect = fluentMapper.getRecordMapper(SamplePojo.class); + assertInstanceOf(FluentRecordMapper.class, viaInterface); + assertSame(viaInterface, viaDirect, "getMapper and getRecordMapper should return the same cached instance"); + } + + // ── Null validation ─────────────────────────────────────────────────────── + + @Test + public void constructor_nullSession_throws() { + AerospikeMapperException ex = assertThrows( + AerospikeMapperException.class, + () -> new FluentAeroMapper(null)); + assertTrue(ex.getMessage().contains("Session must not be null")); + } +} diff --git a/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordLoaderTest.java b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordLoaderTest.java new file mode 100644 index 0000000..90666ee --- /dev/null +++ b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordLoaderTest.java @@ -0,0 +1,127 @@ +package com.aerospike.mapper.tools.fluent; + +import com.aerospike.client.fluent.Key; +import com.aerospike.client.fluent.Record; +import com.aerospike.client.fluent.RecordStream; +import com.aerospike.client.fluent.Session; +import com.aerospike.client.fluent.query.KeyBasedQueryBuilderInterface; +import com.aerospike.mapper.exceptions.AerospikeMapperException; +import com.aerospike.mapper.tools.RecordKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class FluentRecordLoaderTest { + + private Session session; + private FluentRecordLoader loader; + + @BeforeEach + public void setUp() { + session = mock(Session.class); + loader = new FluentRecordLoader(session); + } + + private void mockSingleRecordQuery(Record record) { + KeyBasedQueryBuilderInterface qb = mock(KeyBasedQueryBuilderInterface.class); + RecordStream rs = mock(RecordStream.class); + when(session.query(any(Key.class))).thenReturn(qb); + when(qb.executeSync()).thenReturn(rs); + when(rs.getFirstRecord()).thenReturn(record); + } + + // ── getRecord ───────────────────────────────────────────────────── + + @Test + public void getRecord_returnsNull_whenNotFound() { + mockSingleRecordQuery(null); + Map result = loader.getRecord("ns", "set", "key1"); + assertNull(result); + } + + @Test + public void getRecord_returnsBins_whenFound() { + Map bins = new HashMap<>(); + bins.put("name", "Alice"); + Record record = new Record(bins, 1, 0); + mockSingleRecordQuery(record); + + Map result = loader.getRecord("ns", "set", "key1"); + + assertNotNull(result); + assertEquals("Alice", result.get("name")); + assertNotSame(bins, result, "Should return a defensive copy"); + } + + // ── getRecordByDigest ───────────────────────────────────────────── + + @Test + public void getRecordByDigest_returnsNull() { + mockSingleRecordQuery(null); + Map result = loader.getRecordByDigest("ns", "set", new byte[20]); + assertNull(result); + } + + // ── getBatchRecords ─────────────────────────────────────────────── + + @Test + public void getBatchRecords_emptyList_returnsEmpty() { + List> results = loader.getBatchRecords(new ArrayList<>()); + assertTrue(results.isEmpty()); + } + + @Test + public void getBatchRecords_exceptionWrapped() { + KeyBasedQueryBuilderInterface qb = mock(KeyBasedQueryBuilderInterface.class); + when(session.query(any(List.class))).thenReturn(qb); + when(qb.executeSync()).thenThrow(new RuntimeException("connection failed")); + + List keys = List.of(new RecordKey("ns", "set", "k1")); + + AerospikeMapperException ex = assertThrows( + AerospikeMapperException.class, + () -> loader.getBatchRecords(keys)); + assertTrue(ex.getMessage().contains("Batch record fetch failed")); + assertNotNull(ex.getCause()); + } + + @Test + public void getBatchRecords_mapperExceptionNotWrapped() { + KeyBasedQueryBuilderInterface qb = mock(KeyBasedQueryBuilderInterface.class); + when(session.query(any(List.class))).thenReturn(qb); + AerospikeMapperException original = new AerospikeMapperException("mapper error"); + when(qb.executeSync()).thenThrow(original); + + List keys = List.of(new RecordKey("ns", "set", "k1")); + + AerospikeMapperException ex = assertThrows( + AerospikeMapperException.class, + () -> loader.getBatchRecords(keys)); + assertSame(original, ex, "AerospikeMapperException should not be wrapped"); + } + + // ── computeDigest ───────────────────────────────────────────────── + + @Test + public void computeDigest_returnsNonNullBytes() { + byte[] digest = loader.computeDigest("mySet", "myKey"); + assertNotNull(digest); + assertTrue(digest.length > 0); + } + + @Test + public void computeDigest_sameInputSameOutput() { + byte[] d1 = loader.computeDigest("set", 42); + byte[] d2 = loader.computeDigest("set", 42); + assertArrayEquals(d1, d2); + } +} diff --git a/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordMapperTest.java b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordMapperTest.java new file mode 100644 index 0000000..0fc4386 --- /dev/null +++ b/fluentapi/src/test/java/com/aerospike/mapper/tools/fluent/FluentRecordMapperTest.java @@ -0,0 +1,180 @@ +package com.aerospike.mapper.tools.fluent; + +import com.aerospike.client.fluent.Key; +import com.aerospike.client.fluent.Value; +import com.aerospike.mapper.tools.ClassCacheEntry; +import com.aerospike.mapper.tools.converters.MappingConverter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import org.mockito.ArgumentMatchers; +import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.eq; + +/** + * Unit tests for {@link FluentRecordMapper}. + * Uses mocked ClassCacheEntry and MappingConverter — no Aerospike server needed. + */ +@SuppressWarnings("unchecked") +public class FluentRecordMapperTest { + + private static class Pojo { + int id; + String name; + } + + private ClassCacheEntry entry; + private MappingConverter converter; + private FluentRecordMapper mapper; + + @BeforeEach + public void setUp() { + entry = mock(ClassCacheEntry.class); + converter = mock(MappingConverter.class); + mapper = new FluentRecordMapper<>(entry, converter); + } + + // ── id ──────────────────────────────────────────────────────────────────── + + @Test + public void id_delegatesToEntry() { + Pojo pojo = new Pojo(); + when(entry.getKey(pojo)).thenReturn(42); + + Object result = mapper.id(pojo); + + assertEquals(42, result); + verify(entry).getKey(pojo); + } + + // ── toMap ───────────────────────────────────────────────────────────────── + + @Test + public void toMap_convertsRawValuesToFluentValues() { + Pojo pojo = new Pojo(); + Map rawMap = new HashMap<>(); + rawMap.put("id", 1); + rawMap.put("name", "Alice"); + when(entry.getMap(pojo, false)).thenReturn(rawMap); + + Map result = mapper.toMap(pojo); + + assertEquals(rawMap.size(), result.size()); + assertTrue(result.containsKey("id")); + assertTrue(result.containsKey("name")); + // Each value must be a proper fluent Value instance + assertNotNull(result.get("id")); + assertNotNull(result.get("name")); + } + + @Test + public void toMap_emptyObject_returnsEmptyMap() { + Pojo pojo = new Pojo(); + when(entry.getMap(pojo, false)).thenReturn(new HashMap<>()); + + Map result = mapper.toMap(pojo); + + assertTrue(result.isEmpty()); + } + + // ── fromMap ─────────────────────────────────────────────────────────────── + + @Test + public void fromMap_noKeyInjection_whenKeyFieldNameIsNull() { + // When there is no key field name, the bins map goes to converter as-is + Map bins = new HashMap<>(); + bins.put("name", "Alice"); + Key key = new Key("test", "s", "k1"); + Pojo expected = new Pojo(); + + when(entry.getKeyFieldName()).thenReturn(null); + when(entry.getUnderlyingClass()).thenReturn((Class) Pojo.class); + when(converter.convertToObject(Pojo.class, bins)).thenReturn(expected); + + Pojo result = mapper.fromMap(bins, key, 1); + + assertSame(expected, result); + // The exact same map is passed (no copy was made) + verify(converter).convertToObject(Pojo.class, bins); + } + + @Test + public void fromMap_noKeyInjection_whenKeyStoredAsBin() { + Map bins = new HashMap<>(); + bins.put("id", 7); + Key key = new Key("test", "s", "k2"); + Pojo expected = new Pojo(); + + when(entry.getKeyFieldName()).thenReturn("id"); + when(entry.isKeyFieldStoredAsBin()).thenReturn(true); + when(entry.getUnderlyingClass()).thenReturn((Class) Pojo.class); + when(converter.convertToObject(Pojo.class, bins)).thenReturn(expected); + + Pojo result = mapper.fromMap(bins, key, 0); + + assertSame(expected, result); + // bins passed unchanged — key already present as a bin + verify(converter).convertToObject(Pojo.class, bins); + } + + @Test + public void fromMap_injectsKeyWhenSendKeyScenario() { + // sendKey scenario: keyFieldName != null, not stored as bin, key.userKey != null + Map bins = new HashMap<>(); + bins.put("name", "Bob"); + Key key = new Key("test", "s", "bob-key"); // userKey = StringValue("bob-key") + Pojo expected = new Pojo(); + + when(entry.getKeyFieldName()).thenReturn("id"); + when(entry.isKeyFieldStoredAsBin()).thenReturn(false); + when(entry.getUnderlyingClass()).thenReturn((Class) Pojo.class); + when(converter.convertToObject(eq(Pojo.class), + ArgumentMatchers.>argThat(m -> + m.containsKey("id") && "bob-key".equals(m.get("id"))) + )).thenReturn(expected); + + Pojo result = mapper.fromMap(bins, key, 3); + + assertSame(expected, result); + // Original bins map must not be mutated + assertFalse(bins.containsKey("id")); + } + + @Test + public void fromMap_setsGenerationOnResult() { + Map bins = new HashMap<>(); + Key key = new Key("test", "s", "gkey"); + Pojo expected = new Pojo(); + + when(entry.getKeyFieldName()).thenReturn(null); + when(entry.getUnderlyingClass()).thenReturn((Class) Pojo.class); + when(converter.convertToObject(Pojo.class, bins)).thenReturn(expected); + + mapper.fromMap(bins, key, 7); + + verify(entry).setGenerationValue(expected, 7); + } + + @Test + public void fromMap_noKeyInjection_whenUserKeyIsNull() { + // Key created from a digest has no userKey + Map bins = new HashMap<>(); + Key key = new Key("test", new byte[20], "digestSet", null); + Pojo expected = new Pojo(); + + when(entry.getKeyFieldName()).thenReturn("id"); + when(entry.isKeyFieldStoredAsBin()).thenReturn(false); + when(entry.getUnderlyingClass()).thenReturn((Class) Pojo.class); + when(converter.convertToObject(Pojo.class, bins)).thenReturn(expected); + + Pojo result = mapper.fromMap(bins, key, 0); + + assertSame(expected, result); + // No key injected because userKey is null + verify(converter).convertToObject(Pojo.class, bins); + } +} diff --git a/legacyapi/pom.xml b/legacyapi/pom.xml new file mode 100644 index 0000000..73361a5 --- /dev/null +++ b/legacyapi/pom.xml @@ -0,0 +1,75 @@ + + 4.0.0 + + + com.aerospike + java-object-mapper-parent + 2.6.0 + + + java-object-mapper + jar + + Aerospike Object Mapper + Aerospike Object Mapper using the legacy (aerospike-client-jdk8) Aerospike Java client. + + + + com.aerospike + java-object-mapper-core + + + com.aerospike + aerospike-client-jdk8 + + + com.aerospike + aerospike-reactor-client + + + org.projectlombok + lombok + + + io.projectreactor + reactor-test + + + org.junit.jupiter + junit-jupiter + + + org.mockito + mockito-core + + + + + ${project.basedir}/src/main/java + ${project.basedir}/src/test/java + + + maven-compiler-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + ${project.basedir}/src/main/java + + **/*.properties + **/*.xml + + + + + diff --git a/src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java similarity index 95% rename from src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java index 9978291..5a2d7a8 100644 --- a/src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/AbstractBuilder.java @@ -22,7 +22,7 @@ import com.aerospike.client.policy.QueryPolicy; import com.aerospike.client.policy.ScanPolicy; import com.aerospike.mapper.annotations.AerospikeRecord; -import com.aerospike.mapper.tools.ClassCache.PolicyType; +import com.aerospike.mapper.tools.PolicyCache.PolicyType; import com.aerospike.mapper.tools.configuration.ClassConfig; import com.aerospike.mapper.tools.configuration.Configuration; import com.aerospike.mapper.tools.utils.TypeUtils; @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +@SuppressWarnings("unused") public abstract class AbstractBuilder { private final T mapper; private List> classesToPreload = null; @@ -95,6 +96,9 @@ public AbstractBuilder preLoadClassesFromPackage(String thePackage) { private Set> findAllClassesUsingClassLoader(String packageName) { InputStream stream = ClassLoader.getSystemClassLoader() .getResourceAsStream(packageName.replaceAll("[.]", "/")); + if (stream == null) { + throw new AerospikeException("Package not found: " + packageName); + } BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); return reader.lines().filter(line -> line.endsWith(".class")).map(line -> getClass(line, packageName)) .collect(Collectors.toSet()); @@ -197,18 +201,18 @@ public AeroPolicyMapper(AbstractBuilder builder, PolicyType policyType, Polic public AbstractBuilder forClasses(Class... classes) { for (Class thisClass : classes) { - ClassCache.getInstance().setSpecificPolicy(policyType, thisClass, policy); + PolicyCache.getInstance().setSpecificPolicy(policyType, thisClass, policy); } return builder; } public AbstractBuilder forThisOrChildrenOf(Class clazz) { - ClassCache.getInstance().setChildrenPolicy(this.policyType, clazz, this.policy); + PolicyCache.getInstance().setChildrenPolicy(this.policyType, clazz, this.policy); return builder; } public AbstractBuilder forAll() { - ClassCache.getInstance().setDefaultPolicy(policyType, policy); + PolicyCache.getInstance().setDefaultPolicy(policyType, policy); return builder; } } diff --git a/src/main/java/com/aerospike/mapper/tools/AeroMapper.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/AeroMapper.java similarity index 77% rename from src/main/java/com/aerospike/mapper/tools/AeroMapper.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/AeroMapper.java index d7d27bf..3998dae 100644 --- a/src/main/java/com/aerospike/mapper/tools/AeroMapper.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/AeroMapper.java @@ -18,7 +18,8 @@ import com.aerospike.client.query.Filter; import com.aerospike.client.query.RecordSet; import com.aerospike.client.query.Statement; -import com.aerospike.mapper.tools.ClassCache.PolicyType; +import com.aerospike.client.cdt.MapOrder; +import com.aerospike.mapper.tools.PolicyCache.PolicyType; import com.aerospike.mapper.tools.converters.MappingConverter; import com.aerospike.mapper.tools.utils.MapperUtils; import com.aerospike.mapper.tools.virtuallist.VirtualList; @@ -26,7 +27,10 @@ import javax.validation.constraints.NotNull; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; @@ -34,10 +38,12 @@ public class AeroMapper implements IAeroMapper { private final IAerospikeClient mClient; private final MappingConverter mappingConverter; + private final RecordLoader recordLoader; private AeroMapper(@NotNull IAerospikeClient client) { this.mClient = client; - this.mappingConverter = new MappingConverter(this, mClient); + this.recordLoader = new AerospikeRecordLoader(client); + this.mappingConverter = new MappingConverter(this, this.recordLoader); } /** @@ -48,13 +54,14 @@ private AeroMapper(@NotNull IAerospikeClient client) { public static class Builder extends AbstractBuilder { public Builder(IAerospikeClient client) { super(new AeroMapper(client)); - ClassCache.getInstance().setDefaultPolicies(client); + PolicyCache.getInstance().setDefaultPolicies(client); } } + @SafeVarargs @Override - public void save(@NotNull T... objects) throws AerospikeException { + public final void save(@NotNull T... objects) throws AerospikeException { for (T thisObject : objects) { this.save(thisObject); } @@ -85,7 +92,7 @@ public void save(@NotNull WritePolicy writePolicy, @NotNull T object, String } Key key = new Key(entry.getNamespace(), set, Value.get(entry.getKey(object))); - Bin[] bins = entry.getBins(object, writePolicy.recordExistsAction != RecordExistsAction.REPLACE, binNames); + Bin[] bins = toBins(entry, object, writePolicy.recordExistsAction != RecordExistsAction.REPLACE, binNames); mClient.put(writePolicy, key, bins); } @@ -109,7 +116,7 @@ private WritePolicy generateWritePolicyFromObject(T object) { Class clazz = (Class) object.getClass(); ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); - WritePolicy writePolicy = new WritePolicy(entry.getWritePolicy()); + WritePolicy writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(clazz, mClient.getWritePolicyDefault()); // #132 -- Ensure that if an overriding TTL / sendKey is passed in the policy it // is NOT overwritten. Hence, only if the policy is null do we override these settings. @@ -231,7 +238,7 @@ private T read(Policy readPolicy, @NotNull Class clazz, @NotNull Key key, } } if (readPolicy == null) { - readPolicy = entry.getReadPolicy(); + readPolicy = PolicyCache.getInstance().getEffectiveReadPolicy(clazz, mClient.getReadPolicyDefault()); } Record record = mClient.get(readPolicy, key); @@ -239,8 +246,14 @@ private T read(Policy readPolicy, @NotNull Class clazz, @NotNull Key key, return null; } else { try { - ThreadLocalKeySaver.save(key); - return mappingConverter.convertToObject(clazz, key, record, entry, resolveDependencies); + ThreadLocalKeySaver.save(key, key.userKey); + Map map = recordToMap(entry, key, record); + T result = entry.constructAndHydrate(map); + entry.setGenerationValue(result, record.generation); + if (resolveDependencies) { + mappingConverter.resolveDependencies(entry); + } + return result; } finally { ThreadLocalKeySaver.clear(); } @@ -251,7 +264,7 @@ private T read(Policy readPolicy, @NotNull Class clazz, @NotNull Key key, private T[] readBatch(BatchPolicy batchPolicy, @NotNull Class clazz, @NotNull Key[] keys, @NotNull ClassCacheEntry entry, Operation... operations) { if (batchPolicy == null) { - batchPolicy = entry.getBatchPolicy(); + batchPolicy = PolicyCache.getInstance().getEffectiveBatchPolicy(clazz, mClient.getBatchPolicyDefault()); } Record[] records; @@ -267,8 +280,10 @@ private T[] readBatch(BatchPolicy batchPolicy, @NotNull Class clazz, @Not results[i] = null; } else { try { - ThreadLocalKeySaver.save(keys[i]); - T result = mappingConverter.convertToObject(clazz, keys[i], records[i], entry, false); + ThreadLocalKeySaver.save(keys[i], keys[i].userKey); + Map map = recordToMap(entry, keys[i], records[i]); + T result = entry.constructAndHydrate(map); + entry.setGenerationValue(result, records[i].generation); results[i] = result; } finally { ThreadLocalKeySaver.clear(); @@ -291,9 +306,8 @@ public boolean delete(WritePolicy writePolicy, @NotNull Class clazz, @Not Object asKey = entry.translateKeyToAerospikeKey(userKey); if (writePolicy == null) { - writePolicy = entry.getWritePolicy(); + writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(clazz, mClient.getWritePolicyDefault()); if (entry.getDurableDelete() != null) { - // Clone the write policy so we're not changing the original one writePolicy = new WritePolicy(writePolicy); writePolicy.durableDelete = entry.getDurableDelete(); } @@ -314,7 +328,7 @@ public boolean delete(WritePolicy writePolicy, @NotNull Object object) throws Ae Key key = new Key(entry.getNamespace(), entry.getSetName(), Value.get(entry.getKey(object))); if (writePolicy == null) { - writePolicy = entry.getWritePolicy(); + writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(object.getClass(), mClient.getWritePolicyDefault()); if (entry.getDurableDelete() != null) { writePolicy = new WritePolicy(writePolicy); writePolicy.durableDelete = entry.getDurableDelete(); @@ -331,24 +345,19 @@ public void find(@NotNull Class clazz, Function function) thr statement.setNamespace(entry.getNamespace()); statement.setSetName(entry.getSetName()); - RecordSet recordSet = null; - try { + try (RecordSet recordSet = mClient.query(null, statement)) { // TODO: set the policy (If this statement is thought to be useful, which is dubious) - recordSet = mClient.query(null, statement); T result; while (recordSet.next()) { result = clazz.getConstructor().newInstance(); - entry.hydrateFromRecord(recordSet.getRecord(), result); + Map map = recordToMap(entry, recordSet.getKey(), recordSet.getRecord()); + entry.hydrateFromMap(map, result); if (!function.apply(result)) { break; } } } catch (ReflectiveOperationException e) { throw new AerospikeException(e); - } finally { - if (recordSet != null) { - recordSet.close(); - } } } @@ -372,7 +381,7 @@ public void scan(ScanPolicy policy, @NotNull Class clazz, @NotNull Proces int recordsPerSecond) { ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); if (policy == null) { - policy = entry.getScanPolicy(); + policy = PolicyCache.getInstance().getEffectiveScanPolicy(clazz, mClient.getScanPolicyDefault()); } if (recordsPerSecond >= 0) { // Ensure the underlying rate on the policy does not change @@ -385,7 +394,8 @@ public void scan(ScanPolicy policy, @NotNull Class clazz, @NotNull Proces AtomicBoolean userTerminated = new AtomicBoolean(false); try { mClient.scanAll(policy, namespace, setName, (key, record) -> { - T object = this.getMappingConverter().convertToObject(clazz, key, record); + Map map = recordToMap(entry, key, record); + T object = mappingConverter.convertToObject(clazz, map); if (!processor.process(object)) { userTerminated.set(true); throw new AerospikeException.ScanTerminated(); @@ -425,23 +435,21 @@ public void query(@NotNull Class clazz, @NotNull Processor processor, public void query(QueryPolicy policy, @NotNull Class clazz, @NotNull Processor processor, Filter filter) { ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); if (policy == null) { - policy = entry.getQueryPolicy(); + policy = PolicyCache.getInstance().getEffectiveQueryPolicy(clazz, mClient.getQueryPolicyDefault()); } Statement statement = new Statement(); statement.setFilter(filter); statement.setNamespace(entry.getNamespace()); statement.setSetName(entry.getSetName()); - RecordSet recordSet = mClient.query(policy, statement); - try { + try (RecordSet recordSet = mClient.query(policy, statement)) { while (recordSet.next()) { - T object = this.getMappingConverter().convertToObject(clazz, recordSet.getKey(), recordSet.getRecord()); + Map map = recordToMap(entry, recordSet.getKey(), recordSet.getRecord()); + T object = mappingConverter.convertToObject(clazz, map); if (!processor.process(object)) { break; } } - } finally { - recordSet.close(); } } @@ -482,6 +490,11 @@ public MappingConverter getMappingConverter() { return this.mappingConverter; } + @Override + public RecordLoader getRecordLoader() { + return this.recordLoader; + } + @Override public IAeroMapper asMapper() { return this; @@ -513,24 +526,73 @@ public QueryPolicy getQueryPolicy(Class clazz) { } private Policy getPolicyByClassAndType(Class clazz, PolicyType policyType) { - ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this); - switch (policyType) { case READ: - return entry == null ? mClient.getReadPolicyDefault() : entry.getReadPolicy(); + return PolicyCache.getInstance().getEffectiveReadPolicy(clazz, mClient.getReadPolicyDefault()); case WRITE: - return entry == null ? mClient.getWritePolicyDefault() : entry.getWritePolicy(); + return PolicyCache.getInstance().getEffectiveWritePolicy(clazz, mClient.getWritePolicyDefault()); case BATCH: - return entry == null ? mClient.getBatchPolicyDefault() : entry.getBatchPolicy(); + return PolicyCache.getInstance().getEffectiveBatchPolicy(clazz, mClient.getBatchPolicyDefault()); case SCAN: - return entry == null ? mClient.getScanPolicyDefault() : entry.getScanPolicy(); + return PolicyCache.getInstance().getEffectiveScanPolicy(clazz, mClient.getScanPolicyDefault()); case QUERY: - return entry == null ? mClient.getQueryPolicyDefault() : entry.getQueryPolicy(); + return PolicyCache.getInstance().getEffectiveQueryPolicy(clazz, mClient.getQueryPolicyDefault()); default: throw new UnsupportedOperationException("Provided unsupported policy type: " + policyType); } } + /** + * Builds an Aerospike map representation of an object ready for writing, excluding non-bin key + * fields and applying optional bin name filtering. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + static Bin[] toBins(ClassCacheEntry entry, Object instance, boolean allowNullBins, String[] binNames) { + Map map = entry.getMap(instance, false); + // Exclude key field if it is not stored as a bin + String keyFieldName = entry.getKeyFieldName(); + if (keyFieldName != null && !entry.isKeyFieldStoredAsBin()) { + map.remove(keyFieldName); + } + List binList = new ArrayList<>(); + for (Map.Entry e : map.entrySet()) { + if (!containsBinName(binNames, e.getKey())) continue; + Object value = e.getValue(); + if (value == null && !allowNullBins) continue; + if (value instanceof TreeMap) { + TreeMap treeMap = (TreeMap) value; + binList.add(new Bin(e.getKey(), new ArrayList(treeMap.entrySet()), MapOrder.KEY_ORDERED)); + } else { + binList.add(new Bin(e.getKey(), Value.get(value))); + } + } + return binList.toArray(new Bin[0]); + } + + private static boolean containsBinName(String[] names, String thisName) { + if (names == null || names.length == 0) return true; + if (thisName == null) return false; + for (String name : names) { + if (thisName.equals(name)) return true; + } + return false; + } + + /** + * Converts an Aerospike Record to a bin map, injecting the user key value for classes + * that use sendKey=true (keyAsBin=false). + */ + static Map recordToMap(ClassCacheEntry entry, Key key, Record record) { + Map map = new HashMap<>(record.bins); + if (!entry.isKeyFieldStoredAsBin()) { + String keyFieldName = entry.getKeyFieldName(); + if (keyFieldName != null && key != null && key.userKey != null) { + map.put(keyFieldName, key.userKey.getObject()); + } + } + return map; + } + @Override public String getNamespace(Class clazz) { ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this); diff --git a/legacyapi/src/main/java/com/aerospike/mapper/tools/AerospikeRecordLoader.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/AerospikeRecordLoader.java new file mode 100644 index 0000000..c6efae2 --- /dev/null +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/AerospikeRecordLoader.java @@ -0,0 +1,79 @@ +package com.aerospike.mapper.tools; + +import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.Value; +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.policy.Policy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements {@link RecordLoader} using the legacy {@link IAerospikeClient}. + * Uses {@link PolicyCache} for policy resolution so that user-overridden default policies are respected. + */ +public class AerospikeRecordLoader implements RecordLoader { + + private final IAerospikeClient client; + + public AerospikeRecordLoader(IAerospikeClient client) { + this.client = client; + } + + @Override + public Map getRecord(String namespace, String setName, Object keyValue) { + Key key = new Key(namespace, setName, Value.get(keyValue)); + Policy readPolicy = resolveReadPolicy(); + Record record = client.get(readPolicy, key); + return record == null ? null : new HashMap<>(record.bins); + } + + @Override + public Map getRecordByDigest(String namespace, String setName, byte[] digest) { + Key key = new Key(namespace, digest, setName, null); + Policy readPolicy = resolveReadPolicy(); + Record record = client.get(readPolicy, key); + return record == null ? null : new HashMap<>(record.bins); + } + + @Override + public List> getBatchRecords(List keys) { + Key[] aerospikeKeys = new Key[keys.size()]; + for (int i = 0; i < keys.size(); i++) { + RecordKey rk = keys.get(i); + if (rk.digest != null) { + aerospikeKeys[i] = new Key(rk.namespace, rk.digest, rk.setName, null); + } else { + aerospikeKeys[i] = new Key(rk.namespace, rk.setName, Value.get(rk.keyValue)); + } + } + + BatchPolicy batchPolicy = PolicyCache.getInstance() + .getEffectiveBatchPolicy(Object.class, client.getBatchPolicyDefault()); + if (keys.size() <= 2) { + batchPolicy = new BatchPolicy(batchPolicy); + batchPolicy.maxConcurrentThreads = 1; + } + + Record[] records = client.get(batchPolicy, aerospikeKeys); + + List> result = new ArrayList<>(records.length); + for (Record r : records) { + result.add(r == null ? null : new HashMap<>(r.bins)); + } + return result; + } + + @Override + public byte[] computeDigest(String setName, Object userKey) { + return com.aerospike.client.util.Crypto.computeDigest(setName, Value.get(userKey)); + } + + private Policy resolveReadPolicy() { + return PolicyCache.getInstance().getEffectiveReadPolicy(Object.class, client.getReadPolicyDefault()); + } +} diff --git a/src/main/java/com/aerospike/mapper/tools/IAeroMapper.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/IAeroMapper.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/IAeroMapper.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/IAeroMapper.java diff --git a/src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java similarity index 90% rename from src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java index 4a8bce4..327d4b0 100644 --- a/src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/IBaseAeroMapper.java @@ -7,10 +7,17 @@ import com.aerospike.client.policy.QueryPolicy; import com.aerospike.mapper.tools.converters.MappingConverter; -public interface IBaseAeroMapper { +@SuppressWarnings("unused") +public interface IBaseAeroMapper extends IObjectMapper { + @Override MappingConverter getMappingConverter(); + @Override + default SetterParamTypeResolver getSetterParamTypeResolver() { + return LegacySetterParamTypeResolver.INSTANCE; + } + IAeroMapper asMapper(); /** diff --git a/src/main/java/com/aerospike/mapper/tools/IReactiveAeroMapper.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/IReactiveAeroMapper.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/IReactiveAeroMapper.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/IReactiveAeroMapper.java diff --git a/legacyapi/src/main/java/com/aerospike/mapper/tools/LegacySetterParamTypeResolver.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/LegacySetterParamTypeResolver.java new file mode 100644 index 0000000..57e3dac --- /dev/null +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/LegacySetterParamTypeResolver.java @@ -0,0 +1,25 @@ +package com.aerospike.mapper.tools; + +import com.aerospike.mapper.tools.PropertyDefinition.SetterParamType; + +/** + * Recognizes the legacy Aerospike Java client's Key and Value types as valid second-parameter types for 2-argument + * property setters. + */ +public class LegacySetterParamTypeResolver implements SetterParamTypeResolver { + + public static final LegacySetterParamTypeResolver INSTANCE = new LegacySetterParamTypeResolver(); + + private static final String LEGACY_KEY = "com.aerospike.client.Key"; + private static final String LEGACY_VALUE = "com.aerospike.client.Value"; + + @Override + public SetterParamType resolve(String paramTypeName) { + if (LEGACY_KEY.equals(paramTypeName)) { + return SetterParamType.KEY; + } else if (LEGACY_VALUE.equals(paramTypeName)) { + return SetterParamType.VALUE; + } + return SetterParamType.NONE; + } +} diff --git a/legacyapi/src/main/java/com/aerospike/mapper/tools/PolicyCache.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/PolicyCache.java new file mode 100644 index 0000000..97b2a0d --- /dev/null +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/PolicyCache.java @@ -0,0 +1,134 @@ +package com.aerospike.mapper.tools; + +import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.reactor.IAerospikeReactorClient; +import lombok.Getter; + +import java.util.EnumMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Stores per-class and default Aerospike client policies. + * Thread-safe singleton for policy resolution across mapper instances. + */ +public class PolicyCache { + + public enum PolicyType { + READ, + WRITE, + BATCH, + SCAN, + QUERY + } + + @Getter + private static final PolicyCache instance = new PolicyCache(); + private final Map defaultPolicies = new ConcurrentHashMap<>(); + private final EnumMap, Policy>> childrenPolicies = + new EnumMap<>(PolicyType.class); + private final EnumMap, Policy>> specificPolicies = + new EnumMap<>(PolicyType.class); + + private PolicyCache() { + for (PolicyType thisType : PolicyType.values()) { + this.childrenPolicies.put(thisType, new ConcurrentHashMap<>()); + this.specificPolicies.put(thisType, new ConcurrentHashMap<>()); + } + } + + public void clear() { + defaultPolicies.clear(); + for (PolicyType type : PolicyType.values()) { + childrenPolicies.get(type).clear(); + specificPolicies.get(type).clear(); + } + } + + public void setDefaultPolicies(IAerospikeClient client) { + if (client != null) { + setDefault(client.getReadPolicyDefault(), client.getWritePolicyDefault(), client.getBatchPolicyDefault(), + client.getQueryPolicyDefault(), client.getScanPolicyDefault()); + } + } + + private void setDefault(Policy readPolicyDefault, WritePolicy writePolicyDefault, BatchPolicy batchPolicyDefault, + QueryPolicy queryPolicyDefault, ScanPolicy scanPolicyDefault) { + this.defaultPolicies.put(PolicyType.READ, readPolicyDefault); + this.defaultPolicies.put(PolicyType.WRITE, writePolicyDefault); + this.defaultPolicies.put(PolicyType.BATCH, batchPolicyDefault); + this.defaultPolicies.put(PolicyType.QUERY, queryPolicyDefault); + this.defaultPolicies.put(PolicyType.SCAN, scanPolicyDefault); + } + + public void setReactiveDefaultPolicies(IAerospikeReactorClient reactorClient) { + setDefault(reactorClient.getReadPolicyDefault(), reactorClient.getWritePolicyDefault(), + reactorClient.getBatchPolicyDefault(), reactorClient.getQueryPolicyDefault(), + reactorClient.getScanPolicyDefault()); + } + + public void setDefaultPolicy(PolicyType policyType, Policy policy) { + this.defaultPolicies.put(policyType, policy); + } + + public void setChildrenPolicy(PolicyType policyType, Class parentClass, Policy policy) { + this.childrenPolicies.get(policyType).put(parentClass, policy); + } + + public void setSpecificPolicy(PolicyType policyType, Class clazz, Policy policy) { + this.specificPolicies.get(policyType).put(clazz, policy); + } + + public Policy determinePolicy(Class clazz, PolicyType policyType) { + if (clazz != null) { + Policy result = specificPolicies.get(policyType).get(clazz); + if (result != null) { + return result; + } + Class thisClass = clazz; + while (thisClass != null) { + Policy aPolicy = childrenPolicies.get(policyType).get(thisClass); + if (aPolicy != null) { + return aPolicy; + } + thisClass = thisClass.getSuperclass(); + } + } + return this.defaultPolicies.get(policyType); + } + + /** Returns the effective write policy for the given class, falling back to clientDefault. */ + public WritePolicy getEffectiveWritePolicy(Class clazz, WritePolicy clientDefault) { + Policy p = determinePolicy(clazz, PolicyType.WRITE); + return new WritePolicy(p != null ? (WritePolicy) p : clientDefault); + } + + /** Returns the effective read policy for the given class, falling back to clientDefault. */ + public Policy getEffectiveReadPolicy(Class clazz, Policy clientDefault) { + Policy p = determinePolicy(clazz, PolicyType.READ); + return p != null ? p : clientDefault; + } + + /** Returns the effective batch policy for the given class, falling back to clientDefault. */ + public BatchPolicy getEffectiveBatchPolicy(Class clazz, BatchPolicy clientDefault) { + Policy p = determinePolicy(clazz, PolicyType.BATCH); + return p != null ? (BatchPolicy) p : clientDefault; + } + + /** Returns the effective scan policy for the given class, falling back to clientDefault. */ + public ScanPolicy getEffectiveScanPolicy(Class clazz, ScanPolicy clientDefault) { + Policy p = determinePolicy(clazz, PolicyType.SCAN); + return p != null ? (ScanPolicy) p : clientDefault; + } + + /** Returns the effective query policy for the given class, falling back to clientDefault. */ + public QueryPolicy getEffectiveQueryPolicy(Class clazz, QueryPolicy clientDefault) { + Policy p = determinePolicy(clazz, PolicyType.QUERY); + return p != null ? (QueryPolicy) p : clientDefault; + } +} diff --git a/src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java similarity index 81% rename from src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java index f18bebd..05eeb85 100644 --- a/src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java @@ -24,6 +24,7 @@ import javax.validation.constraints.NotNull; import java.util.Arrays; +import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -32,11 +33,13 @@ public class ReactiveAeroMapper implements IReactiveAeroMapper { private final IAerospikeReactorClient reactorClient; private final IAeroMapper aeroMapper; private final MappingConverter mappingConverter; + private final RecordLoader recordLoader; private ReactiveAeroMapper(@NotNull IAerospikeReactorClient reactorClient) { this.reactorClient = reactorClient; this.aeroMapper = new AeroMapper.Builder(reactorClient.getAerospikeClient()).build(); - this.mappingConverter = new MappingConverter(this, reactorClient.getAerospikeClient()); + this.recordLoader = new AerospikeRecordLoader(reactorClient.getAerospikeClient()); + this.mappingConverter = new MappingConverter(this, this.recordLoader); } @Override @@ -69,7 +72,7 @@ public Mono save(@NotNull WritePolicy writePolicy, @NotNull T object, Str } Key key = new Key(entry.getNamespace(), set, Value.get(entry.getKey(object))); - Bin[] bins = entry.getBins(object, writePolicy.recordExistsAction != RecordExistsAction.REPLACE, binNames); + Bin[] bins = AeroMapper.toBins(entry, object, writePolicy.recordExistsAction != RecordExistsAction.REPLACE, binNames); return reactorClient .put(writePolicy, key, bins) @@ -95,7 +98,7 @@ private WritePolicy generateWritePolicyFromObject(T object) { Class clazz = (Class) object.getClass(); ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); - WritePolicy writePolicy = new WritePolicy(entry.getWritePolicy()); + WritePolicy writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(clazz, reactorClient.getWritePolicyDefault()); // #132 -- Ensure that if an overriding TTL / sendKey is passed in the policy it // is NOT overwritten. Hence, only if the policy is null do we override these settings. @@ -210,16 +213,23 @@ private Mono read(Policy readPolicy, @NotNull Class clazz, @NotNull Ke } } if (readPolicy == null) { - readPolicy = entry.getReadPolicy(); + readPolicy = PolicyCache.getInstance().getEffectiveReadPolicy(clazz, reactorClient.getReadPolicyDefault()); } + final Policy finalReadPolicy = readPolicy; return reactorClient - .get(readPolicy, key) + .get(finalReadPolicy, key) .filter(keyRecord -> Objects.nonNull(keyRecord.record)) .map(keyRecord -> { try { - ThreadLocalKeySaver.save(key); - return mappingConverter.convertToObject(clazz, key, keyRecord.record, entry, resolveDependencies); + ThreadLocalKeySaver.save(key, key.userKey); + Map map = AeroMapper.recordToMap(entry, key, keyRecord.record); + T result = entry.constructAndHydrate(map); + entry.setGenerationValue(result, keyRecord.record.generation); + if (resolveDependencies) { + mappingConverter.resolveDependencies(entry); + } + return result; } finally { ThreadLocalKeySaver.clear(); } @@ -229,7 +239,7 @@ private Mono read(Policy readPolicy, @NotNull Class clazz, @NotNull Ke private Flux readBatch(BatchPolicy batchPolicy, @NotNull Class clazz, @NotNull Key[] keys, @NotNull ClassCacheEntry entry, Operation... operations) { if (batchPolicy == null) { - batchPolicy = entry.getBatchPolicy(); + batchPolicy = PolicyCache.getInstance().getEffectiveBatchPolicy(clazz, reactorClient.getBatchPolicyDefault()); } Flux keyRecordFlux; @@ -245,8 +255,12 @@ private Flux readBatch(BatchPolicy batchPolicy, @NotNull Class clazz, return keyRecordFlux.filter(keyRecord -> Objects.nonNull(keyRecord.record)) .map(keyRecord -> { try { - ThreadLocalKeySaver.save(keyRecord.key); - return mappingConverter.convertToObject(clazz, keyRecord.key, keyRecord.record, entry, true); + ThreadLocalKeySaver.save(keyRecord.key, keyRecord.key.userKey); + Map map = AeroMapper.recordToMap(entry, keyRecord.key, keyRecord.record); + T result = entry.constructAndHydrate(map); + entry.setGenerationValue(result, keyRecord.record.generation); + mappingConverter.resolveDependencies(entry); + return result; } finally { ThreadLocalKeySaver.clear(); } @@ -264,9 +278,8 @@ public Mono delete(WritePolicy writePolicy, @NotNull Class clazz Object asKey = entry.translateKeyToAerospikeKey(userKey); if (writePolicy == null) { - writePolicy = entry.getWritePolicy(); + writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(clazz, reactorClient.getWritePolicyDefault()); if (entry.getDurableDelete() != null) { - // Clone the write policy, so we're not changing the original one writePolicy = new WritePolicy(writePolicy); writePolicy.durableDelete = entry.getDurableDelete(); } @@ -289,7 +302,7 @@ public Mono delete(WritePolicy writePolicy, @NotNull Object object) { Key key = new Key(entry.getNamespace(), entry.getSetName(), Value.get(entry.getKey(object))); if (writePolicy == null) { - writePolicy = entry.getWritePolicy(); + writePolicy = PolicyCache.getInstance().getEffectiveWritePolicy(object.getClass(), reactorClient.getWritePolicyDefault()); if (entry.getDurableDelete() != null) { writePolicy = new WritePolicy(writePolicy); writePolicy.durableDelete = entry.getDurableDelete(); @@ -328,7 +341,7 @@ public Flux scan(@NotNull Class clazz, int recordsPerSecond) { public Flux scan(ScanPolicy policy, @NotNull Class clazz, int recordsPerSecond) { ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); if (policy == null) { - policy = entry.getScanPolicy(); + policy = PolicyCache.getInstance().getEffectiveScanPolicy(clazz, reactorClient.getScanPolicyDefault()); } if (recordsPerSecond >= 0) { // Ensure the underlying rate on the policy does not change @@ -339,7 +352,10 @@ public Flux scan(ScanPolicy policy, @NotNull Class clazz, int recordsP String setName = entry.getSetName(); return reactorClient.scanAll(policy, namespace, setName) - .map(keyRecord -> getMappingConverter().convertToObject(clazz, keyRecord.key, keyRecord.record)); + .map(keyRecord -> { + Map map = AeroMapper.recordToMap(entry, keyRecord.key, keyRecord.record); + return getMappingConverter().convertToObject(clazz, map); + }); } @Override @@ -351,7 +367,7 @@ public Flux query(@NotNull Class clazz, Filter filter) { public Flux query(QueryPolicy policy, @NotNull Class clazz, Filter filter) { ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(clazz, this); if (policy == null) { - policy = entry.getQueryPolicy(); + policy = PolicyCache.getInstance().getEffectiveQueryPolicy(clazz, reactorClient.getQueryPolicyDefault()); } Statement statement = new Statement(); statement.setFilter(filter); @@ -359,7 +375,10 @@ public Flux query(QueryPolicy policy, @NotNull Class clazz, Filter fil statement.setSetName(entry.getSetName()); return reactorClient.query(policy, statement) - .map(keyRecord -> getMappingConverter().convertToObject(clazz, keyRecord.key, keyRecord.record)); + .map(keyRecord -> { + Map map = AeroMapper.recordToMap(entry, keyRecord.key, keyRecord.record); + return getMappingConverter().convertToObject(clazz, map); + }); } @Override @@ -383,6 +402,11 @@ public MappingConverter getMappingConverter() { return mappingConverter; } + @Override + public RecordLoader getRecordLoader() { + return recordLoader; + } + @Override public IAeroMapper asMapper() { return aeroMapper; @@ -390,46 +414,27 @@ public IAeroMapper asMapper() { @Override public Policy getReadPolicy(Class clazz) { - return getPolicyByClassAndType(clazz, ClassCache.PolicyType.READ); + return PolicyCache.getInstance().getEffectiveReadPolicy(clazz, reactorClient.getReadPolicyDefault()); } @Override public WritePolicy getWritePolicy(Class clazz) { - return (WritePolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.WRITE); + return PolicyCache.getInstance().getEffectiveWritePolicy(clazz, reactorClient.getWritePolicyDefault()); } @Override public BatchPolicy getBatchPolicy(Class clazz) { - return (BatchPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.BATCH); + return PolicyCache.getInstance().getEffectiveBatchPolicy(clazz, reactorClient.getBatchPolicyDefault()); } @Override public ScanPolicy getScanPolicy(Class clazz) { - return (ScanPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.SCAN); + return PolicyCache.getInstance().getEffectiveScanPolicy(clazz, reactorClient.getScanPolicyDefault()); } @Override public QueryPolicy getQueryPolicy(Class clazz) { - return (QueryPolicy) getPolicyByClassAndType(clazz, ClassCache.PolicyType.QUERY); - } - - private Policy getPolicyByClassAndType(Class clazz, ClassCache.PolicyType policyType) { - ClassCacheEntry entry = ClassCache.getInstance().loadClass(clazz, this); - - switch (policyType) { - case READ: - return entry == null ? reactorClient.getReadPolicyDefault() : entry.getReadPolicy(); - case WRITE: - return entry == null ? reactorClient.getWritePolicyDefault() : entry.getWritePolicy(); - case BATCH: - return entry == null ? reactorClient.getBatchPolicyDefault() : entry.getBatchPolicy(); - case SCAN: - return entry == null ? reactorClient.getScanPolicyDefault() : entry.getScanPolicy(); - case QUERY: - return entry == null ? reactorClient.getQueryPolicyDefault() : entry.getQueryPolicy(); - default: - throw new UnsupportedOperationException("Provided unsupported policy."); - } + return PolicyCache.getInstance().getEffectiveQueryPolicy(clazz, reactorClient.getQueryPolicyDefault()); } @Override @@ -462,7 +467,7 @@ public Mono getRecordKey(Object obj) { public static class Builder extends AbstractBuilder { public Builder(IAerospikeReactorClient reactorClient) { super(new ReactiveAeroMapper(reactorClient)); - ClassCache.getInstance().setReactiveDefaultPolicies(reactorClient); + PolicyCache.getInstance().setReactiveDefaultPolicies(reactorClient); } } } diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java similarity index 93% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java index 4e2203f..9d6e429 100644 --- a/src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/BaseVirtualList.java @@ -9,6 +9,7 @@ import com.aerospike.mapper.tools.ClassCache; import com.aerospike.mapper.tools.ClassCacheEntry; import com.aerospike.mapper.tools.IBaseAeroMapper; +import com.aerospike.mapper.tools.PolicyCache; import com.aerospike.mapper.tools.TypeMapper; import com.aerospike.mapper.tools.ValueType; import com.aerospike.mapper.tools.mappers.ListMapper; @@ -87,7 +88,9 @@ protected String alignedSet() { protected WritePolicy getWritePolicy(Policy policy) { if (policy == null) { - return new WritePolicy(owningEntry.getWritePolicy()); + Policy p = PolicyCache.getInstance().determinePolicy( + owningEntry.getUnderlyingClass(), PolicyCache.PolicyType.WRITE); + return p != null ? new WritePolicy((WritePolicy) p) : new WritePolicy(); } return new WritePolicy(policy); } diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/DeferredOperation.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/DeferredOperation.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/DeferredOperation.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/DeferredOperation.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/IReactiveVirtualList.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/IReactiveVirtualList.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/IReactiveVirtualList.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/IReactiveVirtualList.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/IVirtualList.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/IVirtualList.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/IVirtualList.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/IVirtualList.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/Interactor.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/Interactor.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/Interactor.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/Interactor.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/MultiOperation.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/MultiOperation.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/MultiOperation.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/MultiOperation.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java similarity index 60% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java index 3e94b39..2ddcfc4 100644 --- a/src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/OperationParameters.java @@ -1,6 +1,10 @@ package com.aerospike.mapper.tools.virtuallist; +import lombok.Getter; +import lombok.Setter; + public class OperationParameters { + @Getter @Setter private ReturnType needsResultOfType = ReturnType.NONE; public OperationParameters() { @@ -10,12 +14,4 @@ public OperationParameters(ReturnType needsResultOfType) { super(); this.needsResultOfType = needsResultOfType; } - - public ReturnType getNeedsResultOfType() { - return needsResultOfType; - } - - public void setNeedsResultOfType(ReturnType needsResultOfType) { - this.needsResultOfType = needsResultOfType; - } } diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveMultiOperation.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveMultiOperation.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveMultiOperation.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveMultiOperation.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java similarity index 92% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java index 5d98ad0..fcf6984 100644 --- a/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReactiveVirtualList.java @@ -41,7 +41,7 @@ public ReactiveMultiOperation beginMultiOperation() { public ReactiveMultiOperation beginMulti(WritePolicy writePolicy) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } return new ReactiveMultiOperation<>(writePolicy, binName, listMapper, key, virtualListInteractors, reactiveAeroMapper); @@ -55,7 +55,7 @@ public Mono getByValue(Object value, ReturnType returnResultsOfType) { @Override public Mono getByValue(WritePolicy writePolicy, Object value, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueInteractor(value); @@ -74,7 +74,7 @@ public Mono getByValueRange(Object startValue, Object endValue, ReturnType re @Override public Mono getByValueRange(WritePolicy writePolicy, Object startValue, Object endValue, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRangeInteractor(startValue, endValue); @@ -93,7 +93,7 @@ public Mono getByValueList(List values, ReturnType returnResultsOfTyp @Override public Mono getByValueList(WritePolicy writePolicy, List values, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueListInteractor(values); @@ -112,7 +112,7 @@ public Mono getByValueRelativeRankRange(Object value, int rank, ReturnType re @Override public Mono getByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRelativeRankRangeInteractor(value, rank); @@ -131,7 +131,7 @@ public Mono getByValueRelativeRankRange(Object value, int rank, int count, Re @Override public Mono getByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRelativeRankRangeInteractor(value, rank, count); @@ -150,7 +150,7 @@ public Mono getByIndexRange(int index, ReturnType returnResultsOfType) { @Override public Mono getByIndexRange(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByIndexRangeInteractor(index); @@ -169,7 +169,7 @@ public Mono getByIndexRange(int index, int count, ReturnType returnResultsOfT @Override public Mono getByIndexRange(WritePolicy writePolicy, int index, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByIndexRangeInteractor(index, count); @@ -188,7 +188,7 @@ public Mono getByRank(int rank, ReturnType returnResultsOfType) { @Override public Mono getByRank(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankInteractor(rank); @@ -207,7 +207,7 @@ public Mono getByRankRange(int rank, ReturnType returnResultsOfType) { @Override public Mono getByRankRange(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankRangeInteractor(rank); @@ -226,7 +226,7 @@ public Mono getByRankRange(int rank, int count, ReturnType returnResultsOfTyp @Override public Mono getByRankRange(WritePolicy writePolicy, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankRangeInteractor(rank, count); @@ -245,7 +245,7 @@ public Mono getByKey(Object key, ReturnType returnResultsOfType) { @Override public Mono getByKey(WritePolicy writePolicy, Object key, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByKeyInteractor(key); @@ -264,7 +264,7 @@ public Mono getByKeyRange(Object startKey, Object endKey, ReturnType returnRe @Override public Mono getByKeyRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByKeyRangeInteractor(startKey, endKey); @@ -283,7 +283,7 @@ public Mono removeByKey(Object key, ReturnType returnResultsOfType) { @Override public Mono removeByKey(WritePolicy writePolicy, Object key, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveKeyInteractor(key); @@ -302,7 +302,7 @@ public Mono removeByValue(Object value, ReturnType returnResultsOfType) { @Override public Mono removeByValue(WritePolicy writePolicy, Object value, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueInteractor(value); @@ -321,7 +321,7 @@ public Mono removeByValueList(List values, ReturnType returnResultsOf @Override public Mono removeByValueList(WritePolicy writePolicy, List values, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueListInteractor(values); @@ -340,7 +340,7 @@ public Mono removeByValueRange(Object startValue, Object endValue, ReturnType @Override public Mono removeByValueRange(WritePolicy writePolicy, Object startValue, Object endValue, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRangeInteractor(startValue, endValue); @@ -359,7 +359,7 @@ public Mono removeByValueRelativeRankRange(Object value, int rank, ReturnType @Override public Mono removeByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRelativeRankRangeInteractor(value, rank); @@ -378,7 +378,7 @@ public Mono removeByValueRelativeRankRange(Object value, int rank, int count, @Override public Mono removeByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRelativeRankRangeInteractor(value, rank, count); @@ -397,7 +397,7 @@ public Mono removeByIndex(int index, ReturnType returnResultsOfType) { @Override public Mono removeByIndex(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexInteractor(index); @@ -416,7 +416,7 @@ public Mono removeByIndexRange(int index, ReturnType returnResultsOfType) { @Override public Mono removeByIndexRange(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexRangeInteractor(index); @@ -435,7 +435,7 @@ public Mono removeByIndexRange(int index, int count, ReturnType returnResults @Override public Mono removeByIndexRange(WritePolicy writePolicy, int index, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexRangeInteractor(index, count); @@ -454,7 +454,7 @@ public Mono removeByRank(int rank, ReturnType returnResultsOfType) { @Override public Mono removeByRank(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankInteractor(rank); @@ -473,7 +473,7 @@ public Mono removeByRankRange(int rank, ReturnType returnResultsOfType) { @Override public Mono removeByRankRange(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankRangeInteractor(rank); @@ -492,7 +492,7 @@ public Mono removeByRankRange(int rank, int count, ReturnType returnResultsOf @Override public Mono removeByRankRange(WritePolicy writePolicy, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankRangeInteractor(rank, count); @@ -511,7 +511,7 @@ public Mono removeByKeyRange(Object startKey, Object endKey, ReturnType retur @Override public Mono removeByKeyRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveKeyRangeInteractor(startKey, endKey); @@ -531,7 +531,7 @@ public Mono append(E element) { public Mono append(WritePolicy writePolicy, E element) { Object result = listMapper.toAerospikeInstanceFormat(element); if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } return reactiveAeroMapper.getReactorClient() diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/ResultsUnpacker.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ResultsUnpacker.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/ResultsUnpacker.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ResultsUnpacker.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/ReturnType.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReturnType.java similarity index 100% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/ReturnType.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/ReturnType.java diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java similarity index 92% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java index b63823b..3312cf3 100644 --- a/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualList.java @@ -40,7 +40,7 @@ public MultiOperation beginMultiOperation() { public MultiOperation beginMulti(WritePolicy writePolicy) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } return new MultiOperation<>(writePolicy, binName, listMapper, key, virtualListInteractors, mapper); @@ -54,7 +54,7 @@ public List getByValue(Object value, ReturnType returnResultsOfType) { @Override public List getByValue(WritePolicy writePolicy, Object value, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueInteractor(value); @@ -71,7 +71,7 @@ public List getByValueRange(Object startValue, Object endValue, ReturnType re @Override public List getByValueRange(WritePolicy writePolicy, Object startValue, Object endValue, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRangeInteractor(startValue, endValue); @@ -88,7 +88,7 @@ public List getByValueList(List values, ReturnType returnResultsOfTyp @Override public List getByValueList(WritePolicy writePolicy, List values, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueListInteractor(values); @@ -105,7 +105,7 @@ public List getByValueRelativeRankRange(Object value, int rank, ReturnType re @Override public List getByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRelativeRankRangeInteractor(value, rank); @@ -122,7 +122,7 @@ public List getByValueRelativeRankRange(Object value, int rank, int count, Re @Override public List getByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByValueRelativeRankRangeInteractor(value, rank, count); @@ -139,7 +139,7 @@ public List getByIndexRange(int index, ReturnType returnResultsOfType) { @Override public List getByIndexRange(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByIndexRangeInteractor(index); @@ -156,7 +156,7 @@ public List getByIndexRange(int index, int count, ReturnType returnResultsOfT @Override public List getByIndexRange(WritePolicy writePolicy, int index, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByIndexRangeInteractor(index, count); @@ -173,7 +173,7 @@ public List getByRank(int rank, ReturnType returnResultsOfType) { @Override public List getByRank(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankInteractor(rank); @@ -190,7 +190,7 @@ public List getByRankRange(int rank, ReturnType returnResultsOfType) { @Override public List getByRankRange(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankRangeInteractor(rank); @@ -207,7 +207,7 @@ public List getByRankRange(int rank, int count, ReturnType returnResultsOfTyp @Override public List getByRankRange(WritePolicy writePolicy, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByRankRangeInteractor(rank, count); @@ -224,7 +224,7 @@ public List getByKey(Object key, ReturnType returnResultsOfType) { @Override public List getByKey(WritePolicy writePolicy, Object key, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByKeyInteractor(key); @@ -241,7 +241,7 @@ public List getByKeyRange(Object startKey, Object endKey, ReturnType returnRe @Override public List getByKeyRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getGetByKeyRangeInteractor(startKey, endKey); @@ -258,7 +258,7 @@ public List removeByKey(Object key, ReturnType returnResultsOfType) { @Override public List removeByKey(WritePolicy writePolicy, Object key, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveKeyInteractor(key); @@ -275,7 +275,7 @@ public List removeByValue(Object value, ReturnType returnResultsOfType) { @Override public List removeByValue(WritePolicy writePolicy, Object value, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueInteractor(value); @@ -292,7 +292,7 @@ public List removeByValueList(List values, ReturnType returnResultsOf @Override public List removeByValueList(WritePolicy writePolicy, List values, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueListInteractor(values); @@ -309,7 +309,7 @@ public List removeByValueRange(Object startValue, Object endValue, ReturnType @Override public List removeByValueRange(WritePolicy writePolicy, Object startValue, Object endValue, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRangeInteractor(startValue, endValue); @@ -326,7 +326,7 @@ public List removeByValueRelativeRankRange(Object value, int rank, ReturnType @Override public List removeByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRelativeRankRangeInteractor(value, rank); @@ -343,7 +343,7 @@ public List removeByValueRelativeRankRange(Object value, int rank, int count, @Override public List removeByValueRelativeRankRange(WritePolicy writePolicy, Object value, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByValueRelativeRankRangeInteractor(value, rank, count); @@ -360,7 +360,7 @@ public List removeByIndex(int index, ReturnType returnResultsOfType) { @Override public List removeByIndex(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexInteractor(index); @@ -377,7 +377,7 @@ public List removeByIndexRange(int index, ReturnType returnResultsOfType) { @Override public List removeByIndexRange(WritePolicy writePolicy, int index, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexRangeInteractor(index); @@ -394,7 +394,7 @@ public List removeByIndexRange(int index, int count, ReturnType returnResults @Override public List removeByIndexRange(WritePolicy writePolicy, int index, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByIndexRangeInteractor(index, count); @@ -411,7 +411,7 @@ public List removeByRank(int rank, ReturnType returnResultsOfType) { @Override public List removeByRank(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankInteractor(rank); @@ -428,7 +428,7 @@ public List removeByRankRange(int rank, ReturnType returnResultsOfType) { @Override public List removeByRankRange(WritePolicy writePolicy, int rank, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankRangeInteractor(rank); @@ -445,7 +445,7 @@ public List removeByRankRange(int rank, int count, ReturnType returnResultsOf @Override public List removeByRankRange(WritePolicy writePolicy, int rank, int count, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveByRankRangeInteractor(rank, count); @@ -462,7 +462,7 @@ public List removeByKeyRange(Object startKey, Object endKey, ReturnType retur @Override public List removeByKeyRange(WritePolicy writePolicy, Object startKey, Object endKey, ReturnType returnResultsOfType) { if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Interactor interactor = virtualListInteractors.getRemoveKeyRangeInteractor(startKey, endKey); @@ -480,7 +480,7 @@ public long append(E element) { public long append(WritePolicy writePolicy, E element) { Object result = listMapper.toAerospikeInstanceFormat(element); if (writePolicy == null) { - writePolicy = new WritePolicy(owningEntry.getWritePolicy()); + writePolicy = getWritePolicy(null); writePolicy.recordExistsAction = RecordExistsAction.UPDATE; } Record record = this.mapper.getClient().operate(writePolicy, key, virtualListInteractors.getAppendOperation(result)); diff --git a/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java similarity index 86% rename from src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java rename to legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java index 2dcb897..73411c5 100644 --- a/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java +++ b/legacyapi/src/main/java/com/aerospike/mapper/tools/virtuallist/VirtualListInteractors.java @@ -6,7 +6,6 @@ import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.tools.ClassCacheEntry; import com.aerospike.mapper.tools.IBaseAeroMapper; -import com.aerospike.mapper.tools.utils.TypeUtils; import java.util.List; import java.util.Map; @@ -48,10 +47,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValue(binName, getValue(value, false), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValue(binName, getValue(value, false), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -81,10 +80,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRange(binName, getValue(startValue, false), getValue(endValue, false), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValueRange(binName, getValue(startValue, false), getValue(endValue, false), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -117,10 +116,10 @@ public Operation getOperation(OperationParameters operationParams) { .collect(Collectors.toList()); if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueList(binName, aerospikeValues, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValueList(binName, aerospikeValues, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -150,10 +149,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRelativeRankRange(binName, getValue(value, false), rank, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValueRelativeRankRange(binName, getValue(value, false), rank, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -183,10 +182,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRelativeRankRange(binName, getValue(value, false), rank, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByValueRelativeRankRange(binName, getValue(value, false), rank, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -216,10 +215,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByIndexRange(binName, index, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByIndexRange(binName, index, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -249,10 +248,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByIndexRange(binName, index, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByIndexRange(binName, index, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -282,10 +281,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByRank(binName, index, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByRank(binName, index, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -315,10 +314,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByRankRange(binName, index, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByRankRange(binName, index, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -348,10 +347,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByRankRange(binName, index, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByRankRange(binName, index, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -381,10 +380,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValue(binName, getValue(key, true), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByKey(binName, getValue(key, true), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -414,10 +413,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.getByValueRange(binName, getValue(startKey, true), getValue(endKey, true), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.getByKeyRange(binName, getValue(startKey, true), getValue(endKey, true), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -447,10 +446,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRange(binName, getValue(startKey, true), getValue(endKey, true), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByKeyRange(binName, getValue(startKey, true), getValue(endKey, true), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -480,10 +479,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValue(binName, getValue(key, true), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByKey(binName, getValue(key, true), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -513,10 +512,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValue(binName, getValue(value, false), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValue(binName, getValue(value, false), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -549,10 +548,10 @@ public Operation getOperation(OperationParameters operationParams) { .collect(Collectors.toList()); if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueList(binName, aerospikeValues, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValueList(binName, aerospikeValues, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -582,10 +581,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRange(binName, getValue(startValue, false), getValue(endValue, false), - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValueRange(binName, getValue(startValue, false), getValue(endValue, false), - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -615,10 +614,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRelativeRankRange(binName, getValue(value, false), rank, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValueRelativeRankRange(binName, getValue(value, false), rank, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -648,10 +647,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByValueRelativeRankRange(binName, getValue(value, false), rank, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByValueRelativeRankRange(binName, getValue(value, false), rank, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -681,10 +680,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByIndex(binName, index, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByIndex(binName, index, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -714,10 +713,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByIndexRange(binName, index, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByIndexRange(binName, index, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -747,10 +746,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByIndexRange(binName, index, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByIndexRange(binName, index, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -780,10 +779,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByRank(binName, rank, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByRank(binName, rank, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -813,10 +812,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByRankRange(binName, rank, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByRankRange(binName, rank, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -846,10 +845,10 @@ public ResultsUnpacker[] getUnpackers(OperationParameters operationParams) { public Operation getOperation(OperationParameters operationParams) { if (listType == AerospikeEmbed.EmbedType.LIST) { return ListOperation.removeByRankRange(binName, rank, count, - TypeUtils.returnTypeToListReturnType(operationParams.getNeedsResultOfType())); + listReturnType(operationParams.getNeedsResultOfType())); } else { return MapOperation.removeByRankRange(binName, rank, count, - TypeUtils.returnTypeToMapReturnType(operationParams.getNeedsResultOfType())); + mapReturnType(operationParams.getNeedsResultOfType())); } } @@ -911,4 +910,34 @@ public Interactor getClearInteractor() { return new Interactor(MapOperation.clear(binName)); } } + + private static int listReturnType(ReturnType returnType) { + switch (returnType) { + case DEFAULT: + case ELEMENTS: + return ListReturnType.VALUE; + case COUNT: + return ListReturnType.COUNT; + case INDEX: + return ListReturnType.INDEX; + case NONE: + default: + return ListReturnType.NONE; + } + } + + private static int mapReturnType(ReturnType returnType) { + switch (returnType) { + case DEFAULT: + case ELEMENTS: + return MapReturnType.KEY_VALUE; + case COUNT: + return MapReturnType.COUNT; + case INDEX: + return MapReturnType.INDEX; + case NONE: + default: + return MapReturnType.NONE; + } + } } diff --git a/src/test/java/com/aerospike/mapper/AeroMapperArrayTests.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperArrayTests.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperArrayTests.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperArrayTests.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java similarity index 97% rename from src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java index ed11304..0aa0d69 100644 --- a/src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java @@ -26,6 +26,7 @@ public static void setupClass() { ClientPolicy policy = new ClientPolicy(); // Set event loops to use in asynchronous commands. policy.eventLoops = new NioEventLoops(1); + policy.writePolicyDefault.durableDelete = true; Host[] hosts = Host.parseHosts(System.getProperty("test.host", "localhost:3000"), 3000); client = new AerospikeClient(policy, hosts); } diff --git a/src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java similarity index 98% rename from src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java index 218c3df..7c8f012 100644 --- a/src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperComplexClassTest.java @@ -28,7 +28,7 @@ public static class ComplexClass { private List byte1Fields; private List charFields; private List strFields; - private boolean testData = false; + private final boolean testData = false; public ComplexClass() { } diff --git a/src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java similarity index 92% rename from src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java index 8855ecb..66ccce4 100644 --- a/src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperConfigurationYamlTest.java @@ -1,6 +1,7 @@ package com.aerospike.mapper; import com.aerospike.mapper.tools.AeroMapper; +import lombok.Getter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -12,11 +13,9 @@ public class AeroMapperConfigurationYamlTest extends AeroMapperBaseTest { - private AeroMapper mapper; - @BeforeEach public void setup() { - mapper = new AeroMapper.Builder(client).build(); + AeroMapper mapper = new AeroMapper.Builder(client).build(); client.truncate(null, NAMESPACE, "testSet", null); } @@ -63,6 +62,7 @@ public void testConvenienceMethods() throws Exception { assertEquals(container, container2); } + @Getter public static class DataClass { private int id; private int integer; @@ -80,22 +80,6 @@ public DataClass(int id, int integer, String string, Date date) { this.date = date; } - public int getId() { - return id; - } - - public int getInteger() { - return integer; - } - - public String getString() { - return string; - } - - public Date getDate() { - return date; - } - @Override public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { diff --git a/src/test/java/com/aerospike/mapper/AeroMapperCustomConverterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperCustomConverterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperCustomConverterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperCustomConverterTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperEnumTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperEnumTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperEnumTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperEnumTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperListOfObjectTransformTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperListOfObjectTransformTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperListOfObjectTransformTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperListOfObjectTransformTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperListTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperListTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperMapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperMapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperMapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperMapTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperObjectTransformTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperObjectTransformTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperObjectTransformTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperObjectTransformTest.java diff --git a/src/test/java/com/aerospike/mapper/AeroMapperTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AeroMapperTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AeroMapperTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AeroMapperTest.java diff --git a/src/test/java/com/aerospike/mapper/AnonymousReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/AnonymousReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/AnonymousReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/AnonymousReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/ArrayTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ArrayTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/ArrayTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ArrayTest.java diff --git a/src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java similarity index 89% rename from src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java index 612ed6f..86998ff 100644 --- a/src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/BaseClassWithNoAttributesTest.java @@ -46,13 +46,6 @@ public Dog(String breed) { dogId = "" + ((int) (Math.random() * 1000)); } - public String getBreed() { - return breed; - } - - public String getDogId() { - return dogId; - } } @Data @@ -77,17 +70,6 @@ public Cat() { catId = "" + ((int) (Math.random() * 1000)); } - public String getBreed() { - return breed; - } - - public String getCatId() { - return catId; - } - - public String getLifeSpan() { - return lifeSpan; - } } @AerospikeRecord(namespace = "test", set = "zoo", mapAll = false) diff --git a/src/test/java/com/aerospike/mapper/BatchLoadTest.java b/legacyapi/src/test/java/com/aerospike/mapper/BatchLoadTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/BatchLoadTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/BatchLoadTest.java diff --git a/src/test/java/com/aerospike/mapper/BigIntegerBigDecimalTest.java b/legacyapi/src/test/java/com/aerospike/mapper/BigIntegerBigDecimalTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/BigIntegerBigDecimalTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/BigIntegerBigDecimalTest.java diff --git a/src/test/java/com/aerospike/mapper/BooleanTest.java b/legacyapi/src/test/java/com/aerospike/mapper/BooleanTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/BooleanTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/BooleanTest.java diff --git a/legacyapi/src/test/java/com/aerospike/mapper/ClassCacheUnitTests.java b/legacyapi/src/test/java/com/aerospike/mapper/ClassCacheUnitTests.java new file mode 100644 index 0000000..ef4ed1a --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/ClassCacheUnitTests.java @@ -0,0 +1,230 @@ +package com.aerospike.mapper; + +import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.mapper.annotations.AerospikeBin; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.tools.AeroMapper; +import com.aerospike.mapper.tools.ClassCache; +import com.aerospike.mapper.tools.ClassCacheEntry; +import lombok.Getter; +import lombok.Setter; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for ClassCache behavior that don't require a running Aerospike server. + * These tests verify that metadata caching works correctly by checking ClassCacheEntry + * instance reuse and cache state management. + * Coverage: + * - Same instance returned on repeated loads (with stress test) + * - Cache state tracking (hasClass) + * - Multiple classes cached simultaneously + * - Clear behavior forces new reflection + * - Singleton behavior across mapper instances + */ +public class ClassCacheUnitTests { + + @Setter + @Getter + @AerospikeRecord(namespace = "test", set = "testSet") + public static class TestClass { + @AerospikeKey + private String id; + + @AerospikeBin(name = "name") + private String name; + + private int value; + private Date timestamp; + + } + + @Setter + @Getter + @AerospikeRecord(namespace = "test", set = "otherclass") + public static class OtherTestClass { + @AerospikeKey + private int id; + private String data; + + } + + private AeroMapper mapper; + + private static IAerospikeClient createMockClient() { + IAerospikeClient mockClient = mock(IAerospikeClient.class); + when(mockClient.getReadPolicyDefault()).thenReturn(new Policy()); + when(mockClient.getWritePolicyDefault()).thenReturn(new WritePolicy()); + when(mockClient.getBatchPolicyDefault()).thenReturn(new BatchPolicy()); + when(mockClient.getQueryPolicyDefault()).thenReturn(new QueryPolicy()); + when(mockClient.getScanPolicyDefault()).thenReturn(new ScanPolicy()); + return mockClient; + } + + @BeforeEach + public void setup() { + ClassCache.getInstance().clear(); + IAerospikeClient mockClient = createMockClient(); + mapper = new AeroMapper.Builder(mockClient).build(); + } + + @AfterEach + public void cleanup() { + ClassCache.getInstance().clear(); + } + + /** + * PRIMARY TEST: Verifies that metadata reflection happens only once. + * Multiple calls to loadClass() must return the exact same ClassCacheEntry instance. + * Includes stress test with 100 iterations. + */ + @Test + public void testSameInstanceReturnedOnRepeatedLoads() { + // First load - performs reflection and creates entry + ClassCacheEntry entry1 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertNotNull(entry1, "First load should return a ClassCacheEntry"); + + // Second load - must return exact same instance + ClassCacheEntry entry2 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertSame(entry1, entry2, "Second load must return the same instance (no new reflection)"); + + // Third load - verify consistency + ClassCacheEntry entry3 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertSame(entry1, entry3, "Third load must return the same instance"); + + // Stress test: 100 repeated loads - all must return same instance + final int iterations = 100; + for (int i = 0; i < iterations; i++) { + ClassCacheEntry entryN = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertSame(entry1, entryN, "Iteration " + i + " must return the same cached instance"); + } + } + + /** + * Test that cache properly tracks which classes have been loaded using hasClass(). + */ + @Test + public void testHasClassTracksLoadedClasses() { + // Initially, cache should not have TestClass + assertFalse(ClassCache.getInstance().hasClass(TestClass.class), + "Cache should not have TestClass before it's loaded"); + + // Load the class + ClassCacheEntry entry = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertNotNull(entry, "Load should succeed"); + + // Now cache should have TestClass + assertTrue(ClassCache.getInstance().hasClass(TestClass.class), + "Cache should have TestClass after it's loaded"); + + // Loading again should not change hasClass result + ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertTrue(ClassCache.getInstance().hasClass(TestClass.class), + "Cache should still have TestClass after repeated load"); + } + + /** + * Test that cache maintains separate entries for multiple classes simultaneously + * and that reloading returns the same cached instances. + */ + @Test + public void testMultipleClassesCachedSimultaneously() { + // Load first class + ClassCacheEntry testEntry1 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertNotNull(testEntry1, "TestClass should load"); + + // Load second class + ClassCacheEntry otherEntry1 = ClassCache.getInstance().loadClass(OtherTestClass.class, mapper); + assertNotNull(otherEntry1, "OtherTestClass should load"); + + // Verify no mixup: each entry must reflect its own class metadata + assertEquals(TestClass.class, testEntry1.getUnderlyingClass(), + "TestClass entry must map to TestClass"); + assertEquals(OtherTestClass.class, otherEntry1.getUnderlyingClass(), + "OtherTestClass entry must map to OtherTestClass"); + assertEquals("testSet", testEntry1.getSetName(), + "TestClass entry must have set 'testSet'"); + assertEquals("otherclass", otherEntry1.getSetName(), + "OtherTestClass entry must have set 'otherclass'"); + + // Reload first class - should get same instance + ClassCacheEntry testEntry2 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertSame(testEntry1, testEntry2, "TestClass should return same cached instance"); + + // Reload second class - should get same instance + ClassCacheEntry otherEntry2 = ClassCache.getInstance().loadClass(OtherTestClass.class, mapper); + assertSame(otherEntry1, otherEntry2, "OtherTestClass should return same cached instance"); + + // Both should be in cache + assertTrue(ClassCache.getInstance().hasClass(TestClass.class), + "TestClass should be in cache"); + assertTrue(ClassCache.getInstance().hasClass(OtherTestClass.class), + "OtherTestClass should be in cache"); + } + + /** + * Test that clear() removes all entries and forces new reflection. + * After clear, loadClass must create a NEW ClassCacheEntry instance. + */ + @Test + public void testClearRemovesAllEntries() { + // Load a class + ClassCacheEntry entry1 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertNotNull(entry1, "Initial load should succeed"); + assertTrue(ClassCache.getInstance().hasClass(TestClass.class), + "Class should be in cache after load"); + + // Clear the cache + ClassCache.getInstance().clear(); + + // Class should no longer be in cache + assertFalse(ClassCache.getInstance().hasClass(TestClass.class), + "Class should not be in cache after clear"); + + // Loading again should create a new entry + ClassCacheEntry entry2 = ClassCache.getInstance().loadClass(TestClass.class, mapper); + assertNotNull(entry2, "Load after clear should succeed"); + + // Must be a different instance (new reflection occurred) + assertNotSame(entry1, entry2, + "Load after clear must create a new instance (reflection happens again)"); + } + + /** + * Test that cache is singleton - shared across different AeroMapper instances. + * Different mapper instances should get the same cached ClassCacheEntry. + */ + @Test + public void testCacheSharedAcrossMapperInstances() { + // Create first mapper and load a class + AeroMapper mapper1 = new AeroMapper.Builder(createMockClient()).build(); + ClassCacheEntry entry1 = ClassCache.getInstance().loadClass(TestClass.class, mapper1); + assertNotNull(entry1, "Load with mapper1 should succeed"); + + // Create second mapper and load the same class + AeroMapper mapper2 = new AeroMapper.Builder(createMockClient()).build(); + ClassCacheEntry entry2 = ClassCache.getInstance().loadClass(TestClass.class, mapper2); + assertNotNull(entry2, "Load with mapper2 should succeed"); + + // Must be the exact same instance (cache is singleton) + assertSame(entry1, entry2, + "Different mapper instances should share the same cached ClassCacheEntry"); + + // Cache state should be consistent + assertTrue(ClassCache.getInstance().hasClass(TestClass.class), + "Cache state should be consistent across mapper instances"); + } +} diff --git a/src/test/java/com/aerospike/mapper/CollectionMapperTest.java b/legacyapi/src/test/java/com/aerospike/mapper/CollectionMapperTest.java similarity index 93% rename from src/test/java/com/aerospike/mapper/CollectionMapperTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/CollectionMapperTest.java index 6b04b2d..305bc43 100644 --- a/src/test/java/com/aerospike/mapper/CollectionMapperTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/CollectionMapperTest.java @@ -7,12 +7,14 @@ import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.tools.AeroMapper; import com.aerospike.mapper.tools.virtuallist.VirtualList; +import lombok.Getter; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; public class CollectionMapperTest extends AeroMapperBaseTest { + @Getter @AerospikeRecord public static class CollectionElement { @AerospikeKey @@ -27,18 +29,6 @@ public CollectionElement(@ParamFrom("id") int id, @ParamFrom("name") String name this.date = date; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public long getDate() { - return date; - } - @Override public String toString() { return String.format("{id=%d, name=%s, date=%d}", id, name, date); diff --git a/src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java b/legacyapi/src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java similarity index 92% rename from src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java index 0723661..7ec1bf1 100644 --- a/src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/CompetingAnnotationsTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; -import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; @@ -75,7 +75,7 @@ public void testCompetingReferenceAndEmbedAnnotations() { try { mapper.save(b); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -90,7 +90,7 @@ public void testCompetingListReferenceAndEmbedAnnotations() { try { mapper.save(b); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -104,7 +104,7 @@ public void testMapCompetingReferenceAndEmbedAnnotations() { try { mapper.save(b); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -119,7 +119,7 @@ public void testCompetingArrayReferenceAndEmbedAnnotations() { try { mapper.save(b); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } diff --git a/src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java similarity index 96% rename from src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java index 61c4c61..a35f0af 100644 --- a/src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/ConfigurationThroughCodeTest.java @@ -8,6 +8,7 @@ import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; @@ -97,7 +98,7 @@ public void testWithInvalidConfiguration() { .build(); Assertions.fail("Expected invalid configuration to throw an error"); } - catch (AerospikeException ignore) { + catch (AerospikeMapperException ignore) { } } diff --git a/src/test/java/com/aerospike/mapper/ConstructorTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ConstructorTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/ConstructorTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ConstructorTest.java diff --git a/src/test/java/com/aerospike/mapper/DateCustomConverterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/DateCustomConverterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/DateCustomConverterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/DateCustomConverterTest.java diff --git a/src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java similarity index 95% rename from src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java index 7577f8c..e7095ed 100644 --- a/src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/DefaultFieldValuesTest.java @@ -79,7 +79,7 @@ public void testBooleanValue() { UseBoolBin = false; mapper.save(obj); Record record = client.get(null, key); - assertTrue(record.bins.get("bool2") instanceof Long); + assertInstanceOf(Long.class, record.bins.get("bool2")); assertEquals(0, record.getLong("bool2")); DefaultFieldsClass dfc = mapper.read(DefaultFieldsClass.class, "dfc"); assertFalse(dfc.bool2); @@ -87,7 +87,7 @@ public void testBooleanValue() { UseBoolBin = true; mapper.save(obj); record = client.get(null, key); - assertTrue(record.bins.get("bool2") instanceof Boolean); + assertInstanceOf(Boolean.class, record.bins.get("bool2")); assertFalse(record.getBoolean("bool2")); dfc = mapper.read(DefaultFieldsClass.class, "dfc"); assertFalse(dfc.bool2); diff --git a/src/test/java/com/aerospike/mapper/DefaultObjectMappingTest.java b/legacyapi/src/test/java/com/aerospike/mapper/DefaultObjectMappingTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/DefaultObjectMappingTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/DefaultObjectMappingTest.java diff --git a/src/test/java/com/aerospike/mapper/EmbeddedClassTest.java b/legacyapi/src/test/java/com/aerospike/mapper/EmbeddedClassTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/EmbeddedClassTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/EmbeddedClassTest.java diff --git a/src/test/java/com/aerospike/mapper/FactoryConstructorTest.java b/legacyapi/src/test/java/com/aerospike/mapper/FactoryConstructorTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/FactoryConstructorTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/FactoryConstructorTest.java diff --git a/src/test/java/com/aerospike/mapper/FinalFieldMappingTest.java b/legacyapi/src/test/java/com/aerospike/mapper/FinalFieldMappingTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/FinalFieldMappingTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/FinalFieldMappingTest.java diff --git a/src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java b/legacyapi/src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java similarity index 90% rename from src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java index 37251c4..9f2a0f2 100644 --- a/src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/GenerationAnnotationTest.java @@ -4,11 +4,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; import com.aerospike.client.AerospikeException; import com.aerospike.client.policy.GenerationPolicy; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeGeneration; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; @@ -17,8 +20,11 @@ public class GenerationAnnotationTest extends AeroMapperBaseTest { + @Setter + @Getter @AerospikeRecord(namespace = NAMESPACE, set = "generationTest") public static class GenerationEntity { + // Getters and setters @AerospikeKey private int id; private String name; @@ -32,17 +38,13 @@ public GenerationEntity(int id, String name) { this.name = name; } - // Getters and setters - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public int getGeneration() { return generation; } - public void setGeneration(int generation) { this.generation = generation; } } + @Setter + @Getter @AerospikeRecord(namespace = NAMESPACE, set = "generationTestInteger") public static class GenerationEntityWithInteger { + // Getters and setters @AerospikeKey private int id; private String name; @@ -57,19 +59,17 @@ public GenerationEntityWithInteger(int id, String name) { this.generation = 0; } - // Getters and setters - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public Integer getGeneration() { return generation; } - public void setGeneration(Integer generation) { this.generation = generation; } } @AerospikeRecord(namespace = NAMESPACE, set = "generationTestMethods") public static class GenerationEntityWithMethods { + // Getters and setters + @Setter + @Getter @AerospikeKey private int id; + @Setter + @Getter private String name; private int generation; @@ -81,12 +81,6 @@ public GenerationEntityWithMethods(int id, String name) { this.generation = 0; } - // Getters and setters - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - @AerospikeGeneration public int getGeneration() { return generation; } @@ -199,9 +193,7 @@ public void testOptimisticConcurrencyControl() { // Try to update second entity with stale generation - should fail readEntity2.setName("Updated by second"); - assertThrows(AerospikeException.class, () -> { - mapper.save(readEntity2); - }); + assertThrows(AerospikeException.class, () -> mapper.save(readEntity2)); } @Test @@ -231,7 +223,7 @@ public void testGenerationIncrementOnUpdate() { @Test public void testInvalidGenerationFieldType() { - assertThrows(AerospikeException.class, () -> { + assertThrows(AerospikeMapperException.class, () -> { AeroMapper mapper = new AeroMapper.Builder(client).build(); InvalidGenerationEntity entity = new InvalidGenerationEntity(); entity.id = 6; @@ -241,7 +233,7 @@ public void testInvalidGenerationFieldType() { @Test public void testMultipleGenerationFields() { - assertThrows(AerospikeException.class, () -> { + assertThrows(AerospikeMapperException.class, () -> { AeroMapper mapper = new AeroMapper.Builder(client).build(); MultipleGenerationEntity entity = new MultipleGenerationEntity(); entity.id = 7; @@ -309,4 +301,4 @@ public void testZeroGenerationDoesNotSetGeneration() { GenerationEntity readEntity = mapper.read(GenerationEntity.class, 10); assertEquals(1, readEntity.getGeneration()); // Should be 1 after save } -} \ No newline at end of file +} diff --git a/src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java b/legacyapi/src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java similarity index 91% rename from src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java index 4189373..634b67a 100644 --- a/src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/GenerationConfigurationTest.java @@ -2,18 +2,22 @@ import static org.junit.jupiter.api.Assertions.*; +import com.aerospike.mapper.annotations.AerospikeKey; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; -import com.aerospike.mapper.GenerationAnnotationTest.GenerationEntity; -import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.tools.AeroMapper; import com.aerospike.mapper.tools.configuration.ClassConfig; public class GenerationConfigurationTest extends AeroMapperBaseTest { + @Setter + @Getter @AerospikeRecord(namespace = NAMESPACE, set = "configGenerationTest") public static class ConfigGenerationEntity { + // Getters and setters @AerospikeKey private int id; private String name; @@ -27,13 +31,6 @@ public ConfigGenerationEntity(int id, String name) { this.generation = 0; } - // Getters and setters - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public int getGeneration() { return generation; } - public void setGeneration(int generation) { this.generation = generation; } } @Test @@ -42,7 +39,7 @@ public void testGenerationThroughCodeConfiguration() { ClassConfig config = new ClassConfig.Builder(ConfigGenerationEntity.class) .withFieldNamed("generation").asGenerationField() .build(); - + AeroMapper mapper = new AeroMapper.Builder(client) .withClassConfigurations(config) .build(); @@ -122,9 +119,7 @@ public void testOptimisticConcurrencyWithConfiguration() { // Try to update second entity with stale generation - should fail readEntity2.setName("Updated by second"); - assertThrows(Exception.class, () -> { - mapper.save(readEntity2); - }); + assertThrows(Exception.class, () -> mapper.save(readEntity2)); } @Test @@ -158,4 +153,4 @@ public void testGenerationIncrementWithConfiguration() { assertEquals(2, updatedEntity.getGeneration()); assertEquals("Updated Name", updatedEntity.getName()); } -} \ No newline at end of file +} diff --git a/legacyapi/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java b/legacyapi/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java new file mode 100644 index 0000000..4c84615 --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java @@ -0,0 +1,58 @@ +package com.aerospike.mapper; + +import static org.junit.jupiter.api.Assertions.*; + +import lombok.Getter; +import lombok.Setter; +import org.junit.jupiter.api.Test; + +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.annotations.AerospikeGeneration; +import com.aerospike.mapper.tools.AeroMapper; +import com.aerospike.mapper.tools.ClassCacheEntry; +import com.aerospike.mapper.tools.utils.MapperUtils; + +import java.util.Map; + +public class GenerationNotStoredTest extends AeroMapperBaseTest { + + @Setter + @Getter + @AerospikeRecord(namespace = NAMESPACE, set = "generationNotStoredTest") + public static class EntityWithGeneration { + @AerospikeKey + private int id; + private String name; + @AerospikeGeneration + private Integer generation = 0; + + public EntityWithGeneration() {} + + public EntityWithGeneration(int id, String name) { + this.id = id; + this.name = name; + } + + } + + @Test + public void testGenerationFieldNotStoredAsBin() { + AeroMapper mapper = new AeroMapper.Builder(client).build(); + EntityWithGeneration entity = new EntityWithGeneration(1, "test"); + entity.setGeneration(5); // Set some generation value + + // Get the ClassCacheEntry and the bin map that would be stored + ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(EntityWithGeneration.class, mapper); + Map binMap = entry.getMap(entity, false); + + // Verify that the generation field should NOT be included + assertTrue(binMap.containsKey("name"), "Should find 'name' bin"); + assertEquals("test", binMap.get("name"), "name bin should have correct value"); + assertFalse(binMap.containsKey("generation"), "Should NOT find 'generation' bin - it should not be stored"); + + // Log what bins we actually found for debugging + System.out.println("Found " + binMap.size() + " bins:"); + binMap.forEach((k, v) -> System.out.println(" - " + k + " = " + v)); + } +} \ No newline at end of file diff --git a/src/test/java/com/aerospike/mapper/InheritanceTest.java b/legacyapi/src/test/java/com/aerospike/mapper/InheritanceTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/InheritanceTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/InheritanceTest.java diff --git a/src/test/java/com/aerospike/mapper/InsertOnlyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/InsertOnlyTest.java similarity index 85% rename from src/test/java/com/aerospike/mapper/InsertOnlyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/InsertOnlyTest.java index 605b1dd..7a191d3 100644 --- a/src/test/java/com/aerospike/mapper/InsertOnlyTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/InsertOnlyTest.java @@ -7,6 +7,7 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.tools.AeroMapper; +import com.aerospike.mapper.tools.PolicyCache; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,7 +28,10 @@ public static class DataClass { @BeforeEach public void setup() { - client.delete(null, new Key("test", "testSet", 1)); + WritePolicy writePolicy = + PolicyCache.getInstance().getEffectiveWritePolicy(DataClass.class, client.getWritePolicyDefault()); + writePolicy.durableDelete = false; + client.delete(writePolicy, new Key("test", "testSet", 1)); } @Test diff --git a/src/test/java/com/aerospike/mapper/InterfaceHierarchyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/InterfaceHierarchyTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/InterfaceHierarchyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/InterfaceHierarchyTest.java diff --git a/src/test/java/com/aerospike/mapper/KeysViaMethodTest.java b/legacyapi/src/test/java/com/aerospike/mapper/KeysViaMethodTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/KeysViaMethodTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/KeysViaMethodTest.java diff --git a/legacyapi/src/test/java/com/aerospike/mapper/LegacySetterParamTypeResolverTest.java b/legacyapi/src/test/java/com/aerospike/mapper/LegacySetterParamTypeResolverTest.java new file mode 100644 index 0000000..cf3a675 --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/LegacySetterParamTypeResolverTest.java @@ -0,0 +1,42 @@ +package com.aerospike.mapper; + +import com.aerospike.mapper.tools.LegacySetterParamTypeResolver; +import com.aerospike.mapper.tools.PropertyDefinition.SetterParamType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LegacySetterParamTypeResolverTest { + + private final LegacySetterParamTypeResolver resolver = LegacySetterParamTypeResolver.INSTANCE; + + @Test + public void resolvesLegacyKeyType() { + assertEquals(SetterParamType.KEY, resolver.resolve("com.aerospike.client.Key")); + } + + @Test + public void resolvesLegacyValueType() { + assertEquals(SetterParamType.VALUE, resolver.resolve("com.aerospike.client.Value")); + } + + @Test + public void unknownType_returnsNone() { + assertEquals(SetterParamType.NONE, resolver.resolve("java.lang.String")); + } + + @Test + public void nullType_returnsNone() { + assertEquals(SetterParamType.NONE, resolver.resolve(null)); + } + + @Test + public void fluentKeyType_returnsNone() { + assertEquals(SetterParamType.NONE, resolver.resolve("com.aerospike.client.fluent.Key")); + } + + @Test + public void singletonInstance_isNotNull() { + assertNotNull(LegacySetterParamTypeResolver.INSTANCE); + } +} diff --git a/src/test/java/com/aerospike/mapper/ListOfReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ListOfReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/ListOfReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ListOfReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/LocalDateConverterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/LocalDateConverterTest.java similarity index 94% rename from src/test/java/com/aerospike/mapper/LocalDateConverterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/LocalDateConverterTest.java index 64a2089..1aa2e61 100644 --- a/src/test/java/com/aerospike/mapper/LocalDateConverterTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/LocalDateConverterTest.java @@ -7,6 +7,7 @@ import com.aerospike.mapper.tools.AeroMapper; import org.junit.jupiter.api.Test; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -27,6 +28,7 @@ public void testLocalDateTypes() { container.localDate = LocalDate.now(); container.localDateTime = LocalDateTime.now(); container.localTime = LocalTime.now(); + container.instant = Instant.now(); mapper.save(container); @@ -35,6 +37,7 @@ public void testLocalDateTypes() { assertEquals(container.localDate, readContainer.localDate); assertEquals(container.localTime, readContainer.localTime); assertEquals(container.localDateTime, readContainer.localDateTime); + assertEquals(container.instant, readContainer.instant); com.aerospike.client.Record record = client.get(null, new Key("test", "local", 1)); long value = record.getLong("localDate"); @@ -58,7 +61,7 @@ public void testEmbeddedLocalDateTypes() { LocalDateTimeTester tester = new LocalDateTimeTester(); tester.id = 1; tester.cont = container; - tester.contList = new ArrayList(); + tester.contList = new ArrayList<>(); tester.contList.add(container); mapper.save(tester); @@ -79,6 +82,7 @@ public static class LocalDateTimeContainer { public LocalTime localTime; public LocalDate localDate; public LocalDateTime localDateTime; + public Instant instant; } @AerospikeRecord(namespace = "test", set = "localNest") diff --git a/src/test/java/com/aerospike/mapper/MapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/MapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/MapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/MapTest.java diff --git a/src/test/java/com/aerospike/mapper/MessagesMappingTest.java b/legacyapi/src/test/java/com/aerospike/mapper/MessagesMappingTest.java similarity index 91% rename from src/test/java/com/aerospike/mapper/MessagesMappingTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/MessagesMappingTest.java index b896550..6bcf82a 100644 --- a/src/test/java/com/aerospike/mapper/MessagesMappingTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/MessagesMappingTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; -import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.AerospikeVersion; @@ -29,7 +29,7 @@ public void testVersion() { try { mapper.save(invalid); fail("Exception should have been thrown"); - } catch (AerospikeException e) { + } catch (AerospikeMapperException e) { assertTrue(e.getMessage().toLowerCase().contains("version"), "Inaccurate error message does not reference version"); } } diff --git a/legacyapi/src/test/java/com/aerospike/mapper/MetadataCachingTests.java b/legacyapi/src/test/java/com/aerospike/mapper/MetadataCachingTests.java new file mode 100644 index 0000000..98aa718 --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/MetadataCachingTests.java @@ -0,0 +1,136 @@ +package com.aerospike.mapper; + +import com.aerospike.mapper.annotations.AerospikeBin; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.tools.AeroMapper; +import com.aerospike.mapper.tools.ClassCache; +import com.aerospike.mapper.tools.ClassCacheEntry; +import lombok.Getter; +import lombok.Setter; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests that verify metadata caching during actual database operations. + * These tests require a running Aerospike server and verify that ClassCacheEntry + * instances are properly reused across save() and read() operations. + */ +public class MetadataCachingTests extends AeroMapperBaseTest { + + @Setter + @Getter + @AerospikeRecord(namespace = "test", set = "person") + public static class Person { + @AerospikeKey + private String id; + + @AerospikeBin(name = "fname") + private String firstName; + + private String lastName; + private int age; + private Date birthDate; + + public Person() { + } + + public Person(String id, String firstName, String lastName, int age) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.birthDate = new Date(); + } + + } + + /** + * INTEGRATION TEST: Verify cache is reused across mixed read and write operations. + * This is the primary integration test showing that the same ClassCacheEntry + * is used for both saves and reads in real database operations. + */ + @Test + public void testMetadataCachedAcrossMixedOperations() { + AeroMapper mapper = new AeroMapper.Builder(client).build(); + + // Write operation + Person person1 = new Person("p200", "Charlie", "Davis", 45); + mapper.save(person1); + + ClassCacheEntry entryAfterWrite = ClassCache.getInstance().loadClass(Person.class, mapper); + assertNotNull(entryAfterWrite, "Cache entry should exist after write"); + + // Read operation - should reuse same cache entry + Person read1 = mapper.read(Person.class, "p200"); + assertNotNull(read1, "Read should succeed"); + + ClassCacheEntry entryAfterRead = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(entryAfterWrite, entryAfterRead, + "Read should reuse the same cache entry created by write"); + + // Another write - should still reuse same cache entry + Person person2 = new Person("p201", "Diana", "Evans", 28); + mapper.save(person2); + + ClassCacheEntry entryAfterSecondWrite = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(entryAfterWrite, entryAfterSecondWrite, + "Second write should reuse the same cache entry"); + + // Another read - should still reuse same cache entry + Person read2 = mapper.read(Person.class, "p201"); + assertNotNull(read2, "Second read should succeed"); + + ClassCacheEntry entryAfterSecondRead = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(entryAfterWrite, entryAfterSecondRead, + "Mixed operations should all use the same cached instance"); + + // Verify data correctness + assertEquals("Diana", read2.getFirstName()); + assertEquals("Evans", read2.getLastName()); + assertEquals(28, read2.getAge()); + } + + /** + * STRESS TEST: Verify cache behavior with high volume database operations. + * Performs 100 save operations and verifies the same ClassCacheEntry + * is used throughout. This tests cache stability under load. + */ + @Test + public void testCacheWithHighVolumeOperations() { + AeroMapper mapper = new AeroMapper.Builder(client).build(); + + // Get the initial cached entry + ClassCacheEntry initialEntry = ClassCache.getInstance().loadClass(Person.class, mapper); + + // Perform many save operations + final int iterations = 100; + for (int i = 0; i < iterations; i++) { + Person person = new Person("p" + (500 + i), "Person" + i, "Lastname" + i, 20 + (i % 50)); + mapper.save(person); + + // Every 10 iterations, verify cache entry is still the same + if (i % 10 == 0) { + ClassCacheEntry currentEntry = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(initialEntry, currentEntry, + "Cache entry should remain the same after " + i + " operations"); + } + } + + // Final verification after all operations + ClassCacheEntry finalEntry = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(initialEntry, finalEntry, + "Cache entry should be the same after " + iterations + " operations"); + + // Verify a read also uses same cached entry + Person readPerson = mapper.read(Person.class, "p550"); + assertNotNull(readPerson, "Read should succeed after many writes"); + + ClassCacheEntry entryAfterRead = ClassCache.getInstance().loadClass(Person.class, mapper); + assertSame(initialEntry, entryAfterRead, + "Read after many writes should still use the same cached instance"); + } +} diff --git a/src/test/java/com/aerospike/mapper/MultipleParameterSetterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/MultipleParameterSetterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/MultipleParameterSetterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/MultipleParameterSetterTest.java diff --git a/src/test/java/com/aerospike/mapper/NamespacePlaceHolderTest.java b/legacyapi/src/test/java/com/aerospike/mapper/NamespacePlaceHolderTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/NamespacePlaceHolderTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/NamespacePlaceHolderTest.java diff --git a/src/test/java/com/aerospike/mapper/ObjectReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ObjectReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/ObjectReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ObjectReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/PartialRecordsTest.java b/legacyapi/src/test/java/com/aerospike/mapper/PartialRecordsTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/PartialRecordsTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/PartialRecordsTest.java diff --git a/legacyapi/src/test/java/com/aerospike/mapper/PolicyCacheTest.java b/legacyapi/src/test/java/com/aerospike/mapper/PolicyCacheTest.java new file mode 100644 index 0000000..57e4b3d --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/PolicyCacheTest.java @@ -0,0 +1,156 @@ +package com.aerospike.mapper; + +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.mapper.tools.PolicyCache; +import com.aerospike.mapper.tools.PolicyCache.PolicyType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PolicyCacheTest { + + private PolicyCache cache; + + @BeforeEach + public void setUp() { + cache = PolicyCache.getInstance(); + cache.clear(); + } + + // ── Default policy fallback ────────────────────────────────────── + + @Test + public void noPolicy_returnsNull() { + assertDoesNotThrow(() -> cache.determinePolicy(getClass(), PolicyType.READ)); + } + + @Test + public void defaultPolicy_returnedForUnknownClass() { + WritePolicy defaultWrite = new WritePolicy(); + defaultWrite.totalTimeout = 9999; + cache.setDefaultPolicy(PolicyType.WRITE, defaultWrite); + + Policy result = cache.determinePolicy(PolicyCacheTest.class, PolicyType.WRITE); + + assertSame(defaultWrite, result); + } + + // ── Specific policy precedence ─────────────────────────────────── + + @Test + public void specificPolicy_overridesDefault() { + Policy defaultRead = new Policy(); + Policy specificRead = new Policy(); + specificRead.totalTimeout = 1234; + cache.setDefaultPolicy(PolicyType.READ, defaultRead); + cache.setSpecificPolicy(PolicyType.READ, PolicyCacheTest.class, specificRead); + + Policy result = cache.determinePolicy(PolicyCacheTest.class, PolicyType.READ); + + assertSame(specificRead, result); + } + + // ── Children policy hierarchy ──────────────────────────────────── + + static class ParentEntity { + + } + + static class ChildEntity extends ParentEntity { + + } + + static class GrandchildEntity extends ChildEntity { + + } + + @Test + public void childrenPolicy_matchesSuperclass() { + Policy parentPolicy = new Policy(); + parentPolicy.totalTimeout = 555; + cache.setChildrenPolicy(PolicyType.READ, ParentEntity.class, parentPolicy); + + Policy result = cache.determinePolicy(ChildEntity.class, PolicyType.READ); + + assertSame(parentPolicy, result); + } + + @Test + public void childrenPolicy_matchesGrandchild() { + Policy parentPolicy = new Policy(); + cache.setChildrenPolicy(PolicyType.READ, ParentEntity.class, parentPolicy); + + Policy result = cache.determinePolicy(GrandchildEntity.class, PolicyType.READ); + + assertSame(parentPolicy, result); + } + + @Test + public void specificPolicy_overridesChildren() { + Policy childrenPolicy = new Policy(); + Policy specificPolicy = new Policy(); + specificPolicy.totalTimeout = 777; + cache.setChildrenPolicy(PolicyType.READ, ParentEntity.class, childrenPolicy); + cache.setSpecificPolicy(PolicyType.READ, ChildEntity.class, specificPolicy); + + Policy result = cache.determinePolicy(ChildEntity.class, PolicyType.READ); + + assertSame(specificPolicy, result); + } + + // ── Effective policy methods ───────────────────────────────────── + + @Test + public void effectiveWritePolicy_returnsNewInstance() { + WritePolicy clientDefault = new WritePolicy(); + WritePolicy result = cache.getEffectiveWritePolicy(PolicyCacheTest.class, clientDefault); + assertNotNull(result); + assertNotSame(clientDefault, result, "Must return a defensive copy"); + } + + @Test + public void effectiveReadPolicy_fallsBackToDefault() { + Policy clientDefault = new Policy(); + clientDefault.totalTimeout = 3000; + Policy result = cache.getEffectiveReadPolicy(String.class, clientDefault); + assertNotNull(result); + } + + @Test + public void effectiveBatchPolicy_fallsBackToDefault() { + BatchPolicy clientDefault = new BatchPolicy(); + BatchPolicy result = cache.getEffectiveBatchPolicy(String.class, clientDefault); + assertSame(clientDefault, result); + } + + @Test + public void effectiveScanPolicy_fallsBackToDefault() { + ScanPolicy clientDefault = new ScanPolicy(); + ScanPolicy result = cache.getEffectiveScanPolicy(String.class, clientDefault); + assertSame(clientDefault, result); + } + + @Test + public void effectiveQueryPolicy_fallsBackToDefault() { + QueryPolicy clientDefault = new QueryPolicy(); + QueryPolicy result = cache.getEffectiveQueryPolicy(String.class, clientDefault); + assertSame(clientDefault, result); + } + + // ── Null class handling ────────────────────────────────────────── + + @Test + public void determinePolicy_nullClass_returnsDefault() { + WritePolicy defaultWrite = new WritePolicy(); + cache.setDefaultPolicy(PolicyType.WRITE, defaultWrite); + + Policy result = cache.determinePolicy(null, PolicyType.WRITE); + + assertSame(defaultWrite, result); + } +} diff --git a/src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java b/legacyapi/src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java similarity index 97% rename from src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java index 16962d5..6c53553 100644 --- a/src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/PreloadingMethodsTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; -import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeBin; import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; @@ -82,7 +82,7 @@ public void TestWriteThenClearThenRead() { // Animal, Dog, Cat are unknown at this point, and not fully qualified by the // type in the database, so we expect failure fail("Classes should be unknown at this point"); - } catch (AerospikeException ignored) { + } catch (AerospikeMapperException ignored) { } } diff --git a/src/test/java/com/aerospike/mapper/QueryTest.java b/legacyapi/src/test/java/com/aerospike/mapper/QueryTest.java similarity index 96% rename from src/test/java/com/aerospike/mapper/QueryTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/QueryTest.java index c1f0171..6134742 100644 --- a/src/test/java/com/aerospike/mapper/QueryTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/QueryTest.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import lombok.Getter; import org.junit.jupiter.api.Test; import com.aerospike.client.AerospikeException; @@ -22,6 +23,7 @@ import com.aerospike.mapper.tools.AeroMapper; public class QueryTest extends AeroMapperBaseTest { + @Getter @AerospikeRecord(namespace = "test", set = "testScan") public static class A { @AerospikeKey @@ -36,18 +38,6 @@ public A(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFrom("ag this.age = age; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } - @Override public String toString() { return String.format("id:%d, name:%s, age:%d", id, name, age); diff --git a/src/test/java/com/aerospike/mapper/RecursiveObjectTest.java b/legacyapi/src/test/java/com/aerospike/mapper/RecursiveObjectTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/RecursiveObjectTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/RecursiveObjectTest.java diff --git a/src/test/java/com/aerospike/mapper/ScanTest.java b/legacyapi/src/test/java/com/aerospike/mapper/ScanTest.java similarity index 95% rename from src/test/java/com/aerospike/mapper/ScanTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/ScanTest.java index 5016f80..d3823d5 100644 --- a/src/test/java/com/aerospike/mapper/ScanTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/ScanTest.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import lombok.Getter; import org.junit.jupiter.api.Test; import com.aerospike.client.exp.Exp; @@ -19,6 +20,7 @@ import com.aerospike.mapper.tools.AeroMapper; public class ScanTest extends AeroMapperBaseTest { + @Getter @AerospikeRecord(namespace = "test", set = "testScan") public static class Person { @AerospikeKey @@ -33,18 +35,6 @@ public Person(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFro this.age = age; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } - @Override public String toString() { return String.format("id:%d, name:%s, age:%d", id, name, age); diff --git a/src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java similarity index 93% rename from src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java index ac4840d..c9cd274 100644 --- a/src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/SubClassHierarchyTest.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Map; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; import com.aerospike.mapper.annotations.AerospikeBin; @@ -34,12 +36,18 @@ public BaseClass() { @AerospikeRecord(set = "customer", namespace = "test") public static class Customer extends BaseClass { + @Getter @AerospikeKey @AerospikeBin(name = "id") private final String customerId; + @Getter private final String name; + @Setter + @Getter @AerospikeBin(name = "date") private Date dateJoined; + @Setter + @Getter @AerospikeReference private List accounts; private final Map accountMap; @@ -63,29 +71,6 @@ public Customer(@ParamFrom("id") String customerId, @ParamFrom("name") String na this.accountMap = new HashMap<>(); } - public Date getDateJoined() { - return dateJoined; - } - - public void setDateJoined(Date dateJoined) { - this.dateJoined = dateJoined; - } - - public List getAccounts() { - return accounts; - } - - public void setAccounts(List accounts) { - this.accounts = accounts; - } - - public String getCustomerId() { - return customerId; - } - - public String getName() { - return name; - } } @AerospikeRecord(namespace = "test", set = "subaccs", durableDelete = true, sendKey = true) diff --git a/src/test/java/com/aerospike/mapper/SubclassListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/SubclassListTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/SubclassListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/SubclassListTest.java diff --git a/src/test/java/com/aerospike/mapper/SubclassMapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/SubclassMapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/SubclassMapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/SubclassMapTest.java diff --git a/src/test/java/com/aerospike/mapper/TestCustomTtl.java b/legacyapi/src/test/java/com/aerospike/mapper/TestCustomTtl.java similarity index 96% rename from src/test/java/com/aerospike/mapper/TestCustomTtl.java rename to legacyapi/src/test/java/com/aerospike/mapper/TestCustomTtl.java index 716a32d..cfb2f51 100644 --- a/src/test/java/com/aerospike/mapper/TestCustomTtl.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/TestCustomTtl.java @@ -12,7 +12,7 @@ import com.aerospike.mapper.tools.AeroMapper; public class TestCustomTtl extends AeroMapperBaseTest { - + @AerospikeRecord(namespace = "test", set = "classWithTtl", ttl=300) public static class ClassWithTtl { @AerospikeKey @@ -20,37 +20,37 @@ public static class ClassWithTtl { public String name; } - @AerospikeRecord(namespace = "test", set = "classWithTtl") + @AerospikeRecord(namespace = "test", set = "classWithTtl") public static class ClassWithTtlViaPolicy { @AerospikeKey public int id; public String name; } - + @Test public void testTtl() { AeroMapper mapper = new AeroMapper.Builder(client) .build(); - + ClassWithTtl myRecord = new ClassWithTtl(); myRecord.id = 1; myRecord.name = "not overridden ttl"; mapper.save(myRecord); - + WritePolicy customWritePolicy = new WritePolicy(mapper.getWritePolicy(ClassWithTtl.class)); customWritePolicy.expiration = 100; myRecord.id = 2; myRecord.name = "overridden ttl"; mapper.save(customWritePolicy, myRecord); - + // To validate the TTL, read the records as raw records rather than via the mapper interface Record readClient1 = client.get(null, new Key("test", "classWithTtl", 1)); Record readClient2 = client.get(null, new Key("test", "classWithTtl", 2)); - + assertTrue(readClient1.getTimeToLive() > 290 && readClient1.getTimeToLive() <= 300); assertTrue(readClient2.getTimeToLive() > 90 && readClient2.getTimeToLive() <= 100); } - + @Test public void testTtlViaPolicy() { WritePolicy writePolicy = new WritePolicy(); @@ -58,22 +58,22 @@ public void testTtlViaPolicy() { AeroMapper mapper = new AeroMapper.Builder(client) .withWritePolicy(writePolicy).forClasses(ClassWithTtlViaPolicy.class) .build(); - + ClassWithTtlViaPolicy myRecord = new ClassWithTtlViaPolicy(); myRecord.id = 1; myRecord.name = "not overridden ttl"; mapper.save(myRecord); - + WritePolicy customWritePolicy = new WritePolicy(mapper.getWritePolicy(ClassWithTtlViaPolicy.class)); customWritePolicy.expiration = 100; myRecord.id = 2; myRecord.name = "overridden ttl"; mapper.save(customWritePolicy, myRecord); - + // To validate the TTL, read the records as raw records rather than via the mapper interface Record readClient1 = client.get(null, new Key("test", "classWithTtl", 1)); Record readClient2 = client.get(null, new Key("test", "classWithTtl", 2)); - + assertTrue(readClient1.getTimeToLive() > 290 && readClient1.getTimeToLive() <= 300); assertTrue(readClient2.getTimeToLive() > 90 && readClient2.getTimeToLive() <= 100); } diff --git a/src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java similarity index 93% rename from src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java index f0db6d0..d09b87a 100644 --- a/src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/UsePKColumnInsteadOfBinForKeyTest.java @@ -1,6 +1,6 @@ package com.aerospike.mapper; -import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.client.Record; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; @@ -38,7 +38,7 @@ public static class B { @Data @AllArgsConstructor @NoArgsConstructor - @AerospikeRecord(namespace = "test", set = "testSet", sendKey = false) + @AerospikeRecord(namespace = "test", set = "testSet") public static class C { @AerospikeKey(storeAsBin = false) private long key1; @@ -104,8 +104,6 @@ public void runTestViaConfig() { public void runTestWithInvalidConfig() { AeroMapper mapper = new AeroMapper.Builder(client).build(); C c = new C(1, "test"); - assertThrows(AerospikeException.class, () -> { - mapper.save(c); - }); + assertThrows(AerospikeMapperException.class, () -> mapper.save(c)); } } diff --git a/src/test/java/com/aerospike/mapper/VirtualListReferenceTest.java b/legacyapi/src/test/java/com/aerospike/mapper/VirtualListReferenceTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/VirtualListReferenceTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/VirtualListReferenceTest.java diff --git a/src/test/java/com/aerospike/mapper/VirtualListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/VirtualListTest.java similarity index 95% rename from src/test/java/com/aerospike/mapper/VirtualListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/VirtualListTest.java index 17f8fcb..b4fa63b 100644 --- a/src/test/java/com/aerospike/mapper/VirtualListTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/VirtualListTest.java @@ -5,6 +5,8 @@ import com.aerospike.mapper.tools.AeroMapper; import com.aerospike.mapper.tools.virtuallist.ReturnType; import com.aerospike.mapper.tools.virtuallist.VirtualList; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -43,11 +45,17 @@ public int hashCode() { @AerospikeRecord public static class B { + @Getter @AerospikeKey public int id; + @Getter public String name; + @Getter public long date; + @Setter + @Getter public C thisC; + @Getter public List Cs; public List otherCs; public C anonC; @@ -72,30 +80,6 @@ public B(int id, String name, long date, C thisC, C... listCs) { } } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public long getDate() { - return date; - } - - public List getCs() { - return Cs; - } - - public C getThisC() { - return thisC; - } - - public void setThisC(C thisC) { - this.thisC = thisC; - } - @Override public String toString() { return String.format("{id=%d, name=%s, date=%d, thisC=%s, listC=%s}", id, name, date, thisC, Cs); diff --git a/src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java b/legacyapi/src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java similarity index 89% rename from src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java index 96867a2..7354b3c 100644 --- a/src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/VirtualListWithKeyConstructorTest.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.List; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; import com.aerospike.mapper.annotations.AerospikeBin; @@ -23,6 +25,10 @@ public class VirtualListWithKeyConstructorTest extends AeroMapperBaseTest { public static class C { @AerospikeKey public int a; + // TODO + //this.b = key.userKey + ":" +b; + @Getter + @Setter @AerospikeBin(useAccessors = true) public String b; @@ -49,28 +55,27 @@ public int hashCode() { return 17 * a + (b == null ? 0 : b.hashCode()); } - public void setB(String b) { - // TODO - //this.b = key.userKey + ":" +b; - this.b = b; - } - - public String getB() { - return b; - } } @AerospikeRecord public static class B { + @Getter @AerospikeKey public int id; + @Getter public String name; + @Getter public long date; + @Setter + @Getter public C thisC; + @Getter @AerospikeEmbed public List Cs; @AerospikeEmbed public List otherCs; + @Setter + @Getter @AerospikeEmbed @AerospikeBin(useAccessors = true) public C anonC; @@ -98,38 +103,6 @@ public B(int id, String name, long date, C thisC, C... listCs) { } } - public C getAnonC() { - return anonC; - } - - public void setAnonC(C anonC) { - this.anonC = anonC; - } - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public long getDate() { - return date; - } - - public List getCs() { - return Cs; - } - - public C getThisC() { - return thisC; - } - - public void setThisC(C thisC) { - this.thisC = thisC; - } - @Override public String toString() { return String.format("{id=%d, name=%s, date=%d, thisC=%s, listC=%s, anonC=%s}", id, name, date, thisC, Cs, anonC); diff --git a/src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java similarity index 83% rename from src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java index 9c1c5d0..d2adee1 100644 --- a/src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/AdTechUseCaseExample.java @@ -8,6 +8,8 @@ import com.aerospike.mapper.tools.AeroMapper; import com.aerospike.mapper.tools.virtuallist.ReturnType; import com.aerospike.mapper.tools.virtuallist.VirtualList; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.Date; @@ -15,6 +17,7 @@ import java.util.concurrent.TimeUnit; public class AdTechUseCaseExample extends AeroMapperBaseTest { + @Getter @AerospikeRecord(namespace = "test", set = "user") public static class User { @AerospikeKey @@ -30,19 +33,10 @@ public User(@ParamFrom("id") String id, @ParamFrom("created") Date created, @Par this.segments = segments; } - public String getId() { - return id; - } - - public Date getCreated() { - return created; - } - - public List getSegments() { - return segments; - } } + @Setter + @Getter @AerospikeRecord public static class Segment { @AerospikeKey @@ -61,38 +55,6 @@ public Segment(@ParamFrom("name") String name, @ParamFrom("lastSeen") Date lastS this.flags = flags; } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Date getLastSeen() { - return lastSeen; - } - - public void setLastSeen(Date lastSeen) { - this.lastSeen = lastSeen; - } - - public long getPartnerId() { - return partnerId; - } - - public void setPartnerId(long partnerId) { - this.partnerId = partnerId; - } - - public long getFlags() { - return flags; - } - - public void setFlags(long flags) { - this.flags = flags; - } - @Override public String toString() { return String.format("{name=%s, lastSeen=%s, partnerId=%d, flags=%d}\n", name, lastSeen.toString(), partnerId, flags); diff --git a/src/test/java/com/aerospike/mapper/examples/AeroMapperDocExamples.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/AeroMapperDocExamples.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/AeroMapperDocExamples.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/AeroMapperDocExamples.java diff --git a/src/test/java/com/aerospike/mapper/examples/Application.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/Application.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/Application.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/Application.java diff --git a/src/test/java/com/aerospike/mapper/examples/ApplicationBase.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/ApplicationBase.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/ApplicationBase.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/ApplicationBase.java diff --git a/src/test/java/com/aerospike/mapper/examples/JavaMapperApplication.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/JavaMapperApplication.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/JavaMapperApplication.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/JavaMapperApplication.java diff --git a/src/test/java/com/aerospike/mapper/examples/NonJavaMapperApplication.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/NonJavaMapperApplication.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/NonJavaMapperApplication.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/NonJavaMapperApplication.java diff --git a/src/test/java/com/aerospike/mapper/examples/VirtualListExample.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/VirtualListExample.java similarity index 93% rename from src/test/java/com/aerospike/mapper/examples/VirtualListExample.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/VirtualListExample.java index 373742e..1b8d617 100644 --- a/src/test/java/com/aerospike/mapper/examples/VirtualListExample.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/VirtualListExample.java @@ -17,16 +17,9 @@ public class VirtualListExample extends AeroMapperBaseTest { @AerospikeRecord(namespace = "test", set = "item") public static class Item { - @AerospikeKey - private int id; - private Date due; - private String desc; public Item(int id, Date due, String desc) { super(); - this.id = id; - this.due = due; - this.desc = desc; } public Item() { diff --git a/src/test/java/com/aerospike/mapper/examples/model/Address.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Address.java similarity index 59% rename from src/test/java/com/aerospike/mapper/examples/model/Address.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/Address.java index 8440bf2..60f271c 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/Address.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Address.java @@ -4,8 +4,12 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; +import lombok.Getter; +import lombok.Setter; // Addresses are only ever embedded in parent records, so do not need information to map to Aerospike. +@Setter +@Getter @AerospikeRecord public class Address { @AerospikeKey @@ -30,43 +34,4 @@ public Address( this.zipCode = zipCode; } - public String getLine1() { - return line1; - } - - public void setLine1(String line1) { - this.line1 = line1; - } - - public String getLine2() { - return line2; - } - - public void setLine2(String line2) { - this.line2 = line2; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public String getZipCode() { - return zipCode; - } - - public void setZipCode(String zipCode) { - this.zipCode = zipCode; - } } diff --git a/src/test/java/com/aerospike/mapper/examples/model/Branch.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Branch.java similarity index 81% rename from src/test/java/com/aerospike/mapper/examples/model/Branch.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/Branch.java index ea79e09..eae1cda 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/Branch.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Branch.java @@ -5,7 +5,9 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; +import lombok.Getter; +@Getter @AerospikeRecord(namespace = "test", set = "branch") public class Branch { @AerospikeKey @@ -22,15 +24,4 @@ public Branch(@ParamFrom("id") String id, @ParamFrom("addr") Address address, @P this.name = name; } - public String getId() { - return id; - } - - public Address getAddress() { - return address; - } - - public String getName() { - return name; - } } diff --git a/src/test/java/com/aerospike/mapper/examples/model/Checkbook.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Checkbook.java similarity index 74% rename from src/test/java/com/aerospike/mapper/examples/model/Checkbook.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/Checkbook.java index 247843a..962892d 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/Checkbook.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Checkbook.java @@ -5,14 +5,23 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; +import lombok.Getter; +import lombok.Setter; @AerospikeRecord(namespace = "test", set = "chkbook") public class Checkbook { private String acctId; + @Getter private long first; + @Getter private long last; + @Getter private final Date issued; + @Setter + @Getter private boolean recalled; + @Setter + @Getter private Branch issuer; public Checkbook(@ParamFrom("acctId") String acctId, @ParamFrom("first") long first, @ParamFrom("last") long last, @ParamFrom("issued") Date issued) { @@ -23,34 +32,6 @@ public Checkbook(@ParamFrom("acctId") String acctId, @ParamFrom("first") long fi this.issued = issued; } - public long getFirst() { - return first; - } - - public long getLast() { - return last; - } - - public Date getIssued() { - return issued; - } - - public boolean isRecalled() { - return recalled; - } - - public void setRecalled(boolean recalled) { - this.recalled = recalled; - } - - public Branch getIssuer() { - return issuer; - } - - public void setIssuer(Branch issuer) { - this.issuer = issuer; - } - /* The checkbook class does not have a key in it's own right, it uses a composite key */ @AerospikeKey public String getKey() { diff --git a/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Customer.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Customer.java new file mode 100644 index 0000000..a0af84c --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Customer.java @@ -0,0 +1,57 @@ +package com.aerospike.mapper.examples.model; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.aerospike.mapper.annotations.AerospikeBin; +import com.aerospike.mapper.annotations.AerospikeEmbed; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.annotations.ParamFrom; +import com.aerospike.mapper.examples.model.accounts.Account; +import lombok.Getter; +import lombok.Setter; + +@Getter +@AerospikeRecord(namespace = "test", set = "customer") +public class Customer { + @AerospikeKey + @AerospikeBin(name = "id") + private final String customerId; + + @Setter + private String firstName; + @Setter + private String lastName; + + @Setter + @AerospikeEmbed + @AerospikeBin(name = "mail") + private Address mailingAddress; + + @Setter + private List accounts; + + @Setter + @AerospikeBin(name = "dob") + private Date dateOfBirth; + @Setter + private String phone; + @Setter + private Date joinedBank; + @Setter + private boolean vip; + @Setter + @AerospikeBin(name = "greet") + private String preferredSalutation; + + public Customer(@ParamFrom("id") String customerId, @ParamFrom("firstName") String firstName, @ParamFrom("lastName") String lastName) { + super(); + this.customerId = customerId; + this.firstName = firstName; + this.lastName = lastName; + this.accounts = new ArrayList<>(); + } + +} diff --git a/src/test/java/com/aerospike/mapper/examples/model/InterestType.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/InterestType.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/model/InterestType.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/InterestType.java diff --git a/src/test/java/com/aerospike/mapper/examples/model/Property.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Property.java similarity index 68% rename from src/test/java/com/aerospike/mapper/examples/model/Property.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/Property.java index 2cad11c..d69f9a0 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/Property.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Property.java @@ -8,7 +8,10 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; +import lombok.Getter; +import lombok.Setter; +@Getter @AerospikeRecord(namespace = "test", set = "property") public class Property { @@ -20,8 +23,10 @@ public class Property { private final List valuations; @AerospikeBin(name = "cyear") private final int constructionYear; + @Setter @AerospikeBin(name = "cnstrn") private String construction; + @Setter private String notes; public Property(@ParamFrom("id") long id, @ParamFrom("address") Address address, @ParamFrom("cyear") int constructionYear, @ParamFrom("cnstrn") String construction) { @@ -33,40 +38,9 @@ public Property(@ParamFrom("id") long id, @ParamFrom("address") Address address, this.valuations = new ArrayList<>(); } - public long getId() { - return id; - } - - public List getValuations() { - return valuations; - } - public Property addValuation(Valuation valuation) { this.valuations.add(valuation); return this; } - public String getConstruction() { - return construction; - } - - public void setConstruction(String construction) { - this.construction = construction; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; - } - - public Address getAddress() { - return address; - } - - public int getConstructionYear() { - return constructionYear; - } } diff --git a/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Transaction.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Transaction.java new file mode 100644 index 0000000..6ca16f1 --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Transaction.java @@ -0,0 +1,18 @@ +package com.aerospike.mapper.examples.model; + +import java.time.Instant; + +import com.aerospike.mapper.annotations.AerospikeRecord; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@AerospikeRecord +public class Transaction { + private long amount; + private String description; + private Instant time; + private boolean isFraud; + +} diff --git a/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Valuation.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Valuation.java new file mode 100644 index 0000000..7b76fdf --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/Valuation.java @@ -0,0 +1,31 @@ +package com.aerospike.mapper.examples.model; + +import java.util.Date; + +import com.aerospike.mapper.annotations.AerospikeRecord; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@AerospikeRecord +public class Valuation { + private Date date; + private long amount; + private long amountMargin; + private String valuer; + private long valuerCompanyId; + + public Valuation() { + } + + public Valuation(Date date, long amount, long amountMargin, String valuer, long valuerCompanyId, Address valuerAddress) { + super(); + this.date = date; + this.amount = amount; + this.amountMargin = amountMargin; + this.valuer = valuer; + this.valuerCompanyId = valuerCompanyId; + } + +} diff --git a/src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java similarity index 67% rename from src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java index dad1083..4023845 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/Account.java @@ -13,6 +13,8 @@ import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.examples.model.Address; import com.aerospike.mapper.examples.model.Checkbook; +import lombok.Getter; +import lombok.Setter; @AerospikeRecord(namespace = "test", set = "account") public class Account { @@ -22,32 +24,41 @@ public class Account { private final String title; private final AccountType type; - @AerospikeBin(name = "custId") - private final String customerId; - + @Setter + @Getter @AerospikeEmbed @AerospikeBin(name = "bill") private Address billingAddress; + @Setter @AerospikeEmbed @AerospikeBin(name = "mail") private Address mailingAddress; + @Setter @AerospikeBin(name = "alt") @AerospikeEmbed private List
alternateAddresses; + @Setter private long balance; + @Setter private String routing; + @Setter @AerospikeBin(name = "odProt") private boolean overdraftProtection; + @Setter private boolean card; + @Setter private boolean paperless; + @Setter @AerospikeBin(name = "chkBk") private Map checkbooks; + @Setter @AerospikeBin(name = "usr") private String onlineUserName; + @Setter @AerospikeBin(name = "lstLgn") private Date lastLogin; @@ -55,77 +66,40 @@ public Account(@ParamFrom("id") String accountId, @ParamFrom("custId") String cu super(); this.accountId = accountId; this.title = title; - this.customerId = customerId; this.type = type; alternateAddresses = new ArrayList<>(); checkbooks = new HashMap<>(); } - public Address getBillingAddress() { - return billingAddress; - } - - public void setBillingAddress(Address billingAddress) { - this.billingAddress = billingAddress; - } - public List
getAlternateAddresses() { return alternateAddresses; } - public void setAlternateAddresses(List
alternateAddresses) { - this.alternateAddresses = alternateAddresses; - } - public long getBalance() { return balance; } - public void setBalance(long balance) { - this.balance = balance; - } - public String getRouting() { return routing; } - public void setRouting(String routing) { - this.routing = routing; - } - public boolean isOverdraftProtection() { return overdraftProtection; } - public void setOverdraftProtection(boolean overdraftProtection) { - this.overdraftProtection = overdraftProtection; - } - public boolean isCard() { return card; } - public void setCard(boolean card) { - this.card = card; - } - public boolean isPaperless() { return paperless; } - public void setPaperless(boolean paperless) { - this.paperless = paperless; - } - public Map getCheckbooks() { return checkbooks; } - public void setCheckbooks(Map checkbooks) { - this.checkbooks = checkbooks; - } - public String getAccountId() { return accountId; } @@ -138,26 +112,14 @@ public String getOnlineUserName() { return onlineUserName; } - public void setOnlineUserName(String onlineUserName) { - this.onlineUserName = onlineUserName; - } - public Date getLastLogin() { return lastLogin; } - public void setLastLogin(Date lastLogin) { - this.lastLogin = lastLogin; - } - public Address getMailingAddress() { return mailingAddress; } - public void setMailingAddress(Address mailingAddress) { - this.mailingAddress = mailingAddress; - } - public AccountType getType() { return type; } diff --git a/src/test/java/com/aerospike/mapper/examples/model/accounts/AccountType.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/AccountType.java similarity index 100% rename from src/test/java/com/aerospike/mapper/examples/model/accounts/AccountType.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/AccountType.java diff --git a/src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java similarity index 68% rename from src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java index ff43c97..7470915 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/LoanAccount.java @@ -7,13 +7,18 @@ import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.examples.model.InterestType; import com.aerospike.mapper.examples.model.Property; +import lombok.Getter; +import lombok.Setter; // Loan account rolls up under the Account +@Getter @AerospikeRecord public class LoanAccount extends Account { + @Setter @AerospikeBin(name = "prop") private Property securityProperty; + @Setter @AerospikeBin(name = "intType") private InterestType interestType; @AerospikeBin(name = "orig") @@ -36,31 +41,4 @@ public LoanAccount( this.originationDate = originationDate; } - public Property getSecurityProperty() { - return securityProperty; - } - - public void setSecurityProperty(Property securityProperty) { - this.securityProperty = securityProperty; - } - - public InterestType getInterestType() { - return interestType; - } - - public void setInterestType(InterestType interestType) { - this.interestType = interestType; - } - - public Date getOriginationDate() { - return originationDate; - } - - public Date getExpirationDate() { - return expirationDate; - } - - public float getRate() { - return rate; - } } diff --git a/src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java similarity index 84% rename from src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java rename to legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java index 94b97c5..e53b359 100644 --- a/src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/examples/model/accounts/PortfolioAccount.java @@ -9,7 +9,9 @@ import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.examples.model.Property; +import lombok.Getter; +@Getter @AerospikeRecord(namespace = "test", set = "portfolio") public class PortfolioAccount extends Account { @@ -35,15 +37,4 @@ public void addPropertyToPortfolio(Property property, float interestRate) { this.interestRates.put(property.getId(), interestRate); } - public List getProperties() { - return properties; - } - - public int[] getContractClausesExcluded() { - return contractClausesExcluded; - } - - public Map getInterestRates() { - return interestRates; - } } diff --git a/src/test/java/com/aerospike/mapper/model/Account.java b/legacyapi/src/test/java/com/aerospike/mapper/model/Account.java similarity index 64% rename from src/test/java/com/aerospike/mapper/model/Account.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/Account.java index 5b5ad37..a76da60 100644 --- a/src/test/java/com/aerospike/mapper/model/Account.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/Account.java @@ -7,7 +7,11 @@ import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.AerospikeReference; import com.aerospike.mapper.annotations.AerospikeSetter; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter @AerospikeRecord(namespace = "test", set = "account", version = 2) public class Account { @AerospikeKey @@ -23,38 +27,6 @@ public class Account { @AerospikeExclude private int unmapped; - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public int getBalance() { - return balance; - } - - public void setBalance(int balance) { - this.balance = balance; - } - - public int getUnmapped() { - return unmapped; - } - - public void setUnmapped(int unmapped) { - this.unmapped = unmapped; - } - @AerospikeSetter(name = "bob") public void setCraziness(int value) { unmapped = value / 3; @@ -65,14 +37,6 @@ public int getCraziness() { return unmapped * 3; } - public Product getProduct() { - return product; - } - - public void setProduct(Product product) { - this.product = product; - } - @Override public String toString() { return String.format("id: %d, title: %s, balance: %d, unmapped: %d", id, title, balance, unmapped); diff --git a/legacyapi/src/test/java/com/aerospike/mapper/model/Person.java b/legacyapi/src/test/java/com/aerospike/mapper/model/Person.java new file mode 100644 index 0000000..bebcf2d --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/Person.java @@ -0,0 +1,76 @@ +package com.aerospike.mapper.model; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.aerospike.mapper.annotations.AerospikeBin; +import com.aerospike.mapper.annotations.AerospikeEmbed; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.annotations.AerospikeReference; +import lombok.Getter; +import lombok.Setter; + +// The set name will read a system definition of people.set.name for the set name. If not set, it will use "people". For example: +// -Dpeople.set.name=persons +@Setter +@Getter +@AerospikeRecord(namespace = "test", set = "${people.set.name:people}") +public class Person { + + @AerospikeKey + private String ssn; + + private String firstName; + private String lastName; + private int age; + private Date dateOfBirth; + private boolean isValid; + private float balance; + private double height; + private byte[] photo; + private long[] longData; + + @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.MAP) + private Account[] accountArray; + + private List stringList; + private String[] stringArray; + + @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.LIST) + private List accounts; + + @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.LIST) + private Map productMap; + + private Map testMap; + + @AerospikeEmbed(type = AerospikeEmbed.EmbedType.LIST) + private Account primaryAccount; + + @AerospikeBin(name = "2ndAcc") + @AerospikeEmbed(type = AerospikeEmbed.EmbedType.MAP) + private Account secondaryAccount; + + @AerospikeBin(name = "3rdAcc") + @AerospikeReference() + private Account tertiaryAccount; + + public Person() { + accounts = new ArrayList<>(); + } + + + @Override + public String toString() { + byte[] bytes = getPhoto(); + String byteStr = bytes == null ? "null" : Base64.getEncoder().encodeToString(bytes); + return String.format("{ssn=%s, firstName=%s, lastName=%s, age=%d, dob=%s, valid=%b, balance=%f, height=%f, photo=%s}", + this.getSsn(), this.getFirstName(), this.getLastName(), this.getAge(), + this.dateOfBirth == null ? null : this.getDateOfBirth().toString(), this.isValid(), + this.getBalance(), getHeight(), byteStr); + } +} diff --git a/legacyapi/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java b/legacyapi/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java new file mode 100644 index 0000000..6f6530a --- /dev/null +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java @@ -0,0 +1,50 @@ +package com.aerospike.mapper.model; + +import java.util.Base64; +import java.util.Date; +import java.util.List; + +import com.aerospike.mapper.annotations.AerospikeBin; +import com.aerospike.mapper.annotations.AerospikeKey; +import com.aerospike.mapper.annotations.AerospikeRecord; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@AerospikeRecord(namespace = "test", set = "people") +public class PersonDifferentNames { + + @AerospikeKey + @AerospikeBin(name = "s") + private String ssn; + + @AerospikeBin(name = "f") + private String firstName; + + @AerospikeBin(name = "l") + private String lastName; + + @AerospikeBin(name = "a") + private int age; + + private Date dateOfBirth; + private boolean isValid; + private float balance; + private double height; + private byte[] photo; + private List list; + + public PersonDifferentNames() { + } + + @Override + public String toString() { + byte[] bytes = getPhoto(); + String byteStr = bytes == null ? "null" : Base64.getEncoder().encodeToString(bytes); + return String.format("{ssn=%s, firstName=%s, lastName=%s, age=%d, dob=%s, valid=%b, balance=%f, height=%f, photo=%s}", + this.getSsn(), this.getFirstName(), this.getLastName(), this.getAge(), + this.dateOfBirth == null ? null : this.getDateOfBirth().toString(), this.isValid(), + this.getBalance(), getHeight(), byteStr); + } +} diff --git a/src/test/java/com/aerospike/mapper/model/Product.java b/legacyapi/src/test/java/com/aerospike/mapper/model/Product.java similarity index 55% rename from src/test/java/com/aerospike/mapper/model/Product.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/Product.java index 2638ea7..20b1475 100644 --- a/src/test/java/com/aerospike/mapper/model/Product.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/Product.java @@ -2,7 +2,11 @@ import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter @AerospikeRecord(namespace = "test", set = "product") public class Product { @@ -27,35 +31,4 @@ public String getKey() { return id + ":" + version; } - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public ProductType getType() { - return type; - } - - public void setType(ProductType type) { - this.type = type; - } - - public int getVersion() { - return version; - } - - public void setVersion(int version) { - this.version = version; - } } diff --git a/src/test/java/com/aerospike/mapper/model/ProductType.java b/legacyapi/src/test/java/com/aerospike/mapper/model/ProductType.java similarity index 100% rename from src/test/java/com/aerospike/mapper/model/ProductType.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/ProductType.java diff --git a/src/test/java/com/aerospike/mapper/model/preload/Animal.java b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Animal.java similarity index 90% rename from src/test/java/com/aerospike/mapper/model/preload/Animal.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/preload/Animal.java index e023809..e496b22 100644 --- a/src/test/java/com/aerospike/mapper/model/preload/Animal.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Animal.java @@ -21,7 +21,4 @@ public Animal(@ParamFrom("animalId") String animalId) { this.animalId = animalId; } - public String getAnimalId() { - return animalId; - } } diff --git a/src/test/java/com/aerospike/mapper/model/preload/Cat.java b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Cat.java similarity index 87% rename from src/test/java/com/aerospike/mapper/model/preload/Cat.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/preload/Cat.java index d0639d6..3c55090 100644 --- a/src/test/java/com/aerospike/mapper/model/preload/Cat.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Cat.java @@ -25,15 +25,8 @@ public Cat(@ParamFrom("animalId") String catId, @ParamFrom("breed") String breed this.lifeSpan = lifeSpan; } - public String getBreed() { - return breed; - } - public String getCatId() { return super.getAnimalId(); } - public String getLifeSpan() { - return lifeSpan; - } } diff --git a/src/test/java/com/aerospike/mapper/model/preload/Dog.java b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Dog.java similarity index 92% rename from src/test/java/com/aerospike/mapper/model/preload/Dog.java rename to legacyapi/src/test/java/com/aerospike/mapper/model/preload/Dog.java index d64922a..7cd2827 100644 --- a/src/test/java/com/aerospike/mapper/model/preload/Dog.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/model/preload/Dog.java @@ -22,10 +22,6 @@ public Dog(@ParamFrom("animalId") String dogId, @ParamFrom("breed") String breed this.breed = breed; } - public String getBreed() { - return breed; - } - public String getDogId() { return super.getAnimalId(); } diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperBaseTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperBaseTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperBaseTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperBaseTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java similarity index 98% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java index ada6178..e3b2f74 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperComplexClassTest.java @@ -29,7 +29,7 @@ public static class ComplexClass { private List byte1Fields; private List charFields; private List strFields; - private boolean testData = false; + private final boolean testData = false; public ComplexClass() { } diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperCustomConverterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperCustomConverterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperCustomConverterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperCustomConverterTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperEnumTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperEnumTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperEnumTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperEnumTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListOfObjectTransformTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListOfObjectTransformTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListOfObjectTransformTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListOfObjectTransformTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperListTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperMapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperMapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperMapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperMapTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperObjectTransformTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperObjectTransformTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperObjectTransformTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperObjectTransformTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAeroMapperTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveAnonymousReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAnonymousReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveAnonymousReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveAnonymousReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveArrayTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveArrayTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveArrayTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveArrayTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveBatchLoadTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveBatchLoadTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveBatchLoadTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveBatchLoadTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java similarity index 93% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java index e47225b..ff989e1 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveCompetingAnnotationsTest.java @@ -1,6 +1,6 @@ package com.aerospike.mapper.reactive; -import com.aerospike.client.AerospikeException; +import com.aerospike.mapper.exceptions.AerospikeMapperException; import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; @@ -75,7 +75,7 @@ public void testCompetingReferenceAndEmbedAnnotations() { try { reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -90,7 +90,7 @@ public void testCompetingListReferenceAndEmbedAnnotations() { try { reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -104,7 +104,7 @@ public void testMapCompetingReferenceAndEmbedAnnotations() { try { reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } @@ -119,7 +119,7 @@ public void testCompetingArrayReferenceAndEmbedAnnotations() { try { reactiveMapper.save(b).subscribeOn(Schedulers.parallel()).block(); fail(); - } catch (AerospikeException ae) { + } catch (AerospikeMapperException ae) { assertTrue(true); } } diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveConstructorTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveConstructorTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveConstructorTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveConstructorTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveDateCustomConverterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveDateCustomConverterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveDateCustomConverterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveDateCustomConverterTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveInheritanceTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInheritanceTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveInheritanceTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInheritanceTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java similarity index 83% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java index d2b15a9..6ab5ae3 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInsertOnlyTest.java @@ -4,8 +4,10 @@ import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.mapper.InsertOnlyTest; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; +import com.aerospike.mapper.tools.PolicyCache; import com.aerospike.mapper.tools.ReactiveAeroMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,7 +29,11 @@ public static class DataClass { @BeforeEach public void setup() { - client.delete(null, new Key("test", "testSet", 1)); + WritePolicy writePolicy = + PolicyCache.getInstance() + .getEffectiveWritePolicy(InsertOnlyTest.DataClass.class, client.getWritePolicyDefault()); + writePolicy.durableDelete = false; + client.delete(writePolicy, new Key("test", "testSet", 1)); } @Test diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java similarity index 97% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java index d619a4c..93795bb 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveInterfaceHierarchyTest.java @@ -11,12 +11,10 @@ import com.aerospike.mapper.annotations.AerospikeEmbed; import com.aerospike.mapper.annotations.AerospikeKey; import com.aerospike.mapper.annotations.AerospikeRecord; -import com.aerospike.mapper.tools.AeroMapper; import com.aerospike.mapper.tools.ReactiveAeroMapper; import lombok.Data; import lombok.NoArgsConstructor; -import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; public class ReactiveInterfaceHierarchyTest extends ReactiveAeroMapperBaseTest { diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveKeysViaMethodTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveKeysViaMethodTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveKeysViaMethodTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveKeysViaMethodTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveListOfReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveListOfReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveListOfReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveListOfReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveMapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveMapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveMapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveMapTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveMultipleParameterSetterTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveMultipleParameterSetterTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveMultipleParameterSetterTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveMultipleParameterSetterTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveObjectReferencesTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveObjectReferencesTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveObjectReferencesTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveObjectReferencesTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactivePartialRecordsTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactivePartialRecordsTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactivePartialRecordsTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactivePartialRecordsTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java similarity index 93% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java index 76748d0..bc189e7 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveQueryTest.java @@ -7,6 +7,7 @@ import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.tools.ReactiveAeroMapper; +import lombok.Getter; import org.junit.jupiter.api.Test; import reactor.core.scheduler.Schedulers; @@ -15,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class ReactiveQueryTest extends ReactiveAeroMapperBaseTest { + @Getter @AerospikeRecord(namespace = "test", set = "testScan") public static class A { @AerospikeKey @@ -29,18 +31,6 @@ public A(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFrom("ag this.age = age; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } - @Override public String toString() { return String.format("id:%d, name:%s, age:%d", id, name, age); diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveRecursiveObjectTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveRecursiveObjectTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveRecursiveObjectTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveRecursiveObjectTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java similarity index 92% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java index 7185fdd..a40a7ce 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveScanTest.java @@ -6,6 +6,7 @@ import com.aerospike.mapper.annotations.AerospikeRecord; import com.aerospike.mapper.annotations.ParamFrom; import com.aerospike.mapper.tools.ReactiveAeroMapper; +import lombok.Getter; import org.junit.jupiter.api.Test; import reactor.core.scheduler.Schedulers; @@ -14,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class ReactiveScanTest extends ReactiveAeroMapperBaseTest { + @Getter @AerospikeRecord(namespace = "test", set = "testScan") public static class Person { @AerospikeKey @@ -28,17 +30,6 @@ public Person(@ParamFrom("id") int id, @ParamFrom("name") String name, @ParamFro this.age = age; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } } private ReactiveAeroMapper populate() { diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassListTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassListTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassMapTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassMapTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassMapTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveSubclassMapTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveUsePKColumnInsteadOfBinForKeyTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveUsePKColumnInsteadOfBinForKeyTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveUsePKColumnInsteadOfBinForKeyTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveUsePKColumnInsteadOfBinForKeyTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListReferenceTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListReferenceTest.java similarity index 100% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListReferenceTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListReferenceTest.java diff --git a/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java similarity index 96% rename from src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java rename to legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java index dfab015..12a97e4 100644 --- a/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java +++ b/legacyapi/src/test/java/com/aerospike/mapper/reactive/ReactiveVirtualListTest.java @@ -4,6 +4,8 @@ import com.aerospike.mapper.tools.ReactiveAeroMapper; import com.aerospike.mapper.tools.virtuallist.ReactiveVirtualList; import com.aerospike.mapper.tools.virtuallist.ReturnType; +import lombok.Getter; +import lombok.Setter; import org.junit.jupiter.api.Test; import reactor.core.scheduler.Schedulers; @@ -43,11 +45,17 @@ public int hashCode() { @AerospikeRecord public static class B { + @Getter @AerospikeKey public int id; + @Getter public String name; + @Getter public long date; + @Setter + @Getter public C thisC; + @Getter public List Cs; public List otherCs; public C anonC; @@ -72,30 +80,6 @@ public B(int id, String name, long date, C thisC, C... listCs) { } } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public long getDate() { - return date; - } - - public List getCs() { - return Cs; - } - - public C getThisC() { - return thisC; - } - - public void setThisC(C thisC) { - this.thisC = thisC; - } - @Override public String toString() { return String.format("{id=%d, name=%s, date=%d, thisC=%s, listC=%s}", id, name, date, thisC, Cs); diff --git a/pom.xml b/pom.xml index af5e019..5a6cf4a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,19 @@ 4.0.0 com.aerospike - java-object-mapper + java-object-mapper-parent 2.6.0 - jar + pom - Aerospike Object Mapper - Aerospike Object Mapper aims to lower the amount of code required when mapping POJOs - to Aerospike and back as well as reducing some of the brittleness of the code. + Aerospike Object Mapper Parent + Parent POM for the Aerospike Object Mapper multi-module project. https://github.com/aerospike/java-object-mapper + + + core + legacyapi + fluentapi + Aerospike Inc. https://www.aerospike.com @@ -31,11 +36,13 @@ 2.0.1.Final 9.0.5 9.0.5 + 0.0.1_dev 3.18.0 2.19.1 1.18.38 3.7.7 5.11.4 + 4.11.0 @@ -73,146 +80,151 @@ - - - - javax.validation - validation-api - ${javax.validation-api.version} - - - - - com.aerospike - aerospike-client-jdk8 - ${aerospike-client-jdk8.version} - - - - com.aerospike - aerospike-reactor-client - ${aerospike-reactor.version} - - - - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - - - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson-dataformat-yaml.version} - - - - org.projectlombok - lombok - ${lombok.version} - provided - - - - io.projectreactor - reactor-test - ${reactor-test.version} - test - + + + + com.aerospike + java-object-mapper-core + ${project.version} + + + javax.validation + validation-api + ${javax.validation-api.version} + + + com.aerospike + aerospike-client-jdk8 + ${aerospike-client-jdk8.version} + + + com.aerospike + aerospike-reactor-client + ${aerospike-reactor.version} + + + com.aerospike + aerospike-client-fluent + ${aerospike-client-fluent.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson-dataformat-yaml.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + io.projectreactor + reactor-test + ${reactor-test.version} + test + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + - - org.junit.jupiter - junit-jupiter - ${junit-jupiter.version} - test - - - - - org.apache.maven.plugins - maven-source-plugin - ${maven.source.plugin.version} - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven.javadoc.plugin.version} - - none - ${basedir} - Aerospike Java Object Mapper - public - true - Copyright © 2020–{currentYear} Aerospike, Inc. All rights reserved. - ${basedir}/src/main/java - 1.8 - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven.gpg.plugin.version} - - - sign-artifacts - verify - - sign - - - - - - maven-compiler-plugin - ${maven.compiler.plugin.version} - - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - org.sonatype.central - central-publishing-maven-plugin - ${maven-central-publishing-plugin.version} - true - - central - - - - - - ${project.basedir}/src/main/java - - **/*.properties - **/*.xml - - - + + + + org.apache.maven.plugins + maven-source-plugin + ${maven.source.plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + none + 1.8 + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + verify + + sign + + + + + + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.sonatype.central + central-publishing-maven-plugin + ${maven-central-publishing-plugin.version} + true + + central + + + + diff --git a/src/main/java/com/aerospike/mapper/tools/ClassCache.java b/src/main/java/com/aerospike/mapper/tools/ClassCache.java deleted file mode 100644 index 19cd0ff..0000000 --- a/src/main/java/com/aerospike/mapper/tools/ClassCache.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.aerospike.mapper.tools; - -import com.aerospike.client.AerospikeException; -import com.aerospike.client.IAerospikeClient; -import com.aerospike.client.policy.BatchPolicy; -import com.aerospike.client.policy.Policy; -import com.aerospike.client.policy.QueryPolicy; -import com.aerospike.client.policy.ScanPolicy; -import com.aerospike.client.policy.WritePolicy; -import com.aerospike.client.reactor.IAerospikeReactorClient; -import com.aerospike.mapper.exceptions.NotAnnotatedClass; -import com.aerospike.mapper.tools.configuration.ClassConfig; -import com.aerospike.mapper.tools.configuration.Configuration; -import com.aerospike.mapper.tools.utils.TypeUtils; - -import javax.validation.constraints.NotNull; -import java.util.HashMap; -import java.util.Map; - -public class ClassCache { - - private static final ClassCache instance = new ClassCache(); - private final Map, ClassCacheEntry> cacheMap = new HashMap<>(); - private final Map classesConfig = new HashMap<>(); - private final Map defaultPolicies = new HashMap<>(); - private final Map> storedNameToCacheEntry = new HashMap<>(); - private final Map, Policy>> childrenPolicies = new HashMap<>(); - private final Map, Policy>> specificPolicies = new HashMap<>(); - private final Object lock = new Object(); - - private ClassCache() { - for (PolicyType thisType : PolicyType.values()) { - this.childrenPolicies.put(thisType, new HashMap<>()); - this.specificPolicies.put(thisType, new HashMap<>()); - } - } - - public static ClassCache getInstance() { - return instance; - } - - public ClassCacheEntry loadClass(@NotNull Class clazz, IBaseAeroMapper mapper) { - return loadClass(clazz, mapper, true); - } - - @SuppressWarnings("unchecked") - public ClassCacheEntry loadClass(@NotNull Class clazz, IBaseAeroMapper mapper, boolean requireRecord) { - // Clazz can be null if an interface is passed - if (clazz == null || clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class) - || clazz.equals(Character.class) || Number.class.isAssignableFrom(clazz)) { - return null; - } - - ClassCacheEntry entry = (ClassCacheEntry) cacheMap.get(clazz); - if (entry == null || entry.isNotConstructed()) { - synchronized (lock) { - entry = (ClassCacheEntry) cacheMap.get(clazz); - if (entry == null) { - try { - // Construct a class cache entry. This must be done in 2 steps, one creating the entry - // and the other finalizing construction of it. - // This is to cater for classes which recursively refer to themselves, such as - // public static class A { - // @AerospikeKey - // public int id; - // public A a; - // } - entry = new ClassCacheEntry<>(clazz, mapper, getClassConfig(clazz), requireRecord, - determinePolicy(clazz, PolicyType.READ), - (WritePolicy) determinePolicy(clazz, PolicyType.WRITE), - (BatchPolicy) determinePolicy(clazz, PolicyType.BATCH), - (QueryPolicy) determinePolicy(clazz, PolicyType.QUERY), - (ScanPolicy) determinePolicy(clazz, PolicyType.SCAN)); - - } catch (NotAnnotatedClass nae) { - return null; - } - cacheMap.put(clazz, entry); - try { - entry.construct(); - } catch (IllegalArgumentException iae) { - cacheMap.remove(clazz); - return null; - } catch (Exception e) { - cacheMap.remove(clazz); - throw e; - } - } - } - } - return entry; - } - - // package visibility - void setStoredName(@NotNull ClassCacheEntry entry, @NotNull String name) { - ClassCacheEntry existingEntry = storedNameToCacheEntry.get(name); - if (existingEntry != null && !(existingEntry.equals(entry))) { - String errorMessage = String.format("Stored name of \"%s\" is used for both %s and %s", - name, existingEntry.getUnderlyingClass().getName(), entry.getUnderlyingClass().getName()); - throw new AerospikeException(errorMessage); - } else { - storedNameToCacheEntry.put(name, entry); - } - } - - public ClassCacheEntry getCacheEntryFromStoredName(@NotNull String name) { - return storedNameToCacheEntry.get(name); - } - - void setDefaultPolicies(IAerospikeClient client) { - if (client != null) { - this.defaultPolicies.put(PolicyType.READ, client.getReadPolicyDefault()); - this.defaultPolicies.put(PolicyType.WRITE, client.getWritePolicyDefault()); - this.defaultPolicies.put(PolicyType.BATCH, client.getBatchPolicyDefault()); - this.defaultPolicies.put(PolicyType.QUERY, client.getQueryPolicyDefault()); - this.defaultPolicies.put(PolicyType.SCAN, client.getScanPolicyDefault()); - } - } - - void setReactiveDefaultPolicies(IAerospikeReactorClient reactorClient) { - this.defaultPolicies.put(PolicyType.READ, reactorClient.getReadPolicyDefault()); - this.defaultPolicies.put(PolicyType.WRITE, reactorClient.getWritePolicyDefault()); - this.defaultPolicies.put(PolicyType.BATCH, reactorClient.getBatchPolicyDefault()); - this.defaultPolicies.put(PolicyType.QUERY, reactorClient.getQueryPolicyDefault()); - this.defaultPolicies.put(PolicyType.SCAN, reactorClient.getScanPolicyDefault()); - } - - void setDefaultPolicy(PolicyType policyType, Policy policy) { - this.defaultPolicies.put(policyType, policy); - } - - void setChildrenPolicy(PolicyType policyType, Class parentClass, Policy policy) { - this.childrenPolicies.get(policyType).put(parentClass, policy); - } - - void setSpecificPolicy(PolicyType policyType, Class parentClass, Policy policy) { - this.childrenPolicies.get(policyType).put(parentClass, policy); - } - - public boolean hasClass(Class clazz) { - return cacheMap.containsKey(clazz); - } - - private Policy determinePolicy(@NotNull Class clazz, @NotNull PolicyType policyType) { - // Specific classes have the highest precedence - Policy result = specificPolicies.get(policyType).get(clazz); - if (result != null) { - return result; - } - // Otherwise, iterate up class hierarchy looking for the policy. - Class thisClass = clazz; - while (thisClass != null) { - Policy aPolicy = childrenPolicies.get(policyType).get(thisClass); - if (aPolicy != null) { - return aPolicy; - } - thisClass = thisClass.getSuperclass(); - } - // To get here, must have nothing specified, use the default - return this.defaultPolicies.get(policyType); - } - - /** - * This method is typically only used for testing - */ - public void clear() { - this.cacheMap.clear(); - this.classesConfig.clear(); - TypeUtils.clear(); - this.storedNameToCacheEntry.clear(); - } - - public void addConfiguration(@NotNull Configuration configuration) { - for (ClassConfig thisConfig : configuration.getClasses()) { - classesConfig.put(thisConfig.getClassName(), thisConfig); - } - } - - public ClassConfig getClassConfig(String className) { - return classesConfig.get(className); - } - - public ClassConfig getClassConfig(Class clazz) { - return classesConfig.get(clazz.getName()); - } - - public boolean hasClassConfig(String className) { - return classesConfig.containsKey(className); - } - - public boolean hasClassConfig(Class clazz) { - return classesConfig.containsKey(clazz.getName()); - } - - public enum PolicyType { - READ, - WRITE, - BATCH, - SCAN, - QUERY - } -} diff --git a/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java b/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java deleted file mode 100644 index c2860bc..0000000 --- a/src/main/java/com/aerospike/mapper/tools/ThreadLocalKeySaver.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.aerospike.mapper.tools; - -import com.aerospike.client.Key; - -import java.util.ArrayDeque; -import java.util.Deque; - -/** - * 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, - * not A's. - */ -public class ThreadLocalKeySaver { - - private static final ThreadLocal> threadLocalKeys = ThreadLocal.withInitial(ArrayDeque::new); - - private ThreadLocalKeySaver() { - } - - public static void save(Key key) { - threadLocalKeys.get().addLast(key); - LoadedObjectResolver.begin(); - } - - public static void clear() { - LoadedObjectResolver.end(); - threadLocalKeys.get().removeLast(); - if (threadLocalKeys.get().isEmpty()) { - threadLocalKeys.remove(); - } - } - - public static Key get() { - Deque keys = threadLocalKeys.get(); - if (keys.isEmpty()) { - return null; - } - return keys.getLast(); - } -} diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java b/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java deleted file mode 100644 index b986100..0000000 --- a/src/main/java/com/aerospike/mapper/tools/configuration/EmbedConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.aerospike.mapper.tools.configuration; - -import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; - -public class EmbedConfig { - private EmbedType type; - private EmbedType elementType; - private Boolean saveKey; - - public EmbedType getType() { - return type; - } - - public EmbedType getElementType() { - return elementType; - } - - public Boolean getSaveKey() { - return saveKey; - } - - public void setType(EmbedType type) { - this.type = type; - } - - public void setElementType(EmbedType elementType) { - this.elementType = elementType; - } - - public void setSaveKey(Boolean saveKey) { - this.saveKey = saveKey; - } -} diff --git a/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java b/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java deleted file mode 100644 index 7dbe3ff..0000000 --- a/src/main/java/com/aerospike/mapper/tools/configuration/KeyConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.aerospike.mapper.tools.configuration; - -import org.apache.commons.lang3.StringUtils; - -public class KeyConfig { - private String field; - private String getter; - private String setter; - private Boolean storeAsBin; - - public String getField() { - return field; - } - - public String getGetter() { - return getter; - } - - public String getSetter() { - return setter; - } - - public Boolean getStoreAsBin() { - return storeAsBin; - } - - public void setStoreAsBin(boolean value) { - this.storeAsBin = value; - } - - public void setField(String field) { - this.field = field; - } - - public void setGetter(String getter) { - this.getter = getter; - } - - public void setSetter(String setter) { - this.setter = setter; - } - - public boolean isGetter(String methodName) { - return (!StringUtils.isBlank(this.getter)) && this.getter.equals(methodName); - } - - public boolean isSetter(String methodName) { - return (!StringUtils.isBlank(this.setter)) && this.setter.equals(methodName); - } -} diff --git a/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java b/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java deleted file mode 100644 index 544bc3b..0000000 --- a/src/test/java/com/aerospike/mapper/GenerationNotStoredTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.aerospike.mapper; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -import com.aerospike.client.Bin; -import com.aerospike.mapper.annotations.AerospikeKey; -import com.aerospike.mapper.annotations.AerospikeRecord; -import com.aerospike.mapper.annotations.AerospikeGeneration; -import com.aerospike.mapper.tools.AeroMapper; -import com.aerospike.mapper.tools.ClassCacheEntry; -import com.aerospike.mapper.tools.utils.MapperUtils; - -public class GenerationNotStoredTest extends AeroMapperBaseTest { - - @AerospikeRecord(namespace = NAMESPACE, set = "generationNotStoredTest") - public static class EntityWithGeneration { - @AerospikeKey - private int id; - private String name; - @AerospikeGeneration - private Integer generation = 0; - - public EntityWithGeneration() {} - - public EntityWithGeneration(int id, String name) { - this.id = id; - this.name = name; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getGeneration() { - return generation; - } - - public void setGeneration(Integer generation) { - this.generation = generation; - } - } - - @Test - public void testGenerationFieldNotStoredAsBin() { - AeroMapper mapper = new AeroMapper.Builder(client).build(); - EntityWithGeneration entity = new EntityWithGeneration(1, "test"); - entity.setGeneration(5); // Set some generation value - - // Get the ClassCacheEntry and the bins that would be stored - ClassCacheEntry entry = MapperUtils.getEntryAndValidateNamespace(EntityWithGeneration.class, mapper); - Bin[] bins = entry.getBins(entity, false, null); - - // Verify that the generation field should NOT be included - // Should only have bins for regular fields (name and potentially id if stored as bin) - - boolean foundName = false; - boolean foundId = false; - boolean foundGeneration = false; - - for (Bin bin : bins) { - String binName = bin.name; - if ("name".equals(binName)) { - foundName = true; - assertEquals("test", bin.value.getObject()); - } else if ("id".equals(binName)) { - foundId = true; - assertEquals(1, bin.value.getObject()); - } else if ("generation".equals(binName)) { - foundGeneration = true; - } - } - - assertTrue(foundName, "Should find 'name' bin"); - assertFalse(foundGeneration, "Should NOT find 'generation' bin - it should not be stored"); - - // Log what bins we actually found for debugging - System.out.println("Found " + bins.length + " bins:"); - for (Bin bin : bins) { - System.out.println(" - " + bin.name + " = " + bin.value.getObject()); - } - } -} \ No newline at end of file diff --git a/src/test/java/com/aerospike/mapper/examples/model/Customer.java b/src/test/java/com/aerospike/mapper/examples/model/Customer.java deleted file mode 100644 index 5133fc7..0000000 --- a/src/test/java/com/aerospike/mapper/examples/model/Customer.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.aerospike.mapper.examples.model; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import com.aerospike.mapper.annotations.AerospikeBin; -import com.aerospike.mapper.annotations.AerospikeEmbed; -import com.aerospike.mapper.annotations.AerospikeKey; -import com.aerospike.mapper.annotations.AerospikeRecord; -import com.aerospike.mapper.annotations.ParamFrom; -import com.aerospike.mapper.examples.model.accounts.Account; - -@AerospikeRecord(namespace = "test", set = "customer") -public class Customer { - @AerospikeKey - @AerospikeBin(name = "id") - private final String customerId; - - private String firstName; - private String lastName; - - @AerospikeEmbed - @AerospikeBin(name = "mail") - private Address mailingAddress; - - private List accounts; - - @AerospikeBin(name = "dob") - private Date dateOfBirth; - private String phone; - private Date joinedBank; - private boolean vip; - @AerospikeBin(name = "greet") - private String preferredSalutation; - - public Customer(@ParamFrom("id") String customerId, @ParamFrom("firstName") String firstName, @ParamFrom("lastName") String lastName) { - super(); - this.customerId = customerId; - this.firstName = firstName; - this.lastName = lastName; - this.accounts = new ArrayList<>(); - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public Address getMailingAddress() { - return mailingAddress; - } - - public void setMailingAddress(Address mailingAddress) { - this.mailingAddress = mailingAddress; - } - - public List getAccounts() { - return accounts; - } - - public void setAccounts(List accounts) { - this.accounts = accounts; - } - - public String getCustomerId() { - return customerId; - } - - public Date getDateOfBirth() { - return dateOfBirth; - } - - public void setDateOfBirth(Date dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public Date getJoinedBank() { - return joinedBank; - } - - public void setJoinedBank(Date joinedBank) { - this.joinedBank = joinedBank; - } - - public boolean isVip() { - return vip; - } - - public void setVip(boolean vip) { - this.vip = vip; - } - - public String getPreferredSalutation() { - return preferredSalutation; - } - - public void setPreferredSalutation(String preferredSalutation) { - this.preferredSalutation = preferredSalutation; - } -} diff --git a/src/test/java/com/aerospike/mapper/examples/model/Transaction.java b/src/test/java/com/aerospike/mapper/examples/model/Transaction.java deleted file mode 100644 index 0ddfec3..0000000 --- a/src/test/java/com/aerospike/mapper/examples/model/Transaction.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.aerospike.mapper.examples.model; - -import java.time.Instant; - -import com.aerospike.mapper.annotations.AerospikeRecord; - -@AerospikeRecord -public class Transaction { - private long amount; - private String description; - private Instant time; - private boolean isFraud; - - public long getAmount() { - return amount; - } - - public void setAmount(long amount) { - this.amount = amount; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Instant getTime() { - return time; - } - - public void setTime(Instant time) { - this.time = time; - } - - public boolean isFraud() { - return isFraud; - } - - public void setFraud(boolean isFraud) { - this.isFraud = isFraud; - } -} diff --git a/src/test/java/com/aerospike/mapper/examples/model/Valuation.java b/src/test/java/com/aerospike/mapper/examples/model/Valuation.java deleted file mode 100644 index 7549400..0000000 --- a/src/test/java/com/aerospike/mapper/examples/model/Valuation.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.aerospike.mapper.examples.model; - -import java.util.Date; - -import com.aerospike.mapper.annotations.AerospikeEmbed; -import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType; -import com.aerospike.mapper.annotations.AerospikeRecord; - -@AerospikeRecord -public class Valuation { - private Date date; - private long amount; - private long amountMargin; - private String valuer; - private long valuerCompanyId; - @AerospikeEmbed(type = EmbedType.LIST) - private Address valuerAddress; - - public Valuation() { - } - - public Valuation(Date date, long amount, long amountMargin, String valuer, long valuerCompanyId, Address valuerAddress) { - super(); - this.date = date; - this.amount = amount; - this.amountMargin = amountMargin; - this.valuer = valuer; - this.valuerCompanyId = valuerCompanyId; - this.valuerAddress = valuerAddress; - } - - public Date getDate() { - return date; - } - - public void setDate(Date date) { - this.date = date; - } - - public long getAmount() { - return amount; - } - - public void setAmount(long amount) { - this.amount = amount; - } - - public long getAmountMargin() { - return amountMargin; - } - - public void setAmountMargin(long amountMargin) { - this.amountMargin = amountMargin; - } - - public String getValuer() { - return valuer; - } - - public void setValuer(String valuer) { - this.valuer = valuer; - } - - public long getValuerCompanyId() { - return valuerCompanyId; - } - - public void setValuerCompanyId(long valuerCompanyId) { - this.valuerCompanyId = valuerCompanyId; - } -} diff --git a/src/test/java/com/aerospike/mapper/model/Person.java b/src/test/java/com/aerospike/mapper/model/Person.java deleted file mode 100644 index be28eab..0000000 --- a/src/test/java/com/aerospike/mapper/model/Person.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.aerospike.mapper.model; - -import java.util.ArrayList; -import java.util.Base64; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import com.aerospike.mapper.annotations.AerospikeBin; -import com.aerospike.mapper.annotations.AerospikeEmbed; -import com.aerospike.mapper.annotations.AerospikeKey; -import com.aerospike.mapper.annotations.AerospikeRecord; -import com.aerospike.mapper.annotations.AerospikeReference; - -// The set name will read a system definition of people.set.name for the set name. If not set, it will use "people". For example: -// -Dpeople.set.name=persons -@AerospikeRecord(namespace = "test", set = "${people.set.name:people}") -public class Person { - - @AerospikeKey - private String ssn; - - private String firstName; - private String lastName; - private int age; - private Date dateOfBirth; - private boolean isValid; - private float balance; - private double height; - private byte[] photo; - private long[] longData; - - @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.MAP) - private Account[] accountArray; - - private List stringList; - private String[] stringArray; - - @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.LIST) - private List accounts; - - @AerospikeEmbed(elementType = AerospikeEmbed.EmbedType.LIST) - private Map productMap; - - private Map testMap; - - @AerospikeEmbed(type = AerospikeEmbed.EmbedType.LIST) - private Account primaryAccount; - - @AerospikeBin(name = "2ndAcc") - @AerospikeEmbed(type = AerospikeEmbed.EmbedType.MAP) - private Account secondaryAccount; - - @AerospikeBin(name = "3rdAcc") - @AerospikeReference() - private Account tertiaryAccount; - - public Person() { - accounts = new ArrayList<>(); - } - - public String getSsn() { - return ssn; - } - - public void setSsn(String ssn) { - this.ssn = ssn; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public Date getDateOfBirth() { - return dateOfBirth; - } - - public void setDateOfBirth(Date dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } - - public boolean isValid() { - return isValid; - } - - public void setValid(boolean isValid) { - this.isValid = isValid; - } - - public float getBalance() { - return balance; - } - - public void setBalance(float balance) { - this.balance = balance; - } - - - public double getHeight() { - return height; - } - - public void setHeight(double height) { - this.height = height; - } - - public byte[] getPhoto() { - return photo; - } - - public void setPhoto(byte[] photo) { - this.photo = photo; - } - - public List getStringList() { - return stringList; - } - - public void setStringList(List stringList) { - this.stringList = stringList; - } - - public Map getProductMap() { - return productMap; - } - - public void setProductMap(Map productMap) { - this.productMap = productMap; - } - - public Map getTestMap() { - return testMap; - } - - public void setTestMap(Map testMap) { - this.testMap = testMap; - } - - public String[] getStringArray() { - return stringArray; - } - - public void setStringArray(String[] stringArray) { - this.stringArray = stringArray; - } - - public List getAccounts() { - return accounts; - } - - public void setAccounts(List accounts) { - this.accounts = accounts; - } - - public Account getPrimaryAccount() { - return primaryAccount; - } - - public void setPrimaryAccount(Account primaryAccount) { - this.primaryAccount = primaryAccount; - } - - public Account getSecondaryAccount() { - return secondaryAccount; - } - - public void setSecondaryAccount(Account secondaryAccount) { - this.secondaryAccount = secondaryAccount; - } - - public Account getTertiaryAccount() { - return tertiaryAccount; - } - - public void setTertiaryAccount(Account tertiaryAccount) { - this.tertiaryAccount = tertiaryAccount; - } - - public long[] getLongData() { - return longData; - } - - public void setLongData(long[] longData) { - this.longData = longData; - } - - public Account[] getAccountArray() { - return accountArray; - } - - public void setAccountArray(Account[] accountArray) { - this.accountArray = accountArray; - } - - @Override - public String toString() { - byte[] bytes = getPhoto(); - String byteStr = bytes == null ? "null" : Base64.getEncoder().encodeToString(bytes); - return String.format("{ssn=%s, firstName=%s, lastName=%s, age=%d, dob=%s, valid=%b, balance=%f, height=%f, photo=%s}", - this.getSsn(), this.getFirstName(), this.getLastName(), this.getAge(), - this.dateOfBirth == null ? null : this.getDateOfBirth().toString(), this.isValid(), - this.getBalance(), getHeight(), byteStr); - } -} diff --git a/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java b/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java deleted file mode 100644 index 3c6c602..0000000 --- a/src/test/java/com/aerospike/mapper/model/PersonDifferentNames.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.aerospike.mapper.model; - -import java.util.Base64; -import java.util.Date; -import java.util.List; - -import com.aerospike.mapper.annotations.AerospikeBin; -import com.aerospike.mapper.annotations.AerospikeKey; -import com.aerospike.mapper.annotations.AerospikeRecord; - -@AerospikeRecord(namespace = "test", set = "people") -public class PersonDifferentNames { - - @AerospikeKey - @AerospikeBin(name = "s") - private String ssn; - - @AerospikeBin(name = "f") - private String firstName; - - @AerospikeBin(name = "l") - private String lastName; - - @AerospikeBin(name = "a") - private int age; - - private Date dateOfBirth; - private boolean isValid; - private float balance; - private double height; - private byte[] photo; - private List list; - - public PersonDifferentNames() { - } - - public String getSsn() { - return ssn; - } - - public void setSsn(String ssn) { - this.ssn = ssn; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public Date getDateOfBirth() { - return dateOfBirth; - } - - public void setDateOfBirth(Date dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } - - public boolean isValid() { - return isValid; - } - - public void setValid(boolean isValid) { - this.isValid = isValid; - } - - public float getBalance() { - return balance; - } - - public void setBalance(float balance) { - this.balance = balance; - } - - public double getHeight() { - return height; - } - - public void setHeight(double height) { - this.height = height; - } - - public byte[] getPhoto() { - return photo; - } - - public void setPhoto(byte[] photo) { - this.photo = photo; - } - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - @Override - public String toString() { - byte[] bytes = getPhoto(); - String byteStr = bytes == null ? "null" : Base64.getEncoder().encodeToString(bytes); - return String.format("{ssn=%s, firstName=%s, lastName=%s, age=%d, dob=%s, valid=%b, balance=%f, height=%f, photo=%s}", - this.getSsn(), this.getFirstName(), this.getLastName(), this.getAge(), - this.dateOfBirth == null ? null : this.getDateOfBirth().toString(), this.isValid(), - this.getBalance(), getHeight(), byteStr); - } -}