Skip to content

Commit 17f05c4

Browse files
christophstroblmp911de
authored andcommitted
DATAREDIS-1032 - Fix CacheKey conversions for List, Map and Arrays.
Original pull request: #475.
1 parent 8c8ba7a commit 17f05c4

File tree

2 files changed

+107
-6
lines changed

2 files changed

+107
-6
lines changed

src/main/java/org/springframework/data/redis/cache/RedisCache.java

+53-5
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.nio.ByteBuffer;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.Map;
23+
import java.util.Map.Entry;
24+
import java.util.StringJoiner;
2025
import java.util.concurrent.Callable;
2126

2227
import org.springframework.cache.support.AbstractValueAdaptingCache;
2328
import org.springframework.cache.support.NullValue;
2429
import org.springframework.cache.support.SimpleValueWrapper;
30+
import org.springframework.core.convert.ConversionFailedException;
2531
import org.springframework.core.convert.ConversionService;
2632
import org.springframework.core.convert.TypeDescriptor;
2733
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -33,14 +39,14 @@
3339

3440
/**
3541
* {@link org.springframework.cache.Cache} implementation using for Redis as underlying store.
36-
* <p />
42+
* <p/>
3743
* Use {@link RedisCacheManager} to create {@link RedisCache} instances.
3844
*
3945
* @author Christoph Strobl
4046
* @author Mark Paluch
41-
* @since 2.0
4247
* @see RedisCacheConfiguration
4348
* @see RedisCacheWriter
49+
* @since 2.0
4450
*/
4551
public class RedisCache extends AbstractValueAdaptingCache {
4652

@@ -281,8 +287,19 @@ protected String createCacheKey(Object key) {
281287
protected String convertKey(Object key) {
282288

283289
TypeDescriptor source = TypeDescriptor.forObject(key);
290+
284291
if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
285-
return conversionService.convert(key, String.class);
292+
try {
293+
return conversionService.convert(key, String.class);
294+
} catch (ConversionFailedException e) {
295+
296+
// may fail if the given key is a collection
297+
if (isCollectionLikeOrMap(source)) {
298+
return convertCollectionLikeOrMapKey(key, source);
299+
}
300+
301+
throw e;
302+
}
286303
}
287304

288305
Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");
@@ -291,8 +308,39 @@ protected String convertKey(Object key) {
291308
return key.toString();
292309
}
293310

294-
throw new IllegalStateException(
295-
String.format("Cannot convert %s to String. Register a Converter or override toString().", source));
311+
throw new IllegalStateException(String.format(
312+
"Cannot convert cache key %s to String. Please provide a suitable Converter via 'RedisCacheConfiguration.withConversionService(...)' or override '%s.toString()'.",
313+
source, key != null ? key.getClass().getSimpleName() : "Object"));
314+
}
315+
316+
@Nullable
317+
private String convertCollectionLikeOrMapKey(Object key, TypeDescriptor source) {
318+
319+
if (source.isMap()) {
320+
321+
String target = "{";
322+
for (Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
323+
target += (convertKey(entry.getKey()) + "=" + convertKey(entry.getValue()));
324+
}
325+
target += "}";
326+
return target;
327+
} else if (source.isCollection() || source.isArray()) {
328+
329+
StringJoiner sj = new StringJoiner(",");
330+
331+
Collection<?> collection = source.isCollection() ? (Collection<?>) key
332+
: Arrays.asList(ObjectUtils.toObjectArray(key));
333+
334+
for (Object val : collection) {
335+
sj.add(convertKey(val));
336+
}
337+
return "[" + sj.toString() + "]";
338+
}
339+
return null;
340+
}
341+
342+
private boolean isCollectionLikeOrMap(TypeDescriptor source) {
343+
return source.isArray() || source.isCollection() || source.isMap();
296344
}
297345

298346
private byte[] createAndConvertCacheKey(Object key) {

src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java

+54-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.Serializable;
2626
import java.nio.charset.Charset;
2727
import java.util.Collection;
28+
import java.util.Collections;
2829
import java.util.Date;
2930
import java.util.function.Consumer;
3031

@@ -36,11 +37,11 @@
3637
import org.junit.runners.Parameterized.Parameters;
3738
import org.springframework.cache.Cache.ValueWrapper;
3839
import org.springframework.cache.interceptor.SimpleKey;
40+
import org.springframework.cache.interceptor.SimpleKeyGenerator;
3941
import org.springframework.cache.support.NullValue;
4042
import org.springframework.data.redis.ConnectionFactoryTracker;
4143
import org.springframework.data.redis.connection.RedisConnection;
4244
import org.springframework.data.redis.connection.RedisConnectionFactory;
43-
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
4445
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
4546
import org.springframework.data.redis.serializer.RedisSerializer;
4647

@@ -300,6 +301,58 @@ public void fetchKeyWithComputedPrefixReturnsExpectedResult() {
300301
assertThat(result.get()).isEqualTo(sample);
301302
}
302303

304+
@Test // DATAREDIS-1032
305+
public void cacheShouldAllowListKeyCacheKeysOfSimpleTypes() {
306+
307+
Object key = SimpleKeyGenerator.generateKey(Collections.singletonList("my-cache-key-in-a-list"));
308+
cache.put(key, sample);
309+
310+
Object target = cache.get(SimpleKeyGenerator.generateKey(Collections.singletonList("my-cache-key-in-a-list")));
311+
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
312+
}
313+
314+
@Test // DATAREDIS-1032
315+
public void cacheShouldAllowArrayKeyCacheKeysOfSimpleTypes() {
316+
317+
Object key = SimpleKeyGenerator.generateKey(new String[] { "my-cache-key-in-an-array" });
318+
cache.put(key, sample);
319+
320+
Object target = cache.get(SimpleKeyGenerator.generateKey(new String[] { "my-cache-key-in-an-array" }));
321+
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
322+
}
323+
324+
@Test // DATAREDIS-1032
325+
public void cacheShouldAllowListCacheKeysOfComplexTypes() {
326+
327+
Object key = SimpleKeyGenerator
328+
.generateKey(Collections.singletonList(new ComplexKey(sample.getFirstame(), sample.getBirthdate())));
329+
cache.put(key, sample);
330+
331+
Object target = cache.get(SimpleKeyGenerator
332+
.generateKey(Collections.singletonList(new ComplexKey(sample.getFirstame(), sample.getBirthdate()))));
333+
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
334+
}
335+
336+
@Test // DATAREDIS-1032
337+
public void cacheShouldAllowMapCacheKeys() {
338+
339+
Object key = SimpleKeyGenerator
340+
.generateKey(Collections.singletonMap("map-key", new ComplexKey(sample.getFirstame(), sample.getBirthdate())));
341+
cache.put(key, sample);
342+
343+
Object target = cache.get(SimpleKeyGenerator
344+
.generateKey(Collections.singletonMap("map-key", new ComplexKey(sample.getFirstame(), sample.getBirthdate()))));
345+
assertThat(((ValueWrapper) target).get()).isEqualTo(sample);
346+
}
347+
348+
@Test // DATAREDIS-1032
349+
public void cacheShouldFailOnNonConvertibleCacheKey() {
350+
351+
Object key = SimpleKeyGenerator
352+
.generateKey(Collections.singletonList(new InvalidKey(sample.getFirstame(), sample.getBirthdate())));
353+
assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> cache.put(key, sample));
354+
}
355+
303356
void doWithConnection(Consumer<RedisConnection> callback) {
304357
RedisConnection connection = connectionFactory.getConnection();
305358
try {

0 commit comments

Comments
 (0)