Skip to content

Commit 652b1b8

Browse files
DATAREDIS-489 - Add type hints for Object types.
We now store the type hint for simple types when the declaring bean property does not match the actual value type.
1 parent 14e4488 commit 652b1b8

File tree

3 files changed

+231
-90
lines changed

3 files changed

+231
-90
lines changed

src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java

Lines changed: 70 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.data.mapping.PersistentPropertyAccessor;
4949
import org.springframework.data.mapping.PreferredConstructor;
5050
import org.springframework.data.mapping.PropertyHandler;
51+
import org.springframework.data.mapping.model.MappingException;
5152
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
5253
import org.springframework.data.mapping.model.PropertyValueProvider;
5354
import org.springframework.data.redis.core.index.Indexed;
@@ -107,6 +108,8 @@
107108
*/
108109
public class MappingRedisConverter implements RedisConverter, InitializingBean {
109110

111+
private static final String TYPE_HINT_ALIAS = "_class";
112+
110113
private final RedisMappingContext mappingContext;
111114
private final GenericConversionService conversionService;
112115
private final EntityInstantiators entityInstantiators;
@@ -231,41 +234,9 @@ public void doWithPersistentProperty(KeyValuePersistentProperty persistentProper
231234

232235
else if (persistentProperty.isCollectionLike()) {
233236

234-
if (conversionService.canConvert(byte[].class, persistentProperty.getComponentType())) {
235-
236-
Object targetValue = null;
237-
if (persistentProperty.getType().isArray()) {
238-
239-
List<Object> list = (List<Object>) readCollectionOfSimpleTypes(currentPath, ArrayList.class,
240-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source);
241-
242-
targetValue = list.toArray((Object[]) Array.newInstance(
243-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), list.size()));
244-
} else {
245-
targetValue = readCollectionOfSimpleTypes(currentPath, persistentProperty.getType(),
246-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source);
247-
}
248-
249-
accessor.setProperty(persistentProperty, targetValue);
250-
} else {
251-
252-
Object targetValue = null;
253-
if (persistentProperty.getType().isArray()) {
254-
255-
List<Object> list = (List<Object>) readCollectionOfComplexTypes(currentPath, ArrayList.class,
256-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(),
257-
source.getBucket());
258-
259-
targetValue = list.toArray((Object[]) Array.newInstance(
260-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), list.size()));
261-
} else {
262-
263-
targetValue = readCollectionOfComplexTypes(currentPath, persistentProperty.getType(),
264-
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(),
265-
source.getBucket());
266-
}
267-
accessor.setProperty(persistentProperty, targetValue);
268-
}
237+
Object targetValue = readCollectionOrArray(currentPath, persistentProperty.getType(),
238+
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source.getBucket());
239+
accessor.setProperty(persistentProperty, targetValue);
269240

270241
} else if (persistentProperty.isEntity() && !conversionService.canConvert(byte[].class,
271242
persistentProperty.getTypeInformation().getActualType().getType())) {
@@ -276,9 +247,9 @@ else if (persistentProperty.isCollectionLike()) {
276247

277248
RedisData source = new RedisData(bucket);
278249

279-
byte[] type = bucket.get(currentPath + "._class");
250+
byte[] type = bucket.get(currentPath + "." + TYPE_HINT_ALIAS);
280251
if (type != null && type.length > 0) {
281-
source.getBucket().put("_class", type);
252+
source.getBucket().put(TYPE_HINT_ALIAS, type);
282253
}
283254

284255
accessor.setProperty(persistentProperty, readInternal(currentPath, targetType, source));
@@ -294,8 +265,8 @@ else if (persistentProperty.isCollectionLike()) {
294265
}
295266
}
296267

297-
accessor.setProperty(persistentProperty,
298-
fromBytes(source.getBucket().get(currentPath), persistentProperty.getActualType()));
268+
Class<?> typeToUse = getTypeHint(currentPath, source.getBucket(), persistentProperty.getActualType());
269+
accessor.setProperty(persistentProperty, fromBytes(source.getBucket().get(currentPath), typeToUse));
299270
}
300271
}
301272

@@ -405,16 +376,17 @@ private void writeInternal(final String keyspace, final String path, final Objec
405376

406377
if (customConversions.hasCustomWriteTarget(value.getClass())) {
407378

408-
if (customConversions.getCustomWriteTarget(value.getClass()).equals(byte[].class)) {
379+
if (!StringUtils.hasText(path) && customConversions.getCustomWriteTarget(value.getClass()).equals(byte[].class)) {
409380
sink.getBucket().put(StringUtils.hasText(path) ? path : "_raw", conversionService.convert(value, byte[].class));
410381
} else {
411-
writeToBucket(path, value, sink);
382+
writeToBucket(path, value, sink, typeHint.getType());
412383
}
413384
return;
414385
}
415386

416387
if (value.getClass() != typeHint.getType()) {
417-
sink.getBucket().put((!path.isEmpty() ? path + "._class" : "_class"), toBytes(value.getClass().getName()));
388+
sink.getBucket().put((!path.isEmpty() ? path + "." + TYPE_HINT_ALIAS : TYPE_HINT_ALIAS),
389+
toBytes(value.getClass().getName()));
418390
}
419391

420392
final KeyValuePersistentEntity<?> entity = mappingContext.getPersistentEntity(value.getClass());
@@ -459,7 +431,7 @@ public void doWithPersistentProperty(KeyValuePersistentProperty persistentProper
459431
} else {
460432

461433
Object propertyValue = accessor.getProperty(persistentProperty);
462-
sink.getBucket().put(propertyStringPath, toBytes(propertyValue));
434+
writeToBucket(propertyStringPath, propertyValue, sink, persistentProperty.getType());
463435
}
464436
}
465437
});
@@ -541,15 +513,15 @@ private void writeCollection(String keyspace, String path, Iterable<?> values, T
541513
String currentPath = path + ".[" + i + "]";
542514

543515
if (customConversions.hasCustomWriteTarget(value.getClass())) {
544-
writeToBucket(currentPath, value, sink);
516+
writeToBucket(currentPath, value, sink, typeHint.getType());
545517
} else {
546518
writeInternal(keyspace, currentPath, value, typeHint, sink);
547519
}
548520
i++;
549521
}
550522
}
551523

552-
private void writeToBucket(String path, Object value, RedisData sink) {
524+
private void writeToBucket(String path, Object value, RedisData sink, Class<?> propertyType) {
553525

554526
if (value == null) {
555527
return;
@@ -559,6 +531,12 @@ private void writeToBucket(String path, Object value, RedisData sink) {
559531

560532
Class<?> targetType = customConversions.getCustomWriteTarget(value.getClass());
561533

534+
if (!ClassUtils.isAssignable(Map.class, targetType) && customConversions.isSimpleType(value.getClass())
535+
&& value.getClass() != propertyType) {
536+
sink.getBucket().put((!path.isEmpty() ? path + "." + TYPE_HINT_ALIAS : TYPE_HINT_ALIAS),
537+
toBytes(value.getClass().getName()));
538+
}
539+
562540
if (ClassUtils.isAssignable(Map.class, targetType)) {
563541

564542
Map<?, ?> map = (Map<?, ?>) conversionService.convert(value, targetType);
@@ -576,59 +554,37 @@ private void writeToBucket(String path, Object value, RedisData sink) {
576554

577555
}
578556

579-
/**
580-
* @param path
581-
* @param collectionType
582-
* @param valueType
583-
* @param source
584-
* @return
585-
*/
586-
private Collection<?> readCollectionOfSimpleTypes(String path, Class<?> collectionType, Class<?> valueType,
587-
RedisData source) {
588-
589-
Bucket partial = source.getBucket().extract(path + ".[");
557+
private Object readCollectionOrArray(String path, Class<?> collectionType, Class<?> valueType, Bucket bucket) {
590558

591-
List<String> keys = new ArrayList<String>(partial.keySet());
559+
List<String> keys = new ArrayList<String>(bucket.extractAllKeysFor(path));
592560
Collections.sort(keys, listKeyComparator);
593561

594-
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, partial.size());
562+
boolean isArray = collectionType.isArray();
563+
Class<?> collectionTypeToUse = isArray ? ArrayList.class : collectionType;
564+
Collection<Object> target = CollectionFactory.createCollection(collectionTypeToUse, valueType, keys.size());
595565

596566
for (String key : keys) {
597-
target.add(fromBytes(partial.get(key), valueType));
598-
}
599567

600-
return target;
601-
}
602-
603-
/**
604-
* @param path
605-
* @param collectionType
606-
* @param valueType
607-
* @param source
608-
* @return
609-
*/
610-
private Collection<?> readCollectionOfComplexTypes(String path, Class<?> collectionType, Class<?> valueType,
611-
Bucket source) {
612-
613-
List<String> keys = new ArrayList<String>(source.extractAllKeysFor(path));
614-
Collections.sort(keys, listKeyComparator);
615-
616-
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, keys.size());
617-
618-
for (String key : keys) {
568+
if (key.endsWith(TYPE_HINT_ALIAS)) {
569+
continue;
570+
}
619571

620-
Bucket elementData = source.extract(key);
572+
Bucket elementData = bucket.extract(key);
621573

622-
byte[] typeInfo = elementData.get(key + "._class");
574+
byte[] typeInfo = elementData.get(key + "." + TYPE_HINT_ALIAS);
623575
if (typeInfo != null && typeInfo.length > 0) {
624-
elementData.put("_class", typeInfo);
576+
elementData.put(TYPE_HINT_ALIAS, typeInfo);
625577
}
626578

627-
Object o = readInternal(key, valueType, new RedisData(elementData));
628-
target.add(o);
579+
Class<?> typeToUse = getTypeHint(key, elementData, valueType);
580+
if (conversionService.canConvert(byte[].class, typeToUse)) {
581+
target.add(fromBytes(elementData.get(key), typeToUse));
582+
} else {
583+
target.add(readInternal(key, valueType, new RedisData(elementData)));
584+
}
629585
}
630586

631-
return target;
587+
return isArray ? target.toArray((Object[]) Array.newInstance(valueType, target.size())) : target;
632588
}
633589

634590
/**
@@ -653,7 +609,7 @@ private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?
653609
String currentPath = path + ".[" + entry.getKey() + "]";
654610

655611
if (customConversions.hasCustomWriteTarget(entry.getValue().getClass())) {
656-
writeToBucket(currentPath, entry.getValue(), sink);
612+
writeToBucket(currentPath, entry.getValue(), sink, mapValueType);
657613
} else {
658614
writeInternal(keyspace, currentPath, entry.getValue(), ClassTypeInformation.from(mapValueType), sink);
659615
}
@@ -677,6 +633,10 @@ private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?
677633

678634
for (Entry<String, byte[]> entry : partial.entrySet()) {
679635

636+
if (entry.getKey().endsWith(TYPE_HINT_ALIAS)) {
637+
continue;
638+
}
639+
680640
String regex = "^(" + Pattern.quote(path) + "\\.\\[)(.*?)(\\])";
681641
Pattern pattern = Pattern.compile(regex);
682642

@@ -686,7 +646,9 @@ private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?
686646
String.format("Cannot extract map value for key '%s' in path '%s'.", entry.getKey(), path));
687647
}
688648
String key = matcher.group(2);
689-
target.put(key, fromBytes(entry.getValue(), valueType));
649+
650+
Class<?> typeToUse = getTypeHint(path + ".[" + key + "]", source.getBucket(), valueType);
651+
target.put(key, fromBytes(entry.getValue(), typeToUse));
690652
}
691653

692654
return target;
@@ -721,9 +683,9 @@ private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?
721683

722684
Bucket partial = source.getBucket().extract(key);
723685

724-
byte[] typeInfo = partial.get(key + "._class");
686+
byte[] typeInfo = partial.get(key + "." + TYPE_HINT_ALIAS);
725687
if (typeInfo != null && typeInfo.length > 0) {
726-
partial.put("_class", typeInfo);
688+
partial.put(TYPE_HINT_ALIAS, typeInfo);
727689
}
728690

729691
Object o = readInternal(key, valueType, new RedisData(partial));
@@ -733,6 +695,24 @@ private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?
733695
return target;
734696
}
735697

698+
private Class<?> getTypeHint(String path, Bucket bucket, Class<?> fallback) {
699+
700+
byte[] typeInfo = bucket.get(path + "." + TYPE_HINT_ALIAS);
701+
702+
if (typeInfo == null || typeInfo.length < 1) {
703+
return fallback;
704+
}
705+
706+
String typeName = fromBytes(typeInfo, String.class);
707+
try {
708+
return ClassUtils.forName(typeName, this.getClass().getClassLoader());
709+
} catch (ClassNotFoundException e) {
710+
throw new MappingException(String.format("Cannot find class for type %s. ", typeName), e);
711+
} catch (LinkageError e) {
712+
throw new MappingException(String.format("Cannot find class for type %s. ", typeName), e);
713+
}
714+
}
715+
736716
/**
737717
* Convert given source to binary representation using the underlying {@link ConversionService}.
738718
*
@@ -836,7 +816,7 @@ private static class RedisTypeAliasAccessor implements TypeAliasAccessor<RedisDa
836816
private final ConversionService conversionService;
837817

838818
RedisTypeAliasAccessor(ConversionService conversionService) {
839-
this(conversionService, "_class");
819+
this(conversionService, TYPE_HINT_ALIAS);
840820
}
841821

842822
RedisTypeAliasAccessor(ConversionService conversionService, String typeKey) {

src/test/java/org/springframework/data/redis/core/convert/ConversionTestEntities.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import java.time.Period;
2424
import java.time.ZoneId;
2525
import java.time.ZonedDateTime;
26+
import java.util.ArrayList;
2627
import java.util.Date;
28+
import java.util.HashMap;
2729
import java.util.List;
2830
import java.util.Map;
2931
import java.util.concurrent.TimeUnit;
@@ -167,4 +169,9 @@ public static class WithArrays {
167169
Species[] arrayOfCompexTypes;
168170
}
169171

172+
static class TypeWithObjectValueTypes {
173+
Object object;
174+
Map<String, Object> map = new HashMap<String, Object>();
175+
List<Object> list = new ArrayList<Object>();
176+
}
170177
}

0 commit comments

Comments
 (0)