Skip to content

Commit d32c804

Browse files
DATAREDIS-471 - Hacking (TTL, Converter, Adapter).
Delegate ttl resolution to resolver. Move parts of the logic to the mapping redis converter. Only leave delete and persistence handling up to the adapter.
1 parent 3595097 commit d32c804

16 files changed

+382
-88
lines changed

src/main/java/org/springframework/data/redis/core/IndexWriter.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.data.redis.connection.RedisConnection;
2222
import org.springframework.data.redis.core.convert.IndexedData;
2323
import org.springframework.data.redis.core.convert.RedisConverter;
24+
import org.springframework.data.redis.core.convert.RemoveIndexedData;
2425
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
2526
import org.springframework.data.redis.util.ByteUtils;
2627
import org.springframework.util.Assert;
@@ -123,8 +124,8 @@ private void removeKeyFromExistingIndexes(byte[] key, Iterable<IndexedData> inde
123124
protected void removeKeyFromExistingIndexes(byte[] key, IndexedData indexedData) {
124125

125126
Assert.notNull(indexedData, "IndexedData must not be null!");
126-
Set<byte[]> existingKeys = connection.keys(toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName()
127-
+ ":*"));
127+
Set<byte[]> existingKeys = connection
128+
.keys(toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName() + ":*"));
128129

129130
if (!CollectionUtils.isEmpty(existingKeys)) {
130131
for (byte[] existingKey : existingKeys) {
@@ -151,7 +152,11 @@ protected void addKeyToIndex(byte[] key, IndexedData indexedData) {
151152
Assert.notNull(key, "Key must not be null!");
152153
Assert.notNull(indexedData, "IndexedData must not be null!");
153154

154-
if (indexedData instanceof SimpleIndexedPropertyValue) {
155+
if (indexedData instanceof RemoveIndexedData) {
156+
return;
157+
}
158+
159+
else if (indexedData instanceof SimpleIndexedPropertyValue) {
155160

156161
Object value = ((SimpleIndexedPropertyValue) indexedData).getValue();
157162

@@ -166,8 +171,8 @@ protected void addKeyToIndex(byte[] key, IndexedData indexedData) {
166171
// keep track of indexes used for the object
167172
connection.sAdd(ByteUtils.concatAll(toBytes(indexedData.getKeyspace() + ":"), key, toBytes(":idx")), indexKey);
168173
} else {
169-
throw new IllegalArgumentException(String.format("Cannot write index data for unknown index type %s",
170-
indexedData.getClass()));
174+
throw new IllegalArgumentException(
175+
String.format("Cannot write index data for unknown index type %s", indexedData.getClass()));
171176
}
172177
}
173178

@@ -185,10 +190,8 @@ private byte[] toBytes(Object source) {
185190
return converter.getConversionService().convert(source, byte[].class);
186191
}
187192

188-
throw new InvalidDataAccessApiUsageException(
189-
String
190-
.format(
191-
"Cannot convert %s to binary representation for index key generation. Are you missing a Converter? Did you register a non PathBasedRedisIndexDefinition that might apply to a complex type?",
192-
source.getClass()));
193+
throw new InvalidDataAccessApiUsageException(String.format(
194+
"Cannot convert %s to binary representation for index key generation. Are you missing a Converter? Did you register a non PathBasedRedisIndexDefinition that might apply to a complex type?",
195+
source.getClass()));
193196
}
194197
}

src/main/java/org/springframework/data/redis/core/PartialUpdate.java

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import java.util.Collections;
2020
import java.util.List;
2121

22+
import org.springframework.util.Assert;
23+
import org.springframework.util.ClassUtils;
24+
2225
/**
2326
* @author Christoph Strobl
2427
* @param <T>
@@ -27,44 +30,79 @@ public class PartialUpdate<T> {
2730

2831
private final Object id;
2932
private final Class<T> target;
30-
private T value;
33+
private final T value;
3134
private boolean refreshTtl = false;
3235

3336
private final List<PropertyUpdate> propertyUpdates = new ArrayList<PropertyUpdate>();
3437

35-
public PartialUpdate(Object id, Class<T> type) {
38+
private PartialUpdate(Object id, Class<T> target, T value, boolean refreshTtl, List<PropertyUpdate> propertyUpdates) {
3639

3740
this.id = id;
38-
this.target = type;
41+
this.target = target;
42+
this.value = value;
43+
this.refreshTtl = refreshTtl;
44+
this.propertyUpdates.addAll(propertyUpdates);
3945
}
4046

41-
public PartialUpdate(Object id, T value) {
47+
/**
48+
* @param id must not be {@literal null}.
49+
* @param target must not be {@literal null}.
50+
*/
51+
@SuppressWarnings("unchecked")
52+
public PartialUpdate(Object id, Class<T> target) {
53+
54+
Assert.notNull(id, "Id must not be null!");
55+
Assert.notNull(target, "Target must not be null!");
4256

4357
this.id = id;
44-
this.target = (Class<T>) value.getClass();
45-
this.value = value;
58+
this.target = (Class<T>) ClassUtils.getUserClass(target);
59+
this.value = null;
4660
}
4761

48-
public void setValue(T value) {
62+
/**
63+
* @param id must not be {@literal null}.
64+
* @param value must not be {@literal null}.
65+
*/
66+
@SuppressWarnings("unchecked")
67+
public PartialUpdate(Object id, T value) {
68+
69+
Assert.notNull(id, "Id must not be null!");
70+
Assert.notNull(value, "Value must not be null!");
71+
72+
this.id = id;
73+
this.target = (Class<T>) ClassUtils.getUserClass(value.getClass());
4974
this.value = value;
5075
}
5176

77+
/**
78+
* @return can be {@literal null}.
79+
*/
5280
public T getValue() {
5381
return value;
5482
}
5583

56-
public void set(String path, Object value) {
84+
public PartialUpdate<T> set(String path, Object value) {
85+
5786
propertyUpdates.add(new PropertyUpdate(UpdateCommand.SET, path, value));
87+
return new PartialUpdate<T>(this.id, this.target, this.value, this.refreshTtl, this.propertyUpdates);
5888
}
5989

60-
public void del(String path) {
61-
propertyUpdates.add(new PropertyUpdate(UpdateCommand.DEL, path, null));
90+
public PartialUpdate<T> del(String path) {
91+
92+
propertyUpdates.add(new PropertyUpdate(UpdateCommand.DEL, path));
93+
return new PartialUpdate<T>(this.id, this.target, this.value, this.refreshTtl, this.propertyUpdates);
6294
}
6395

96+
/**
97+
* @return never {@literal null}.
98+
*/
6499
public Class<T> getTarget() {
65100
return target;
66101
}
67102

103+
/**
104+
* @return never {@literal null}.
105+
*/
68106
public Object getId() {
69107
return id;
70108
}
@@ -77,21 +115,26 @@ public boolean isRefreshTtl() {
77115
return refreshTtl;
78116
}
79117

80-
public void setRefreshTtl(boolean refreshTtl) {
81-
this.refreshTtl = refreshTtl;
82-
}
118+
public PartialUpdate<T> refreshTtl(boolean refreshTtl) {
83119

84-
static enum UpdateCommand {
85-
SET, DEL
120+
this.refreshTtl = refreshTtl;
121+
return new PartialUpdate<T>(this.id, this.target, this.value, this.refreshTtl, this.propertyUpdates);
86122
}
87123

88-
static class PropertyUpdate {
124+
/**
125+
* @author Christoph Strobl
126+
*/
127+
public static class PropertyUpdate {
89128

90129
private final UpdateCommand cmd;
91130
private final String propertyPath;
92131
private final Object value;
93132

94-
public PropertyUpdate(UpdateCommand cmd, String propertyPath, Object value) {
133+
private PropertyUpdate(UpdateCommand cmd, String propertyPath) {
134+
this(cmd, propertyPath, null);
135+
}
136+
137+
private PropertyUpdate(UpdateCommand cmd, String propertyPath, Object value) {
95138

96139
this.cmd = cmd;
97140
this.propertyPath = propertyPath;
@@ -109,7 +152,13 @@ public String getPropertyPath() {
109152
public Object getValue() {
110153
return value;
111154
}
155+
}
112156

157+
/**
158+
* @author Christoph Strobl
159+
*/
160+
public static enum UpdateCommand {
161+
SET, DEL
113162
}
114163

115164
}

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import org.springframework.data.redis.core.convert.RedisConverter;
4949
import org.springframework.data.redis.core.convert.RedisData;
5050
import org.springframework.data.redis.core.convert.ReferenceResolverImpl;
51-
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
5251
import org.springframework.data.redis.core.mapping.RedisMappingContext;
5352
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
5453
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
@@ -407,8 +406,7 @@ public <T> T update(final PartialUpdate<T> update) {
407406
final byte[] redisKey = createKey(keyspace, converter.getConversionService().convert(id, String.class));
408407

409408
final RedisData rdo = new RedisData();
410-
this.converter.write(update.getValue(), rdo);
411-
rdo.getBucket().put("_class", null); // overwrite stuff in here
409+
this.converter.write(update, rdo);
412410

413411
redisOps.execute(new RedisCallback<Void>() {
414412

@@ -420,20 +418,10 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
420418
for (PropertyUpdate pUpdate : update.getPropertyUpdates()) {
421419

422420
String propertyPath = pUpdate.getPropertyPath();
423-
byte[] existingValue = connection.hGet(redisKey, toBytes(propertyPath));
424421

425-
if (UpdateCommand.SET.equals(pUpdate.getCmd())) {
426-
427-
rdo.getBucket().put(propertyPath, toBytes(pUpdate.getValue()));
428-
429-
if (converter.getIndexDefinitionProvider().hasIndexFor(keyspace, propertyPath) || connection
430-
.keys(ByteUtils.concatAll(toBytes(keyspace), (":" + propertyPath).getBytes(), "*".getBytes()))
431-
.size() > 0) {
432-
rdo.addIndexedData(new SimpleIndexedPropertyValue(keyspace, propertyPath, pUpdate.getValue()));
433-
}
434-
435-
} else if (UpdateCommand.DEL.equals(pUpdate.getCmd())) {
422+
if (UpdateCommand.DEL.equals(pUpdate.getCmd())) {
436423

424+
byte[] existingValue = connection.hGet(redisKey, toBytes(propertyPath));
437425
pathsToRemove.add(toBytes(propertyPath));
438426

439427
byte[] existingValueIndexKey = existingValue != null
@@ -454,22 +442,28 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
454442
}
455443

456444
if (!rdo.getBucket().isEmpty()) {
457-
458-
if (rdo.getBucket().size() == 1 && rdo.getBucket().asMap().containsKey("_class")) {
459-
// ignore
460-
} else {
445+
if (rdo.getBucket().size() > 1
446+
|| (rdo.getBucket().size() == 1 && !rdo.getBucket().asMap().containsKey("_class"))) {
461447
connection.hMSet(redisKey, rdo.getBucket().rawMap());
462448
}
463449
}
464450

465-
if (update.isRefreshTtl() && rdo.getTimeToLive() != null && rdo.getTimeToLive().longValue() > 0) {
451+
if (update.isRefreshTtl()) {
466452

467-
connection.expire(redisKey, rdo.getTimeToLive().longValue());
453+
if (rdo.getTimeToLive() != null && rdo.getTimeToLive().longValue() > 0) {
468454

469-
// add phantom key so values can be restored
470-
byte[] phantomKey = ByteUtils.concat(redisKey, toBytes(":phantom"));
471-
connection.hMSet(phantomKey, rdo.getBucket().rawMap());
472-
connection.expire(phantomKey, rdo.getTimeToLive().longValue() + 300);
455+
connection.expire(redisKey, rdo.getTimeToLive().longValue());
456+
457+
// add phantom key so values can be restored
458+
byte[] phantomKey = ByteUtils.concat(redisKey, toBytes(":phantom"));
459+
connection.hMSet(phantomKey, rdo.getBucket().rawMap());
460+
connection.expire(phantomKey, rdo.getTimeToLive().longValue() + 300);
461+
462+
} else {
463+
464+
connection.persist(redisKey);
465+
connection.persist(ByteUtils.concat(redisKey, toBytes(":phantom")));
466+
}
473467
}
474468

475469
new IndexWriter(connection, converter).updateIndexes(toBytes(id), rdo.getIndexedData());

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,15 @@ public Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Ob
7272
return data;
7373
}
7474

75+
@Override
76+
public Set<IndexedData> resolveIndexesFor(String keyspace, String path, TypeInformation<?> typeInformation,
77+
Object value) {
78+
79+
Set<IndexedData> data = new LinkedHashSet<IndexedData>();
80+
for (IndexResolver resolver : resolvers) {
81+
data.addAll(resolver.resolveIndexesFor(keyspace, path, typeInformation, value));
82+
}
83+
return data;
84+
}
85+
7586
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ public interface IndexResolver {
3838
*/
3939
Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Object value);
4040

41+
Set<IndexedData> resolveIndexesFor(String keyspace, String path, TypeInformation<?> typeInformation, Object value);
42+
4143
}

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

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@
4545
import org.springframework.data.mapping.PersistentPropertyAccessor;
4646
import org.springframework.data.mapping.PreferredConstructor;
4747
import org.springframework.data.mapping.PropertyHandler;
48+
import org.springframework.data.mapping.context.PersistentPropertyPath;
4849
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
4950
import org.springframework.data.mapping.model.PropertyValueProvider;
50-
import org.springframework.data.redis.core.index.IndexDefinitionProvider;
51+
import org.springframework.data.redis.core.PartialUpdate;
52+
import org.springframework.data.redis.core.PartialUpdate.PropertyUpdate;
53+
import org.springframework.data.redis.core.PartialUpdate.UpdateCommand;
5154
import org.springframework.data.redis.core.index.Indexed;
5255
import org.springframework.data.redis.core.mapping.RedisMappingContext;
5356
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
@@ -344,6 +347,11 @@ public void write(Object source, final RedisData sink) {
344347
return;
345348
}
346349

350+
if (source instanceof PartialUpdate) {
351+
writePartialUpdate((PartialUpdate) source, sink);
352+
return;
353+
}
354+
347355
final RedisPersistentEntity entity = mappingContext.getPersistentEntity(source.getClass());
348356

349357
if (!customConversions.hasCustomWriteTarget(source.getClass())) {
@@ -362,7 +370,41 @@ public void write(Object source, final RedisData sink) {
362370
for (IndexedData indexeData : indexResolver.resolveIndexesFor(entity.getTypeInformation(), source)) {
363371
sink.addIndexedData(indexeData);
364372
}
373+
}
374+
375+
protected void writePartialUpdate(PartialUpdate<?> update, RedisData sink) {
376+
377+
RedisPersistentEntity<?> entity = mappingContext.getPersistentEntity(update.getTarget());
378+
379+
write(update.getValue(), sink);
380+
if (sink.getBucket().keySet().contains("_class")) {
381+
sink.getBucket().put("_class", null); // overwrite stuff in here
382+
}
383+
384+
if (update.isRefreshTtl() && !update.getPropertyUpdates().isEmpty()) {
385+
386+
Long ttl = entity.getTimeToLiveAccessor().getTimeToLive(update);
387+
if (ttl != null && ttl > 0) {
388+
sink.setTimeToLive(ttl);
389+
}
390+
}
391+
392+
for (PropertyUpdate pUpdate : update.getPropertyUpdates()) {
393+
394+
PersistentPropertyPath<KeyValuePersistentProperty> persistentPropertyPath = mappingContext
395+
.getPersistentPropertyPath(pUpdate.getPropertyPath(), update.getTarget());
396+
397+
if (UpdateCommand.SET.equals(pUpdate.getCmd())) {
398+
399+
Set<IndexedData> data = indexResolver.resolveIndexesFor(entity.getKeySpace(), pUpdate.getPropertyPath(),
400+
persistentPropertyPath.getLeafProperty().getTypeInformation(), pUpdate.getValue());
401+
402+
writeInternal(entity.getKeySpace(), pUpdate.getPropertyPath(), pUpdate.getValue(),
403+
persistentPropertyPath.getLeafProperty().getTypeInformation(), sink);
365404

405+
sink.addIndexedData(data);
406+
}
407+
}
366408
}
367409

368410
/**
@@ -756,11 +798,6 @@ public void afterPropertiesSet() {
756798
this.initializeConverters();
757799
}
758800

759-
@Override
760-
public IndexDefinitionProvider getIndexDefinitionProvider() {
761-
return this.mappingContext.getMappingConfiguration().getIndexConfiguration();
762-
}
763-
764801
private void initializeConverters() {
765802
customConversions.registerConvertersIn(conversionService);
766803
}

0 commit comments

Comments
 (0)