DATAREDIS-634 - Adapt to moved CustomConversions to Spring Data Commons.
Introduce RedisCustomConversions extending o.s.d.convert.CustomConversions. Remove o.s.d.mongo.core.convert.CustomConversions implementation code and utility classes and let it extend RedisCustomConversions. Replace references to o.s.d.r.c.c.CustomConversions with o.s.d.convert.CustomConversions. Adapt tests. Introduce constructors for ObjectHashMapper and RedisKeyValueAdapter requiring o.s.d.convert.CustomConversions and deprecate constructors accepting o.s.d.r.c.c.CustomConversions. Related ticket: DATACMNS-1035.
This commit is contained in:
@@ -28,8 +28,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -42,7 +40,6 @@ import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
@@ -55,6 +52,7 @@ import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.PathIndexResolver;
|
||||
import org.springframework.data.redis.core.convert.RedisConverter;
|
||||
import org.springframework.data.redis.core.convert.RedisCustomConversions;
|
||||
import org.springframework.data.redis.core.convert.RedisData;
|
||||
import org.springframework.data.redis.core.convert.ReferenceResolverImpl;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
@@ -66,7 +64,6 @@ import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Redis specific {@link KeyValueAdapter} implementation. Uses binary codec to read/write data from/to Redis. Objects
|
||||
@@ -117,7 +114,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
|
||||
* {@link CustomConversions}.
|
||||
* {@link RedisCustomConversions}.
|
||||
*
|
||||
* @param redisOps must not be {@literal null}.
|
||||
*/
|
||||
@@ -126,13 +123,13 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisKeyValueAdapter} with default {@link CustomConversions}.
|
||||
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisCustomConversions}.
|
||||
*
|
||||
* @param redisOps must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisMappingContext mappingContext) {
|
||||
this(redisOps, mappingContext, new CustomConversions());
|
||||
this(redisOps, mappingContext, new RedisCustomConversions());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,9 +138,25 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
* @param redisOps must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param customConversions can be {@literal null}.
|
||||
* @deprecated since 2.0, use
|
||||
* {@link #RedisKeyValueAdapter(RedisOperations, RedisMappingContext, org.springframework.data.convert.CustomConversions)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisMappingContext mappingContext,
|
||||
CustomConversions customConversions) {
|
||||
this(redisOps, mappingContext, (org.springframework.data.convert.CustomConversions) customConversions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisKeyValueAdapter}.
|
||||
*
|
||||
* @param redisOps must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param customConversions can be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisMappingContext mappingContext,
|
||||
org.springframework.data.convert.CustomConversions customConversions) {
|
||||
|
||||
super(new RedisQueryEngine());
|
||||
|
||||
@@ -152,7 +165,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
|
||||
MappingRedisConverter mappingConverter = new MappingRedisConverter(mappingContext,
|
||||
new PathIndexResolver(mappingContext), new ReferenceResolverImpl(redisOps));
|
||||
mappingConverter.setCustomConversions(customConversions == null ? new CustomConversions() : customConversions);
|
||||
mappingConverter.setCustomConversions(customConversions == null ? new RedisCustomConversions() : customConversions);
|
||||
mappingConverter.afterPropertiesSet();
|
||||
|
||||
this.converter = mappingConverter;
|
||||
@@ -374,7 +387,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
|
||||
offset = Math.max(0, offset);
|
||||
if (offset >= 0 && rows > 0) {
|
||||
keys = keys.subList((int)offset, Math.min((int)offset + rows, keys.size()));
|
||||
keys = keys.subList((int) offset, Math.min((int) offset + rows, keys.size()));
|
||||
}
|
||||
|
||||
for (byte[] key : keys) {
|
||||
@@ -428,7 +441,8 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
|
||||
public void update(final PartialUpdate<?> update) {
|
||||
|
||||
final RedisPersistentEntity<?> entity = this.converter.getMappingContext().getPersistentEntity(update.getTarget()).get();
|
||||
final RedisPersistentEntity<?> entity = this.converter.getMappingContext().getPersistentEntity(update.getTarget())
|
||||
.get();
|
||||
|
||||
final String keyspace = entity.getKeySpace();
|
||||
final Object id = update.getId();
|
||||
@@ -622,11 +636,10 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
if (entity.hasExplictTimeToLiveProperty()) {
|
||||
|
||||
Optional<RedisPersistentProperty> ttlProperty = entity.getExplicitTimeToLiveProperty();
|
||||
if(!ttlProperty.isPresent()) {
|
||||
if (!ttlProperty.isPresent()) {
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
final Optional<TimeToLive> ttl = ttlProperty.get().findAnnotation(TimeToLive.class);
|
||||
|
||||
Long timeout = redisOps.execute(new RedisCallback<Long>() {
|
||||
|
||||
@@ -15,445 +15,33 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion. That is essentially a {@link List} of converters and some additional logic
|
||||
* around them.
|
||||
*
|
||||
*
|
||||
* @author Olivyer Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
* @deprecated since 2.0, use {@link RedisCustomConversions}.
|
||||
*/
|
||||
public class CustomConversions {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CustomConversions.class);
|
||||
private static final String READ_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as reading converter although it doesn't convert from a Redis supported type! You might wanna check you annotation setup at the converter implementation.";
|
||||
private static final String WRITE_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as writing converter although it doesn't convert to a Redis supported type! You might wanna check you annotation setup at the converter implementation.";
|
||||
|
||||
private final Set<ConvertiblePair> readingPairs;
|
||||
private final Set<ConvertiblePair> writingPairs;
|
||||
private final Set<Class<?>> customSimpleTypes;
|
||||
private final SimpleTypeHolder simpleTypeHolder;
|
||||
|
||||
private final List<Object> converters;
|
||||
|
||||
private final Map<ConvertiblePair, Optional<Class<?>>> customReadTargetTypes;
|
||||
private final Map<ConvertiblePair, Optional<Class<?>>> customWriteTargetTypes;
|
||||
private final Map<Class<?>, Optional<Class<?>>> rawWriteTargetTypes;
|
||||
@Deprecated
|
||||
public class CustomConversions extends RedisCustomConversions {
|
||||
|
||||
/**
|
||||
* Creates an empty {@link CustomConversions} object.
|
||||
*/
|
||||
public CustomConversions() {
|
||||
this(new ArrayList<Object>());
|
||||
}
|
||||
public CustomConversions() {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CustomConversions} instance registering the given converters.
|
||||
*
|
||||
*
|
||||
* @param converters
|
||||
*/
|
||||
public CustomConversions(List<?> converters) {
|
||||
|
||||
Assert.notNull(converters,"Converters must not be null!");
|
||||
|
||||
this.readingPairs = new LinkedHashSet<ConvertiblePair>();
|
||||
this.writingPairs = new LinkedHashSet<ConvertiblePair>();
|
||||
this.customSimpleTypes = new HashSet<Class<?>>();
|
||||
this.customReadTargetTypes = new ConcurrentHashMap<ConvertiblePair, Optional<Class<?>>>();
|
||||
this.customWriteTargetTypes = new ConcurrentHashMap<ConvertiblePair, Optional<Class<?>>>();
|
||||
this.rawWriteTargetTypes = new ConcurrentHashMap<Class<?>, Optional<Class<?>>>();
|
||||
|
||||
List<Object> toRegister = new ArrayList<Object>();
|
||||
|
||||
// Add user provided converters to make sure they can override the defaults
|
||||
toRegister.addAll(converters);
|
||||
toRegister.add(new BinaryConverters.StringToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToStringConverter());
|
||||
toRegister.add(new BinaryConverters.NumberToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToNumberConverterFactory());
|
||||
toRegister.add(new BinaryConverters.EnumToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToEnumConverterFactory());
|
||||
toRegister.add(new BinaryConverters.BooleanToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToBooleanConverter());
|
||||
toRegister.add(new BinaryConverters.DateToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToDateConverter());
|
||||
|
||||
toRegister.addAll(Jsr310Converters.getConvertersToRegister());
|
||||
|
||||
for (Object c : toRegister) {
|
||||
registerConversion(c);
|
||||
}
|
||||
|
||||
Collections.reverse(toRegister);
|
||||
|
||||
this.converters = Collections.unmodifiableList(toRegister);
|
||||
this.simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link SimpleTypeHolder}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SimpleTypeHolder getSimpleTypeHolder() {
|
||||
return simpleTypeHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is considered to be simple. That means it's either a general simple type or we have
|
||||
* a writing {@link Converter} registered for a particular type.
|
||||
*
|
||||
* @see SimpleTypeHolder#isSimpleType(Class)
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public boolean isSimpleType(Class<?> type) {
|
||||
return simpleTypeHolder.isSimpleType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link GenericConversionService} with the convertes registered.
|
||||
*
|
||||
* @param conversionService
|
||||
*/
|
||||
public void registerConvertersIn(GenericConversionService conversionService) {
|
||||
|
||||
for (Object converter : converters) {
|
||||
|
||||
boolean added = false;
|
||||
|
||||
if (converter instanceof Converter) {
|
||||
conversionService.addConverter((Converter<?, ?>) converter);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (converter instanceof ConverterFactory) {
|
||||
conversionService.addConverterFactory((ConverterFactory<?, ?>) converter);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (converter instanceof GenericConverter) {
|
||||
conversionService.addConverter((GenericConverter) converter);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given set contains element that is neither Converter nor ConverterFactory!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a conversion for the given converter. Inspects either generics or the {@link ConvertiblePair}s returned
|
||||
* by a {@link GenericConverter}.
|
||||
*
|
||||
* @param converter
|
||||
*/
|
||||
private void registerConversion(Object converter) {
|
||||
|
||||
Class<?> type = converter.getClass();
|
||||
boolean isWriting = type.isAnnotationPresent(WritingConverter.class);
|
||||
boolean isReading = type.isAnnotationPresent(ReadingConverter.class);
|
||||
|
||||
if (converter instanceof GenericConverter) {
|
||||
GenericConverter genericConverter = (GenericConverter) converter;
|
||||
for (ConvertiblePair pair : genericConverter.getConvertibleTypes()) {
|
||||
register(new ConverterRegistration(pair, isReading, isWriting));
|
||||
}
|
||||
} else if (converter instanceof Converter) {
|
||||
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), Converter.class);
|
||||
register(new ConverterRegistration(new ConvertiblePair(arguments[0], arguments[1]), isReading, isWriting));
|
||||
} else if (converter instanceof ConverterFactory) {
|
||||
|
||||
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), ConverterFactory.class);
|
||||
register(new ConverterRegistration(new ConvertiblePair(arguments[0], arguments[1]), isReading, isWriting));
|
||||
}
|
||||
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported Converter type!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link ConvertiblePair} as reading or writing pair depending on the type sides being basic
|
||||
* Redis types.
|
||||
*
|
||||
* @param converterRegistration
|
||||
*/
|
||||
private void register(ConverterRegistration converterRegistration) {
|
||||
|
||||
ConvertiblePair pair = converterRegistration.getConvertiblePair();
|
||||
|
||||
if (converterRegistration.isReading()) {
|
||||
|
||||
readingPairs.add(pair);
|
||||
|
||||
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleSourceType()) {
|
||||
LOG.warn(String.format(READ_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
|
||||
}
|
||||
}
|
||||
|
||||
if (converterRegistration.isWriting()) {
|
||||
|
||||
writingPairs.add(pair);
|
||||
customSimpleTypes.add(pair.getSourceType());
|
||||
|
||||
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleTargetType()) {
|
||||
LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target type to convert to in case we have a custom conversion registered to convert the given source
|
||||
* type into a Redis native one.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
public Class<?> getCustomWriteTarget(final Class<?> sourceType) {
|
||||
|
||||
return getOrCreateAndCache(sourceType, rawWriteTargetTypes,
|
||||
() -> getCustomTarget(sourceType, null, writingPairs).isPresent() ? (Class) getCustomTarget(sourceType, null, writingPairs).get() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target type we can readTargetWriteLocl an inject of the given source type to. The returned type might
|
||||
* be a subclass of the given expected type though. If {@code expectedTargetType} is {@literal null} we will simply
|
||||
* return the first target type matching or {@literal null} if no conversion can be found.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}
|
||||
* @param requestedTargetType
|
||||
* @return
|
||||
*/
|
||||
public Class<?> getCustomWriteTarget(final Class<?> sourceType, final Class<?> requestedTargetType) {
|
||||
|
||||
if (requestedTargetType == null) {
|
||||
return getCustomWriteTarget(sourceType);
|
||||
}
|
||||
|
||||
return getOrCreateAndCache(new ConvertiblePair(sourceType, requestedTargetType), customWriteTargetTypes,
|
||||
() -> (Class) getCustomTarget(sourceType, requestedTargetType, writingPairs).get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a custom conversion registered to readTargetWriteLocl into a Redis native type. The
|
||||
* returned type might be a subclass of the given expected type though.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
public boolean hasCustomWriteTarget(Class<?> sourceType) {
|
||||
return hasCustomWriteTarget(sourceType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a custom conversion registered to readTargetWriteLocl an object of the given source type
|
||||
* into an object of the given Redis native target type.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}.
|
||||
* @param requestedTargetType
|
||||
* @return
|
||||
*/
|
||||
public boolean hasCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
|
||||
return getCustomWriteTarget(sourceType, requestedTargetType) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a custom conversion registered to readTargetReadLock the given source into the given target
|
||||
* type.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}
|
||||
* @param requestedTargetType must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
public boolean hasCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
|
||||
return getCustomReadTarget(sourceType, requestedTargetType) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual target type for the given {@code sourceType} and {@code requestedTargetType}. Note that the
|
||||
* returned {@link Class} could be an assignable type to the given {@code requestedTargetType}.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}.
|
||||
* @param requestedTargetType can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Class<?> getCustomReadTarget(final Class<?> sourceType, final Class<?> requestedTargetType) {
|
||||
|
||||
if (requestedTargetType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getOrCreateAndCache(new ConvertiblePair(sourceType, requestedTargetType), customReadTargetTypes,
|
||||
() -> getCustomTarget(sourceType, requestedTargetType, readingPairs).isPresent() ? ((Class) getCustomTarget(sourceType, requestedTargetType, readingPairs).get()) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the given {@link ConvertiblePair}s for ones that have a source compatible type as source. Additionally
|
||||
* checks assignability of the target type if one is given.
|
||||
*
|
||||
* @param sourceType must not be {@literal null}.
|
||||
* @param requestedTargetType can be {@literal null}.
|
||||
* @param pairs must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Optional<Class<?>> getCustomTarget(Class<?> sourceType, Class<?> requestedTargetType,
|
||||
Collection<ConvertiblePair> pairs) {
|
||||
|
||||
Assert.notNull(sourceType, "SourceType must not be null!");
|
||||
Assert.notNull(pairs, "Convertible pairs must not be null!");
|
||||
|
||||
if (requestedTargetType != null && pairs.contains(new ConvertiblePair(sourceType, requestedTargetType))) {
|
||||
return Optional.of(requestedTargetType);
|
||||
}
|
||||
|
||||
for (ConvertiblePair typePair : pairs) {
|
||||
if (typePair.getSourceType().isAssignableFrom(sourceType)) {
|
||||
Class<?> targetType = typePair.getTargetType();
|
||||
if (requestedTargetType == null || targetType.isAssignableFrom(requestedTargetType)) {
|
||||
return Optional.of(targetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will try to find a value for the given key in the given cache or produce one using the given {@link Supplier} and
|
||||
* store it in the cache.
|
||||
*
|
||||
* @param key the key to lookup a potentially existing value, must not be {@literal null}.
|
||||
* @param cache the cache to find the value in, must not be {@literal null}.
|
||||
* @param producer the {@link Supplier} to create values to cache, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static <T> Class<?> getOrCreateAndCache(T key, Map<T, Optional<Class<?>>> cache,
|
||||
Supplier<Class<T>> producer) {
|
||||
Optional<Class<?>> foo = cache.computeIfAbsent(key, t -> Optional.ofNullable(producer.get()));
|
||||
return foo.isPresent() ? foo.get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion registration information.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class ConverterRegistration {
|
||||
|
||||
private final ConvertiblePair convertiblePair;
|
||||
private final boolean reading;
|
||||
private final boolean writing;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConverterRegistration}.
|
||||
*
|
||||
* @param convertiblePair must not be {@literal null}.
|
||||
* @param isReading whether to force to consider the converter for reading.
|
||||
* @param isWriting whether to force to consider the converter for reading.
|
||||
*/
|
||||
public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) {
|
||||
|
||||
Assert.notNull(convertiblePair, "Convertible pair must not be null!");
|
||||
|
||||
this.convertiblePair = convertiblePair;
|
||||
this.reading = isReading;
|
||||
this.writing = isWriting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags.
|
||||
*
|
||||
* @param source the source type to be converted from, must not be {@literal null}.
|
||||
* @param target the target type to be converted to, must not be {@literal null}.
|
||||
* @param isReading whether to force to consider the converter for reading.
|
||||
* @param isWriting whether to force to consider the converter for writing.
|
||||
*/
|
||||
public ConverterRegistration(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
|
||||
this(new ConvertiblePair(source, target), isReading, isWriting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the converter shall be used for writing.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isWriting() {
|
||||
return writing == true || (!reading && isSimpleTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the converter shall be used for reading.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isReading() {
|
||||
return reading == true || (!writing && isSimpleSourceType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual conversion pair.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ConvertiblePair getConvertiblePair() {
|
||||
return convertiblePair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the source type is a Redis simple one.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSimpleSourceType() {
|
||||
return isRedisBasicType(convertiblePair.getSourceType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the target type is a Redis simple one.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSimpleTargetType() {
|
||||
return isRedisBasicType(convertiblePair.getTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is a type that Redis can handle basically.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private static boolean isRedisBasicType(Class<?> type) {
|
||||
return (byte[].class.equals(type) || Map.class.equals(type));
|
||||
}
|
||||
super(converters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,17 +16,8 @@
|
||||
package org.springframework.data.redis.core.convert;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -36,6 +27,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
@@ -152,7 +144,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
entityInstantiators = new EntityInstantiators();
|
||||
|
||||
this.conversionService = new DefaultConversionService();
|
||||
this.customConversions = new CustomConversions();
|
||||
this.customConversions = new RedisCustomConversions();
|
||||
|
||||
typeMapper = new DefaultTypeMapper<RedisData>(new RedisTypeAliasAccessor(this.conversionService));
|
||||
|
||||
@@ -232,7 +224,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
if (persistentProperty.isMap()) {
|
||||
|
||||
Map<?, ?> targetValue = null;
|
||||
Class<?> mapValueType = persistentProperty.getMapValueType().orElseThrow(()-> new IllegalArgumentException("Unable to retrieve MapValueType!"));
|
||||
Class<?> mapValueType = persistentProperty.getMapValueType()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unable to retrieve MapValueType!"));
|
||||
|
||||
if (conversionService.canConvert(byte[].class, mapValueType)) {
|
||||
targetValue = readMapOfSimpleTypes(currentPath, persistentProperty.getType(),
|
||||
@@ -502,7 +495,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
pUpdate.getPropertyPath(), pUpdate.getValue()));
|
||||
}
|
||||
|
||||
writeMap(entity.getKeySpace(), pUpdate.getPropertyPath(), targetProperty.getMapValueType().orElse(Object.class), map, sink);
|
||||
writeMap(entity.getKeySpace(), pUpdate.getPropertyPath(), targetProperty.getMapValueType().orElse(Object.class),
|
||||
map, sink);
|
||||
} else {
|
||||
|
||||
writeInternal(entity.getKeySpace(), pUpdate.getPropertyPath(), pUpdate.getValue(),
|
||||
@@ -735,22 +729,23 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
|
||||
if (customConversions.hasCustomWriteTarget(value.getClass())) {
|
||||
|
||||
Class<?> targetType = customConversions.getCustomWriteTarget(value.getClass());
|
||||
Optional<Class<?>> targetType = customConversions.getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (!ClassUtils.isAssignable(Map.class, targetType) && customConversions.isSimpleType(value.getClass())
|
||||
if (!targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent()
|
||||
&& 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)) {
|
||||
if (targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent()) {
|
||||
|
||||
Map<?, ?> map = (Map<?, ?>) conversionService.convert(value, targetType);
|
||||
Map<?, ?> map = (Map<?, ?>) conversionService.convert(value, targetType.get());
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
sink.getBucket().put(path + (StringUtils.hasText(path) ? "." : "") + entry.getKey(),
|
||||
toBytes(entry.getValue()));
|
||||
}
|
||||
} else if (ClassUtils.isAssignable(byte[].class, targetType)) {
|
||||
} else if (targetType.filter(it -> ClassUtils.isAssignable(byte[].class, it)).isPresent()) {
|
||||
sink.getBucket().put(path, toBytes(value));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
@@ -986,7 +981,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* @param customConversions
|
||||
*/
|
||||
public void setCustomConversions(CustomConversions customConversions) {
|
||||
this.customConversions = customConversions != null ? customConversions : new CustomConversions();
|
||||
this.customConversions = customConversions != null ? customConversions : new RedisCustomConversions();
|
||||
}
|
||||
|
||||
public void setReferenceResolver(ReferenceResolver referenceResolver) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion. That is essentially a {@link List} of converters and some additional logic
|
||||
* around them.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see org.springframework.data.convert.CustomConversions
|
||||
* @see SimpleTypeHolder
|
||||
*/
|
||||
public class RedisCustomConversions extends org.springframework.data.convert.CustomConversions {
|
||||
|
||||
private static final StoreConversions STORE_CONVERSIONS;
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.add(new BinaryConverters.StringToBytesConverter());
|
||||
converters.add(new BinaryConverters.BytesToStringConverter());
|
||||
converters.add(new BinaryConverters.NumberToBytesConverter());
|
||||
converters.add(new BinaryConverters.BytesToNumberConverterFactory());
|
||||
converters.add(new BinaryConverters.EnumToBytesConverter());
|
||||
converters.add(new BinaryConverters.BytesToEnumConverterFactory());
|
||||
converters.add(new BinaryConverters.BooleanToBytesConverter());
|
||||
converters.add(new BinaryConverters.BytesToBooleanConverter());
|
||||
converters.add(new BinaryConverters.DateToBytesConverter());
|
||||
converters.add(new BinaryConverters.BytesToDateConverter());
|
||||
converters.addAll(Jsr310Converters.getConvertersToRegister());
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@link RedisCustomConversions} object.
|
||||
*/
|
||||
public RedisCustomConversions() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link RedisCustomConversions} instance registering the given converters.
|
||||
*
|
||||
* @param converters
|
||||
*/
|
||||
public RedisCustomConversions(List<?> converters) {
|
||||
super(STORE_CONVERSIONS, converters);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import org.springframework.data.redis.core.TimeToLiveAccessor;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings;
|
||||
import org.springframework.data.redis.core.convert.MappingConfiguration;
|
||||
import org.springframework.data.redis.core.convert.RedisCustomConversions;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -51,13 +52,15 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Redis specific {@link MappingContext}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @since 1.7
|
||||
*/
|
||||
public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentEntity<?>, RedisPersistentProperty> {
|
||||
|
||||
private static final SimpleTypeHolder SIMPLE_TYPE_HOLDER = new RedisCustomConversions().getSimpleTypeHolder();
|
||||
|
||||
private final MappingConfiguration mappingConfiguration;
|
||||
private final TimeToLiveAccessor timeToLiveAccessor;
|
||||
|
||||
@@ -72,7 +75,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisMappingContext}.
|
||||
*
|
||||
*
|
||||
* @param mappingConfiguration can be {@literal null}.
|
||||
*/
|
||||
public RedisMappingContext(MappingConfiguration mappingConfiguration) {
|
||||
@@ -83,11 +86,12 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
setFallbackKeySpaceResolver(new ConfigAwareKeySpaceResolver(this.mappingConfiguration.getKeyspaceConfiguration()));
|
||||
this.timeToLiveAccessor = new ConfigAwareTimeToLiveAccessor(this.mappingConfiguration.getKeyspaceConfiguration(),
|
||||
this);
|
||||
this.setSimpleTypeHolder(SIMPLE_TYPE_HOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link KeySpaceResolver} to be used if not explicit key space is annotated to the domain type.
|
||||
*
|
||||
*
|
||||
* @param fallbackKeySpaceResolver can be {@literal null}.
|
||||
*/
|
||||
public void setFallbackKeySpaceResolver(KeySpaceResolver fallbackKeySpaceResolver) {
|
||||
@@ -107,7 +111,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* Get the {@link MappingConfiguration} used.
|
||||
*
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public MappingConfiguration getMappingConfiguration() {
|
||||
@@ -116,7 +120,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* {@link KeySpaceResolver} implementation considering {@link KeySpace} and {@link KeyspaceConfiguration}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
@@ -151,7 +155,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* {@link KeySpaceResolver} implementation considering {@link KeySpace}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
@@ -173,7 +177,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* {@link TimeToLiveAccessor} implementation considering {@link KeyspaceConfiguration}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
@@ -187,7 +191,7 @@ public class RedisMappingContext extends KeyValueMappingContext<RedisPersistentE
|
||||
|
||||
/**
|
||||
* Creates new {@link ConfigAwareTimeToLiveAccessor}
|
||||
*
|
||||
*
|
||||
* @param keyspaceConfig must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import org.springframework.data.redis.core.convert.CustomConversions;
|
||||
import org.springframework.data.redis.core.convert.IndexResolver;
|
||||
import org.springframework.data.redis.core.convert.IndexedData;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.RedisCustomConversions;
|
||||
import org.springframework.data.redis.core.convert.RedisData;
|
||||
import org.springframework.data.redis.core.convert.ReferenceResolver;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
@@ -74,19 +75,31 @@ public class ObjectHashMapper implements HashMapper<Object, byte[], byte[]> {
|
||||
* Creates new {@link ObjectHashMapper}.
|
||||
*/
|
||||
public ObjectHashMapper() {
|
||||
this(new CustomConversions());
|
||||
this(new RedisCustomConversions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectHashMapper}.
|
||||
*
|
||||
* @param customConversions can be {@literal null}.
|
||||
* @deprecated since 2.0, use {@link #ObjectHashMapper(org.springframework.data.convert.CustomConversions)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public ObjectHashMapper(CustomConversions customConversions) {
|
||||
this((org.springframework.data.convert.CustomConversions) customConversions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectHashMapper}.
|
||||
*
|
||||
* @param customConversions can be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public ObjectHashMapper(org.springframework.data.convert.CustomConversions customConversions) {
|
||||
|
||||
MappingRedisConverter mappingConverter = new MappingRedisConverter(new RedisMappingContext(),
|
||||
new NoOpIndexResolver(), new NoOpReferenceResolver());
|
||||
mappingConverter.setCustomConversions(customConversions == null ? new CustomConversions() : customConversions);
|
||||
mappingConverter.setCustomConversions(customConversions == null ? new RedisCustomConversions() : customConversions);
|
||||
mappingConverter.afterPropertiesSet();
|
||||
|
||||
converter = mappingConverter;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,9 +33,9 @@ import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryCon
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import org.springframework.data.redis.core.RedisKeyValueAdapter;
|
||||
import org.springframework.data.redis.core.RedisKeyValueTemplate;
|
||||
import org.springframework.data.redis.core.convert.CustomConversions;
|
||||
import org.springframework.data.redis.core.convert.MappingConfiguration;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.RedisCustomConversions;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
@@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
|
||||
* {@link RepositoryConfigurationExtension} for Redis.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryConfigurationExtension {
|
||||
@@ -92,7 +93,7 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
|
||||
registerIfNotAlreadyRegistered(mappingContextDefinition, registry, MAPPING_CONTEXT_BEAN_NAME, configurationSource);
|
||||
|
||||
// register coustom conversions
|
||||
RootBeanDefinition customConversions = new RootBeanDefinition(CustomConversions.class);
|
||||
RootBeanDefinition customConversions = new RootBeanDefinition(RedisCustomConversions.class);
|
||||
registerIfNotAlreadyRegistered(customConversions, registry, REDIS_CUSTOM_CONVERSIONS_BEAN_NAME,
|
||||
configurationSource);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user