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.
This commit is contained in:
Christoph Strobl
2016-04-08 09:28:01 +02:00
parent 14e44880c7
commit 652b1b8354
3 changed files with 234 additions and 93 deletions

View File

@@ -48,6 +48,7 @@ import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.redis.core.index.Indexed;
@@ -107,6 +108,8 @@ import org.springframework.util.comparator.NullSafeComparator;
*/
public class MappingRedisConverter implements RedisConverter, InitializingBean {
private static final String TYPE_HINT_ALIAS = "_class";
private final RedisMappingContext mappingContext;
private final GenericConversionService conversionService;
private final EntityInstantiators entityInstantiators;
@@ -231,41 +234,9 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
else if (persistentProperty.isCollectionLike()) {
if (conversionService.canConvert(byte[].class, persistentProperty.getComponentType())) {
Object targetValue = null;
if (persistentProperty.getType().isArray()) {
List<Object> list = (List<Object>) readCollectionOfSimpleTypes(currentPath, ArrayList.class,
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source);
targetValue = list.toArray((Object[]) Array.newInstance(
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), list.size()));
} else {
targetValue = readCollectionOfSimpleTypes(currentPath, persistentProperty.getType(),
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source);
}
accessor.setProperty(persistentProperty, targetValue);
} else {
Object targetValue = null;
if (persistentProperty.getType().isArray()) {
List<Object> list = (List<Object>) readCollectionOfComplexTypes(currentPath, ArrayList.class,
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(),
source.getBucket());
targetValue = list.toArray((Object[]) Array.newInstance(
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), list.size()));
} else {
targetValue = readCollectionOfComplexTypes(currentPath, persistentProperty.getType(),
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(),
source.getBucket());
}
accessor.setProperty(persistentProperty, targetValue);
}
Object targetValue = readCollectionOrArray(currentPath, persistentProperty.getType(),
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source.getBucket());
accessor.setProperty(persistentProperty, targetValue);
} else if (persistentProperty.isEntity() && !conversionService.canConvert(byte[].class,
persistentProperty.getTypeInformation().getActualType().getType())) {
@@ -276,9 +247,9 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
RedisData source = new RedisData(bucket);
byte[] type = bucket.get(currentPath + "._class");
byte[] type = bucket.get(currentPath + "." + TYPE_HINT_ALIAS);
if (type != null && type.length > 0) {
source.getBucket().put("_class", type);
source.getBucket().put(TYPE_HINT_ALIAS, type);
}
accessor.setProperty(persistentProperty, readInternal(currentPath, targetType, source));
@@ -294,8 +265,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
}
}
accessor.setProperty(persistentProperty,
fromBytes(source.getBucket().get(currentPath), persistentProperty.getActualType()));
Class<?> typeToUse = getTypeHint(currentPath, source.getBucket(), persistentProperty.getActualType());
accessor.setProperty(persistentProperty, fromBytes(source.getBucket().get(currentPath), typeToUse));
}
}
@@ -405,16 +376,17 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
if (customConversions.hasCustomWriteTarget(value.getClass())) {
if (customConversions.getCustomWriteTarget(value.getClass()).equals(byte[].class)) {
if (!StringUtils.hasText(path) && customConversions.getCustomWriteTarget(value.getClass()).equals(byte[].class)) {
sink.getBucket().put(StringUtils.hasText(path) ? path : "_raw", conversionService.convert(value, byte[].class));
} else {
writeToBucket(path, value, sink);
writeToBucket(path, value, sink, typeHint.getType());
}
return;
}
if (value.getClass() != typeHint.getType()) {
sink.getBucket().put((!path.isEmpty() ? path + "._class" : "_class"), toBytes(value.getClass().getName()));
sink.getBucket().put((!path.isEmpty() ? path + "." + TYPE_HINT_ALIAS : TYPE_HINT_ALIAS),
toBytes(value.getClass().getName()));
}
final KeyValuePersistentEntity<?> entity = mappingContext.getPersistentEntity(value.getClass());
@@ -459,7 +431,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
} else {
Object propertyValue = accessor.getProperty(persistentProperty);
sink.getBucket().put(propertyStringPath, toBytes(propertyValue));
writeToBucket(propertyStringPath, propertyValue, sink, persistentProperty.getType());
}
}
});
@@ -541,7 +513,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
String currentPath = path + ".[" + i + "]";
if (customConversions.hasCustomWriteTarget(value.getClass())) {
writeToBucket(currentPath, value, sink);
writeToBucket(currentPath, value, sink, typeHint.getType());
} else {
writeInternal(keyspace, currentPath, value, typeHint, sink);
}
@@ -549,7 +521,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
}
}
private void writeToBucket(String path, Object value, RedisData sink) {
private void writeToBucket(String path, Object value, RedisData sink, Class<?> propertyType) {
if (value == null) {
return;
@@ -559,6 +531,12 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
Class<?> targetType = customConversions.getCustomWriteTarget(value.getClass());
if (!ClassUtils.isAssignable(Map.class, targetType) && customConversions.isSimpleType(value.getClass())
&& value.getClass() != propertyType) {
sink.getBucket().put((!path.isEmpty() ? path + "." + TYPE_HINT_ALIAS : TYPE_HINT_ALIAS),
toBytes(value.getClass().getName()));
}
if (ClassUtils.isAssignable(Map.class, targetType)) {
Map<?, ?> map = (Map<?, ?>) conversionService.convert(value, targetType);
@@ -576,59 +554,37 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
}
/**
* @param path
* @param collectionType
* @param valueType
* @param source
* @return
*/
private Collection<?> readCollectionOfSimpleTypes(String path, Class<?> collectionType, Class<?> valueType,
RedisData source) {
private Object readCollectionOrArray(String path, Class<?> collectionType, Class<?> valueType, Bucket bucket) {
Bucket partial = source.getBucket().extract(path + ".[");
List<String> keys = new ArrayList<String>(partial.keySet());
List<String> keys = new ArrayList<String>(bucket.extractAllKeysFor(path));
Collections.sort(keys, listKeyComparator);
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, partial.size());
for (String key : keys) {
target.add(fromBytes(partial.get(key), valueType));
}
return target;
}
/**
* @param path
* @param collectionType
* @param valueType
* @param source
* @return
*/
private Collection<?> readCollectionOfComplexTypes(String path, Class<?> collectionType, Class<?> valueType,
Bucket source) {
List<String> keys = new ArrayList<String>(source.extractAllKeysFor(path));
Collections.sort(keys, listKeyComparator);
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, keys.size());
boolean isArray = collectionType.isArray();
Class<?> collectionTypeToUse = isArray ? ArrayList.class : collectionType;
Collection<Object> target = CollectionFactory.createCollection(collectionTypeToUse, valueType, keys.size());
for (String key : keys) {
Bucket elementData = source.extract(key);
byte[] typeInfo = elementData.get(key + "._class");
if (typeInfo != null && typeInfo.length > 0) {
elementData.put("_class", typeInfo);
if (key.endsWith(TYPE_HINT_ALIAS)) {
continue;
}
Object o = readInternal(key, valueType, new RedisData(elementData));
target.add(o);
Bucket elementData = bucket.extract(key);
byte[] typeInfo = elementData.get(key + "." + TYPE_HINT_ALIAS);
if (typeInfo != null && typeInfo.length > 0) {
elementData.put(TYPE_HINT_ALIAS, typeInfo);
}
Class<?> typeToUse = getTypeHint(key, elementData, valueType);
if (conversionService.canConvert(byte[].class, typeToUse)) {
target.add(fromBytes(elementData.get(key), typeToUse));
} else {
target.add(readInternal(key, valueType, new RedisData(elementData)));
}
}
return target;
return isArray ? target.toArray((Object[]) Array.newInstance(valueType, target.size())) : target;
}
/**
@@ -653,7 +609,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
String currentPath = path + ".[" + entry.getKey() + "]";
if (customConversions.hasCustomWriteTarget(entry.getValue().getClass())) {
writeToBucket(currentPath, entry.getValue(), sink);
writeToBucket(currentPath, entry.getValue(), sink, mapValueType);
} else {
writeInternal(keyspace, currentPath, entry.getValue(), ClassTypeInformation.from(mapValueType), sink);
}
@@ -677,6 +633,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
for (Entry<String, byte[]> entry : partial.entrySet()) {
if (entry.getKey().endsWith(TYPE_HINT_ALIAS)) {
continue;
}
String regex = "^(" + Pattern.quote(path) + "\\.\\[)(.*?)(\\])";
Pattern pattern = Pattern.compile(regex);
@@ -686,7 +646,9 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
String.format("Cannot extract map value for key '%s' in path '%s'.", entry.getKey(), path));
}
String key = matcher.group(2);
target.put(key, fromBytes(entry.getValue(), valueType));
Class<?> typeToUse = getTypeHint(path + ".[" + key + "]", source.getBucket(), valueType);
target.put(key, fromBytes(entry.getValue(), typeToUse));
}
return target;
@@ -721,9 +683,9 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
Bucket partial = source.getBucket().extract(key);
byte[] typeInfo = partial.get(key + "._class");
byte[] typeInfo = partial.get(key + "." + TYPE_HINT_ALIAS);
if (typeInfo != null && typeInfo.length > 0) {
partial.put("_class", typeInfo);
partial.put(TYPE_HINT_ALIAS, typeInfo);
}
Object o = readInternal(key, valueType, new RedisData(partial));
@@ -733,6 +695,24 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
return target;
}
private Class<?> getTypeHint(String path, Bucket bucket, Class<?> fallback) {
byte[] typeInfo = bucket.get(path + "." + TYPE_HINT_ALIAS);
if (typeInfo == null || typeInfo.length < 1) {
return fallback;
}
String typeName = fromBytes(typeInfo, String.class);
try {
return ClassUtils.forName(typeName, this.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
throw new MappingException(String.format("Cannot find class for type %s. ", typeName), e);
} catch (LinkageError e) {
throw new MappingException(String.format("Cannot find class for type %s. ", typeName), e);
}
}
/**
* Convert given source to binary representation using the underlying {@link ConversionService}.
*
@@ -836,7 +816,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
private final ConversionService conversionService;
RedisTypeAliasAccessor(ConversionService conversionService) {
this(conversionService, "_class");
this(conversionService, TYPE_HINT_ALIAS);
}
RedisTypeAliasAccessor(ConversionService conversionService, String typeKey) {