1
1
package com .redis .om .spring .repository .support ;
2
2
3
+ import java .lang .reflect .Field ;
4
+ import java .lang .reflect .Method ;
5
+ import java .time .LocalDate ;
6
+ import java .time .LocalDateTime ;
3
7
import java .util .ArrayList ;
8
+ import java .util .Date ;
4
9
import java .util .List ;
10
+ import java .util .concurrent .TimeUnit ;
5
11
import java .util .stream .Collectors ;
6
12
import java .util .stream .StreamSupport ;
7
13
14
+ import com .google .common .base .Optional ;
8
15
import com .google .gson .Gson ;
9
16
import com .google .gson .GsonBuilder ;
10
17
import com .redis .om .spring .convert .MappingRedisOMConverter ;
11
18
import com .redis .om .spring .id .ULIDIdentifierGenerator ;
12
19
import com .redis .om .spring .serialization .gson .GsonBuidlerFactory ;
20
+ import com .redis .om .spring .util .ObjectUtils ;
13
21
import org .apache .commons .lang3 .StringUtils ;
22
+ import org .springframework .beans .PropertyAccessor ;
23
+ import org .springframework .beans .PropertyAccessorFactory ;
14
24
import org .springframework .beans .factory .annotation .Qualifier ;
25
+ import org .springframework .data .annotation .CreatedDate ;
26
+ import org .springframework .data .annotation .LastModifiedDate ;
15
27
import org .springframework .data .domain .Page ;
16
28
import org .springframework .data .domain .PageImpl ;
17
29
import org .springframework .data .domain .Pageable ;
20
32
import org .springframework .data .keyvalue .repository .support .SimpleKeyValueRepository ;
21
33
import org .springframework .data .redis .core .RedisTemplate ;
22
34
import org .springframework .data .redis .core .SetOperations ;
35
+ import org .springframework .data .redis .core .TimeToLive ;
36
+ import org .springframework .data .redis .core .convert .KeyspaceConfiguration ;
23
37
import org .springframework .data .redis .core .convert .RedisData ;
24
38
import org .springframework .data .redis .core .convert .ReferenceResolverImpl ;
39
+ import org .springframework .data .redis .core .mapping .RedisMappingContext ;
25
40
import org .springframework .data .repository .core .EntityInformation ;
26
41
27
42
import com .redis .om .spring .KeyspaceToIndexMap ;
31
46
import com .redislabs .modules .rejson .Path ;
32
47
import org .springframework .util .Assert ;
33
48
import org .springframework .util .ClassUtils ;
49
+ import org .springframework .util .ReflectionUtils ;
34
50
import redis .clients .jedis .Jedis ;
35
51
import redis .clients .jedis .Pipeline ;
36
52
import redis .clients .jedis .commands .ProtocolCommand ;
@@ -47,11 +63,14 @@ public class SimpleRedisDocumentRepository<T, ID> extends SimpleKeyValueReposito
47
63
protected MappingRedisOMConverter mappingConverter ;
48
64
private final ULIDIdentifierGenerator generator ;
49
65
66
+ private RedisMappingContext mappingContext ;
67
+
50
68
@ SuppressWarnings ("unchecked" )
51
69
public SimpleRedisDocumentRepository (EntityInformation <T , ID > metadata , //
52
70
KeyValueOperations operations , //
53
71
@ Qualifier ("redisModulesOperations" ) RedisModulesOperations <?> rmo , //
54
- KeyspaceToIndexMap keyspaceToIndexMap ) {
72
+ KeyspaceToIndexMap keyspaceToIndexMap , //
73
+ RedisMappingContext mappingContext ) {
55
74
super (metadata , operations );
56
75
this .modulesOperations = (RedisModulesOperations <String >)rmo ;
57
76
this .metadata = metadata ;
@@ -61,6 +80,7 @@ public SimpleRedisDocumentRepository(EntityInformation<T, ID> metadata, //
61
80
new ReferenceResolverImpl (modulesOperations .getTemplate ()));
62
81
this .generator = ULIDIdentifierGenerator .INSTANCE ;
63
82
this .gson = this .gsonBuilder .create ();
83
+ this .mappingContext = mappingContext ;
64
84
}
65
85
66
86
@ Override
@@ -124,13 +144,20 @@ public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
124
144
Pipeline pipeline = jedis .pipelined ();
125
145
126
146
for (S entity : entities ) {
147
+ boolean isNew = metadata .isNew (entity );
148
+
127
149
KeyValuePersistentEntity <?, ?> keyValueEntity = mappingConverter .getMappingContext ().getRequiredPersistentEntity (ClassUtils .getUserClass (entity ));
128
- Object id = metadata . isNew ( entity ) ? generator .generateIdentifierOfType (keyValueEntity .getIdProperty ().getTypeInformation ()) : (String ) keyValueEntity .getPropertyAccessor (entity ).getProperty (keyValueEntity .getIdProperty ());
150
+ Object id = isNew ? generator .generateIdentifierOfType (keyValueEntity .getIdProperty ().getTypeInformation ()) : (String ) keyValueEntity .getPropertyAccessor (entity ).getProperty (keyValueEntity .getIdProperty ());
129
151
keyValueEntity .getPropertyAccessor (entity ).setProperty (keyValueEntity .getIdProperty (), id );
130
152
153
+ String keyspace = keyValueEntity .getKeySpace ();
154
+ byte [] objectKey = createKey (keyspace , id .toString ());
155
+
156
+ processAuditAnnotations (objectKey , entity , isNew );
157
+ Optional <Long > maybeTtl = getTTLForEntity (entity );
158
+
131
159
RedisData rdo = new RedisData ();
132
160
mappingConverter .write (entity , rdo );
133
- byte [] objectKey = createKey (rdo .getKeyspace (), id .toString ());
134
161
135
162
pipeline .sadd (rdo .getKeyspace (), id .toString ());
136
163
@@ -140,6 +167,10 @@ public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
140
167
args .add (SafeEncoder .encode (this .gson .toJson (entity )));
141
168
pipeline .sendCommand (Command .SET , args .toArray (new byte [args .size ()][]));
142
169
170
+ if (maybeTtl .isPresent ()) {
171
+ pipeline .expire (objectKey , maybeTtl .get ());
172
+ }
173
+
143
174
saved .add (entity );
144
175
}
145
176
pipeline .sync ();
@@ -160,6 +191,60 @@ public byte[] createKey(String keyspace, String id) {
160
191
return this .mappingConverter .toBytes (keyspace + ":" + id );
161
192
}
162
193
194
+ private boolean expires (RedisData data ) {
195
+ return data .getTimeToLive () != null && data .getTimeToLive () > 0L ;
196
+ }
197
+
198
+ private void processAuditAnnotations (byte [] redisKey , Object item , boolean isNew ) {
199
+ var auditClass = isNew ? CreatedDate .class : LastModifiedDate .class ;
200
+
201
+ List <Field > fields = com .redis .om .spring .util .ObjectUtils .getFieldsWithAnnotation (item .getClass (), auditClass );
202
+ if (!fields .isEmpty ()) {
203
+ PropertyAccessor accessor = PropertyAccessorFactory .forBeanPropertyAccess (item );
204
+ fields .forEach (f -> {
205
+ if (f .getType () == Date .class ) {
206
+ accessor .setPropertyValue (f .getName (), new Date (System .currentTimeMillis ()));
207
+ } else if (f .getType () == LocalDateTime .class ) {
208
+ accessor .setPropertyValue (f .getName (), LocalDateTime .now ());
209
+ } else if (f .getType () == LocalDate .class ) {
210
+ accessor .setPropertyValue (f .getName (), LocalDate .now ());
211
+ }
212
+ });
213
+ }
214
+ }
215
+
216
+ private Optional <Long > getTTLForEntity (Object entity ) {
217
+ KeyspaceConfiguration keyspaceConfig = mappingContext .getMappingConfiguration ().getKeyspaceConfiguration ();
218
+ if (keyspaceConfig .hasSettingsFor (entity .getClass ())) {
219
+ var settings = keyspaceConfig .getKeyspaceSettings (entity .getClass ());
220
+
221
+ if (org .springframework .util .StringUtils .hasText (settings .getTimeToLivePropertyName ())) {
222
+ Method ttlGetter ;
223
+ try {
224
+ Field fld = ReflectionUtils .findField (entity .getClass (), settings .getTimeToLivePropertyName ());
225
+ ttlGetter = ObjectUtils .getGetterForField (entity .getClass (), fld );
226
+ Long ttlPropertyValue = ((Number ) ReflectionUtils .invokeMethod (ttlGetter , entity )).longValue ();
227
+
228
+ ReflectionUtils .invokeMethod (ttlGetter , entity );
229
+
230
+ if (ttlPropertyValue != null ) {
231
+ TimeToLive ttl = fld .getAnnotation (TimeToLive .class );
232
+ if (!ttl .unit ().equals (TimeUnit .SECONDS )) {
233
+ return Optional .of (TimeUnit .SECONDS .convert (ttlPropertyValue , ttl .unit ()));
234
+ } else {
235
+ return Optional .of (ttlPropertyValue );
236
+ }
237
+ }
238
+ } catch (SecurityException | IllegalArgumentException e ) {
239
+ return Optional .absent ();
240
+ }
241
+ } else if (settings != null && settings .getTimeToLive () != null && settings .getTimeToLive () > 0 ) {
242
+ return Optional .of (settings .getTimeToLive ());
243
+ }
244
+ }
245
+ return Optional .absent ();
246
+ }
247
+
163
248
private enum Command implements ProtocolCommand {
164
249
SET ("JSON.SET" );
165
250
0 commit comments