Skip to content

Commit 65b588e

Browse files
committed
Corrected issues with type-less object references not being able to load
correctly.
1 parent e28f7fe commit 65b588e

File tree

13 files changed

+224
-85
lines changed

13 files changed

+224
-85
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ The elements in the list are (in order): createdDate, name, productId, version.
791791
792792
This is far more compact and wastes less space, but has an issue: How do you add new items to the product? The answer is to use versioning.
793793
794-
#### Versioning Links
794+
#### Versioning Lists
795795
796796
Maps and Aerospike records are self-describing -- each value has a name, so it is obvious how to map the data to the database and back. For example, if we have a class
797797
@@ -1032,6 +1032,10 @@ Multiple ordinals can be specified for a single class, but these must be sequent
10321032

10331033
**Note**: Ordinal fields cannot be versioned.
10341034

1035+
#### The importance of Generic Types
1036+
1037+
When using the object mapper, it is important to
1038+
10351039
----
10361040

10371041
## Advanced Features
@@ -1609,10 +1613,6 @@ When mapping a Java object to Aerospike the most common operations to do are to
16091613
----
16101614

16111615
## To finish
1612-
- lists of embedded objects
1613-
- maps of embedded objects
1614-
- lists of referenced objects
1615-
- maps of referenced objects
16161616
- Document virtual lists
16171617
- Validate some of the limits, eg bin name length, set name length, etc.
16181618
- Make all maps (esp Embedded ones) K_ORDERED
@@ -1623,7 +1623,7 @@ When mapping a Java object to Aerospike the most common operations to do are to
16231623
- Document creation of builder -- multiple configuration files are allowed, if the same class is declared in both the first one encountered wins.
16241624
- Document methods with 2 parameters for keys and setters, the second one either a Key or a Value
16251625
- Document subclasses and the mapping to tables + references stored as lists
1626-
- Batch load of child items on Maps and References. Ensure testing of non-parameterized classes too. Also of methods on Virtual LIsts
1626+
- Batch load of child items on Maps and References. Ensure testing of non-parameterized classes too.
16271627
- Document batch loading
16281628
- Ensure batch loading option exists in AerospikeReference Configuration
16291629
- handle object graph circularities (A->B->C). Be careful of: A->B(Lazy), A->C->B: B should end up fully hydrated in both instances, not lazy in both instances

src/main/java/com/aerospike/mapper/tools/AeroMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ public <T> T convertToObject(Class<T> clazz, Record record, ClassCacheEntry<T> e
503503
if (entry == null) {
504504
entry = ClassCache.getInstance().loadClass(clazz, this);
505505
}
506-
T result = entry.constructAndHydrate(clazz, record);
506+
T result = entry.constructAndHydrate(record);
507507
if (resolveDependencies) {
508508
resolveDependencies(entry);
509509
}
@@ -608,7 +608,7 @@ void resolveDependencies(ClassCacheEntry<?> parentEntity) {
608608
DeferredObjectSetter thisObjectSetter = deferredObjects.get(i);
609609
try {
610610
ThreadLocalKeySaver.save(keys[i]);
611-
Object result = this.convertToObject((Class)thisObjectSetter.getObject().getType(), records[i], classCaches[i], false);
611+
Object result = records[i] == null ? null : this.convertToObject((Class)thisObjectSetter.getObject().getType(), records[i], classCaches[i], false);
612612
thisObjectSetter.getSetter().setValue(result);
613613
} catch (ReflectiveOperationException e) {
614614
throw new AerospikeException(e);

src/main/java/com/aerospike/mapper/tools/ClassCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private ClassCache() {
4545
}
4646

4747
public <T> ClassCacheEntry<T> loadClass(@NotNull Class<T> clazz, AeroMapper mapper) {
48-
if (clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class) || Number.class.isAssignableFrom(clazz)) {
48+
if (clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class) || clazz.equals(Character.class) || Number.class.isAssignableFrom(clazz)) {
4949
return null;
5050
}
5151
ClassCacheEntry<T> entry = cacheMap.get(clazz);

src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public class ClassCacheEntry<T> {
101101

102102
AerospikeRecord recordDescription = clazz.getAnnotation(AerospikeRecord.class);
103103
if (recordDescription == null && config == null) {
104-
throw new IllegalArgumentException("Class " + clazz.getName() + " is not augmented by the @AerospikeRecord annotation");
104+
throw new AerospikeException("Class " + clazz.getName() + " is not augmented by the @AerospikeRecord annotation");
105105
}
106106
else if (recordDescription != null) {
107107
this.namespace = ParserUtils.getInstance().get(recordDescription.namespace());
@@ -723,14 +723,14 @@ public List<Object> getList(Object instance, boolean skipKey, boolean needsType)
723723
}
724724
}
725725

726-
public T constructAndHydrate(Class<T> clazz, Map<String, Object> map) {
727-
return constructAndHydrate(clazz, null, map);
726+
public T constructAndHydrate(Map<String, Object> map) {
727+
return constructAndHydrate(null, map);
728728
}
729-
public T constructAndHydrate(Class<T> clazz, Record record) {
730-
return constructAndHydrate(clazz, record, null);
729+
public T constructAndHydrate(Record record) {
730+
return constructAndHydrate(record, null);
731731
}
732732

733-
private T constructAndHydrate(Class<T> clazz, Record record, Map<String, Object> map) {
733+
private T constructAndHydrate(Record record, Map<String, Object> map) {
734734
Map<String, Object> valueMap = new HashMap<>();
735735
try {
736736
ClassCacheEntry<?> thisClass = this;
@@ -754,7 +754,7 @@ private T constructAndHydrate(Class<T> clazz, Record record, Map<String, Object>
754754
valueMap.put(name, value.getTypeMapper().fromAerospikeFormat(aerospikeValue));
755755
}
756756
if (result == null) {
757-
result = (T)thisClass.constructAndHydrate(valueMap);
757+
result = (T)thisClass.constructAndHydrateFromJavaMap(valueMap);
758758
}
759759
else {
760760
for (String field : valueMap.keySet()) {
@@ -827,33 +827,32 @@ public void hydrateFromList(List<Object> list, Object instance) {
827827
this.hydrateFromList(list, instance, false);
828828
}
829829

830-
private T constructAndHydrate(Map<String, Object> javaValuesMap) throws ReflectiveOperationException {
830+
private T constructAndHydrateFromJavaMap(Map<String, Object> javaValuesMap) throws ReflectiveOperationException {
831831
// Now form the values which satisfy the constructor
832832
T result;
833-
if (constructorParamBins.length == 0) {
834-
result = clazz.newInstance();
835-
}
836-
else {
837-
Object[] args = new Object[constructorParamBins.length];
838-
for (int i = 0; i < constructorParamBins.length; i++) {
839-
if (javaValuesMap.containsKey(constructorParamBins[i])) {
840-
args[i] = javaValuesMap.get(constructorParamBins[i]);
841-
}
842-
else {
843-
args[i] = constructorParamDefaults[i];
844-
}
845-
javaValuesMap.remove(constructorParamBins[i]);
833+
Object[] args = new Object[constructorParamBins.length];
834+
for (int i = 0; i < constructorParamBins.length; i++) {
835+
if (javaValuesMap.containsKey(constructorParamBins[i])) {
836+
args[i] = javaValuesMap.get(constructorParamBins[i]);
837+
}
838+
else {
839+
args[i] = constructorParamDefaults[i];
846840
}
847-
result = constructor.newInstance(args);
841+
javaValuesMap.remove(constructorParamBins[i]);
848842
}
843+
result = constructor.newInstance(args);
849844
for (String field : javaValuesMap.keySet()) {
850845
ValueType value = this.values.get(field);
851-
value.set(result, javaValuesMap.get(field));
846+
Object object = javaValuesMap.get(field);
847+
if (object == null && value.getType().isPrimitive()) {
848+
object = PrimitiveDefaults.getDefaultValue(value.getType());
849+
}
850+
value.set(result, object);
852851
}
853852
return result;
854853
}
855854

856-
public T constructAndHydrate(Class<T> clazz, List<Object> list, boolean skipKey) {
855+
public T constructAndHydrate(List<Object> list, boolean skipKey) {
857856
Map<String, Object> valueMap = new HashMap<>();
858857
try {
859858
ClassCacheEntry<?> thisClass = this;
@@ -900,7 +899,7 @@ public T constructAndHydrate(Class<T> clazz, List<Object> list, boolean skipKey)
900899
}
901900
}
902901
if (result == null) {
903-
result = (T)thisClass.constructAndHydrate(valueMap);
902+
result = (T)thisClass.constructAndHydrateFromJavaMap(valueMap);
904903
}
905904
else {
906905
for (String field : valueMap.keySet()) {
@@ -964,7 +963,7 @@ ValueType getValueFromBinName(String name) {
964963

965964
@Override
966965
public String toString() {
967-
return String.format("ClassCacheEntry<%s> (ns=%s, set=%s,subclass=%b,shortName=%s)", this.getUnderlyingClass().getSimpleName(), this.namespace, this.setName, this.isChildClass, this.shortenedClassName);
966+
return String.format("ClassCacheEntry<%s> (ns=%s,set=%s,subclass=%b,shortName=%s)", this.getUnderlyingClass().getSimpleName(), this.namespace, this.setName, this.isChildClass, this.shortenedClassName);
968967
}
969968

970969
}

src/main/java/com/aerospike/mapper/tools/TypeMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public abstract class TypeMapper {
1010
* @param isExpectedType
1111
* @return
1212
*/
13-
public Object toAerospikeFormat(Object value, boolean isExpectedType) {
13+
public Object toAerospikeFormat(Object value, boolean isUnknownType, boolean isSubclassOfKnownType) {
1414
return toAerospikeFormat(value);
1515
}
1616
}

src/main/java/com/aerospike/mapper/tools/TypeUtils.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -267,29 +267,31 @@ else if (binConfig.getEmbed() != null) {
267267
}
268268
}
269269
else {
270-
for (Annotation annotation : type.getAnnotations()) {
271-
if (annotation.annotationType().equals(AerospikeReference.class)) {
272-
if (typeMapper != null) {
273-
throwError = true;
274-
break;
270+
if (type.getAnnotations() != null) {
271+
for (Annotation annotation : type.getAnnotations()) {
272+
if (annotation.annotationType().equals(AerospikeReference.class)) {
273+
if (typeMapper != null) {
274+
throwError = true;
275+
break;
276+
}
277+
else {
278+
AerospikeReference ref = (AerospikeReference)annotation;
279+
typeMapper = new ObjectReferenceMapper(ClassCache.getInstance().loadClass(clazz, mapper), ref.lazy(), ref.batchLoad(), ref.type(), mapper);
280+
addToMap = false;
281+
}
275282
}
276-
else {
277-
AerospikeReference ref = (AerospikeReference)annotation;
278-
typeMapper = new ObjectReferenceMapper(ClassCache.getInstance().loadClass(clazz, mapper), ref.lazy(), ref.batchLoad(), ref.type(), mapper);
279-
addToMap = false;
280-
}
281-
}
282-
if (annotation.annotationType().equals(AerospikeEmbed.class)) {
283-
AerospikeEmbed embed = (AerospikeEmbed)annotation;
284-
if (typeMapper != null) {
285-
throwError = true;
286-
break;
287-
}
288-
else {
289-
EmbedType embedType = isForSubType ? embed.elementType() : embed.type();
290-
boolean skipKey = isForSubType && (embed.type() == EmbedType.MAP && embed.elementType() == EmbedType.LIST && (!embed.saveKey()));
291-
typeMapper = new ObjectEmbedMapper(clazz, embedType, mapper, skipKey);
292-
addToMap = false;
283+
if (annotation.annotationType().equals(AerospikeEmbed.class)) {
284+
AerospikeEmbed embed = (AerospikeEmbed)annotation;
285+
if (typeMapper != null) {
286+
throwError = true;
287+
break;
288+
}
289+
else {
290+
EmbedType embedType = isForSubType ? embed.elementType() : embed.type();
291+
boolean skipKey = isForSubType && (embed.type() == EmbedType.MAP && embed.elementType() == EmbedType.LIST && (!embed.saveKey()));
292+
typeMapper = new ObjectEmbedMapper(clazz, embedType, mapper, skipKey);
293+
addToMap = false;
294+
}
293295
}
294296
}
295297
}

src/main/java/com/aerospike/mapper/tools/mappers/ListMapper.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public Object toAerospikeInstanceFormat(Object obj) {
7373
}
7474
else {
7575
TypeMapper thisMapper = TypeUtils.getMapper(obj.getClass(), null, mapper);
76-
return thisMapper == null ? obj : thisMapper.toAerospikeFormat(obj, false);
76+
return thisMapper == null ? obj : thisMapper.toAerospikeFormat(obj, true, false);
7777
}
7878
}
7979
else {
@@ -82,7 +82,7 @@ public Object toAerospikeInstanceFormat(Object obj) {
8282
}
8383
else {
8484
// This class must be a subclass of the annotated type
85-
return this.instanceClassMapper.toAerospikeFormat(obj, false);
85+
return this.instanceClassMapper.toAerospikeFormat(obj, false, true);
8686
}
8787
}
8888
}
@@ -94,7 +94,7 @@ public Object toAerospikeInstanceFormat(Object obj) {
9494
}
9595
else {
9696
// This class must be a subclass of the annotated type
97-
item = this.instanceClassMapper.toAerospikeFormat(obj, false);
97+
item = this.instanceClassMapper.toAerospikeFormat(obj, false, true);
9898
}
9999
return new AbstractMap.SimpleEntry<Object, Object>(key, item);
100100
}
@@ -124,7 +124,7 @@ public Object toAerospikeFormat(Object value) {
124124
}
125125
else {
126126
// This class must be a subclass of the annotated type
127-
item = this.instanceClassMapper.toAerospikeFormat(obj, false);
127+
item = this.instanceClassMapper.toAerospikeFormat(obj, false, true);
128128
}
129129
results.put(key, item);
130130
}
@@ -133,6 +133,24 @@ public Object toAerospikeFormat(Object value) {
133133
}
134134
}
135135

136+
private Class<?> getClassToUse(Object obj) {
137+
if (List.class.isAssignableFrom(obj.getClass())) {
138+
List<Object> list = (List<Object>) obj;
139+
int lastElementIndex = list.size()-1;
140+
if ((!list.isEmpty()) && (list.get(lastElementIndex) instanceof String)) {
141+
String lastElement = (String)list.get(lastElementIndex);
142+
if (lastElement.startsWith(ClassCacheEntry.TYPE_PREFIX)) {
143+
String className = lastElement.substring(ClassCacheEntry.TYPE_PREFIX.length());
144+
ClassCacheEntry<?> thisClass = ClassCache.getInstance().getCacheEntryFromStoredName(className);
145+
if (thisClass != null) {
146+
return thisClass.getUnderlyingClass();
147+
}
148+
}
149+
}
150+
}
151+
return obj.getClass();
152+
}
153+
136154
public Object fromAerospikeInstanceFormat(Object obj) {
137155
if (embedType == null || embedType == EmbedType.LIST) {
138156
if (instanceClass == null) {
@@ -141,7 +159,7 @@ public Object fromAerospikeInstanceFormat(Object obj) {
141159
return null;
142160
}
143161
else {
144-
TypeMapper thisMapper = TypeUtils.getMapper(obj.getClass(), AnnotatedType.getDefaultAnnotateType(), mapper);
162+
TypeMapper thisMapper = TypeUtils.getMapper(getClassToUse(obj), AnnotatedType.getDefaultAnnotateType(), mapper);
145163
return thisMapper == null ? obj : thisMapper.fromAerospikeFormat(obj);
146164
}
147165
}
@@ -172,14 +190,32 @@ public Object fromAerospikeFormat(Object value) {
172190

173191
if (instanceClass == null) {
174192
// We don't have any hints as to how to translate them, we have to look up each type
193+
int index = 0;
175194
for (Object obj : list) {
176195
if (obj == null) {
177196
results.add(null);
178197
}
179198
else {
180-
TypeMapper thisMapper = TypeUtils.getMapper(obj.getClass(), AnnotatedType.getDefaultAnnotateType(), mapper);
181-
results.add(thisMapper == null ? obj : thisMapper.fromAerospikeFormat(obj));
199+
TypeMapper thisMapper = TypeUtils.getMapper(getClassToUse(obj), AnnotatedType.getDefaultAnnotateType(), mapper);
200+
Object result = thisMapper == null ? obj : thisMapper.fromAerospikeFormat(obj);
201+
if (result instanceof DeferredObject) {
202+
final int thisIndex = index;
203+
DeferredSetter setter = new DeferredSetter() {
204+
@Override
205+
public void setValue(Object object) {
206+
results.set(thisIndex, object);
207+
}
208+
};
209+
DeferredObjectSetter objectSetter = new DeferredObjectSetter(setter, (DeferredObject)result);
210+
DeferredObjectLoader.add(objectSetter);
211+
// add a placeholder to maintain the index
212+
results.add(null);
213+
}
214+
else {
215+
results.add(result);
216+
}
182217
}
218+
index++;
183219
}
184220
}
185221
else {

src/main/java/com/aerospike/mapper/tools/mappers/ObjectEmbedMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ public Object fromAerospikeFormat(Object value) {
5555
switch (type) {
5656
case LIST:
5757
List<Object> listValue = (List<Object>) value;
58-
instance = entry.constructAndHydrate((Class)this.referencedClass, listValue, skipKey);
58+
instance = entry.constructAndHydrate(listValue, skipKey);
5959
break;
6060
case MAP: // Fall through
6161
case DEFAULT:
62-
instance = entry.constructAndHydrate((Class)this.referencedClass, (Map<String,Object>)value);
62+
instance = entry.constructAndHydrate((Map<String,Object>)value);
6363
break;
6464
default:
6565
throw new AerospikeException("Unspecified EmbedType");

0 commit comments

Comments
 (0)