DATAREDIS-1032 - Fix CacheKey conversions for List, Map and Arrays.
Original pull request: #475.
This commit is contained in:
committed by
Mark Paluch
parent
1ae1acf98f
commit
3a77f6a147
@@ -17,11 +17,17 @@ package org.springframework.data.redis.cache;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.cache.support.AbstractValueAdaptingCache;
|
||||
import org.springframework.cache.support.NullValue;
|
||||
import org.springframework.cache.support.SimpleValueWrapper;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
@@ -33,14 +39,14 @@ import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.cache.Cache} implementation using for Redis as underlying store.
|
||||
* <p />
|
||||
* <p/>
|
||||
* Use {@link RedisCacheManager} to create {@link RedisCache} instances.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see RedisCacheConfiguration
|
||||
* @see RedisCacheWriter
|
||||
* @since 2.0
|
||||
*/
|
||||
public class RedisCache extends AbstractValueAdaptingCache {
|
||||
|
||||
@@ -281,8 +287,19 @@ public class RedisCache extends AbstractValueAdaptingCache {
|
||||
protected String convertKey(Object key) {
|
||||
|
||||
TypeDescriptor source = TypeDescriptor.forObject(key);
|
||||
|
||||
if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
|
||||
return conversionService.convert(key, String.class);
|
||||
try {
|
||||
return conversionService.convert(key, String.class);
|
||||
} catch (ConversionFailedException e) {
|
||||
|
||||
// may fail if the given key is a collection
|
||||
if (isCollectionLikeOrMap(source)) {
|
||||
return convertCollectionLikeOrMapKey(key, source);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");
|
||||
@@ -291,8 +308,39 @@ public class RedisCache extends AbstractValueAdaptingCache {
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Cannot convert %s to String. Register a Converter or override toString().", source));
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot convert cache key %s to String. Please provide a suitable Converter via 'RedisCacheConfiguration.withConversionService(...)' or override '%s.toString()'.",
|
||||
source, key != null ? key.getClass().getSimpleName() : "Object"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String convertCollectionLikeOrMapKey(Object key, TypeDescriptor source) {
|
||||
|
||||
if (source.isMap()) {
|
||||
|
||||
String target = "{";
|
||||
for (Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
|
||||
target += (convertKey(entry.getKey()) + "=" + convertKey(entry.getValue()));
|
||||
}
|
||||
target += "}";
|
||||
return target;
|
||||
} else if (source.isCollection() || source.isArray()) {
|
||||
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
|
||||
Collection<?> collection = source.isCollection() ? (Collection<?>) key
|
||||
: Arrays.asList(ObjectUtils.toObjectArray(key));
|
||||
|
||||
for (Object val : collection) {
|
||||
sj.add(convertKey(val));
|
||||
}
|
||||
return "[" + sj.toString() + "]";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isCollectionLikeOrMap(TypeDescriptor source) {
|
||||
return source.isArray() || source.isCollection() || source.isMap();
|
||||
}
|
||||
|
||||
private byte[] createAndConvertCacheKey(Object key) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -36,11 +37,11 @@ import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.springframework.cache.Cache.ValueWrapper;
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.cache.interceptor.SimpleKeyGenerator;
|
||||
import org.springframework.cache.support.NullValue;
|
||||
import org.springframework.data.redis.ConnectionFactoryTracker;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
@@ -300,6 +301,58 @@ public class RedisCacheTests {
|
||||
assertThat(result.get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1032
|
||||
public void cacheShouldAllowListKeyCacheKeysOfSimpleTypes() {
|
||||
|
||||
Object key = SimpleKeyGenerator.generateKey(Collections.singletonList("my-cache-key-in-a-list"));
|
||||
cache.put(key, sample);
|
||||
|
||||
Object target = cache.get(SimpleKeyGenerator.generateKey(Collections.singletonList("my-cache-key-in-a-list")));
|
||||
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1032
|
||||
public void cacheShouldAllowArrayKeyCacheKeysOfSimpleTypes() {
|
||||
|
||||
Object key = SimpleKeyGenerator.generateKey(new String[] { "my-cache-key-in-an-array" });
|
||||
cache.put(key, sample);
|
||||
|
||||
Object target = cache.get(SimpleKeyGenerator.generateKey(new String[] { "my-cache-key-in-an-array" }));
|
||||
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1032
|
||||
public void cacheShouldAllowListCacheKeysOfComplexTypes() {
|
||||
|
||||
Object key = SimpleKeyGenerator
|
||||
.generateKey(Collections.singletonList(new ComplexKey(sample.getFirstame(), sample.getBirthdate())));
|
||||
cache.put(key, sample);
|
||||
|
||||
Object target = cache.get(SimpleKeyGenerator
|
||||
.generateKey(Collections.singletonList(new ComplexKey(sample.getFirstame(), sample.getBirthdate()))));
|
||||
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1032
|
||||
public void cacheShouldAllowMapCacheKeys() {
|
||||
|
||||
Object key = SimpleKeyGenerator
|
||||
.generateKey(Collections.singletonMap("map-key", new ComplexKey(sample.getFirstame(), sample.getBirthdate())));
|
||||
cache.put(key, sample);
|
||||
|
||||
Object target = cache.get(SimpleKeyGenerator
|
||||
.generateKey(Collections.singletonMap("map-key", new ComplexKey(sample.getFirstame(), sample.getBirthdate()))));
|
||||
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1032
|
||||
public void cacheShouldFailOnNonConvertibleCacheKey() {
|
||||
|
||||
Object key = SimpleKeyGenerator
|
||||
.generateKey(Collections.singletonList(new InvalidKey(sample.getFirstame(), sample.getBirthdate())));
|
||||
assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> cache.put(key, sample));
|
||||
}
|
||||
|
||||
void doWithConnection(Consumer<RedisConnection> callback) {
|
||||
RedisConnection connection = connectionFactory.getConnection();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user