16
16
package org .springframework .data .redis .core .convert ;
17
17
18
18
import lombok .RequiredArgsConstructor ;
19
+ import lombok .AccessLevel ;
20
+ import lombok .AllArgsConstructor ;
21
+ import lombok .Getter ;
19
22
20
23
import java .lang .reflect .Array ;
21
24
import java .util .*;
52
55
import org .springframework .data .redis .core .mapping .RedisMappingContext ;
53
56
import org .springframework .data .redis .core .mapping .RedisPersistentEntity ;
54
57
import org .springframework .data .redis .core .mapping .RedisPersistentProperty ;
58
+ import org .springframework .data .redis .util .ByteUtils ;
55
59
import org .springframework .data .util .ClassTypeInformation ;
56
60
import org .springframework .data .util .TypeInformation ;
57
61
import org .springframework .lang .Nullable ;
@@ -308,9 +312,14 @@ private void readAssociation(String path, RedisData source, RedisPersistentEntit
308
312
for (Entry <String , byte []> entry : bucket .entrySet ()) {
309
313
310
314
String referenceKey = fromBytes (entry .getValue (), String .class );
311
- String [] args = referenceKey .split (":" );
312
315
313
- Map <byte [], byte []> rawHash = referenceResolver .resolveReference (args [1 ], args [0 ]);
316
+ if (!KeyspaceIdentifier .isValid (referenceKey )) {
317
+ continue ;
318
+ }
319
+
320
+ KeyspaceIdentifier identifier = KeyspaceIdentifier .of (referenceKey );
321
+ Map <byte [], byte []> rawHash = referenceResolver .resolveReference (identifier .getId (),
322
+ identifier .getKeyspace ());
314
323
315
324
if (!CollectionUtils .isEmpty (rawHash )) {
316
325
target .add (read (association .getInverse ().getActualType (), new RedisData (rawHash )));
@@ -326,15 +335,18 @@ private void readAssociation(String path, RedisData source, RedisPersistentEntit
326
335
return ;
327
336
}
328
337
329
- String key = fromBytes (binKey , String .class );
338
+ String referenceKey = fromBytes (binKey , String .class );
339
+ if (KeyspaceIdentifier .isValid (referenceKey )) {
330
340
331
- String [] args = key . split ( ":" );
341
+ KeyspaceIdentifier identifier = KeyspaceIdentifier . of ( referenceKey );
332
342
333
- Map <byte [], byte []> rawHash = referenceResolver .resolveReference (args [1 ], args [0 ]);
343
+ Map <byte [], byte []> rawHash = referenceResolver .resolveReference (identifier .getId (),
344
+ identifier .getKeyspace ());
334
345
335
- if (!CollectionUtils .isEmpty (rawHash )) {
336
- accessor .setProperty (association .getInverse (),
337
- read (association .getInverse ().getActualType (), new RedisData (rawHash )));
346
+ if (!CollectionUtils .isEmpty (rawHash )) {
347
+ accessor .setProperty (association .getInverse (),
348
+ read (association .getInverse ().getActualType (), new RedisData (rawHash )));
349
+ }
338
350
}
339
351
}
340
352
});
@@ -434,9 +446,10 @@ private void writePartialPropertyUpdate(PartialUpdate<?> update, PropertyUpdate
434
446
targetProperty = getTargetPropertyOrNullForPath (path .replaceAll ("\\ .\\ [.*\\ ]" , "" ), update .getTarget ());
435
447
436
448
TypeInformation <?> ti = targetProperty == null ? ClassTypeInformation .OBJECT
437
- : (targetProperty .isMap () ? (targetProperty .getTypeInformation ().getMapValueType () != null
438
- ? targetProperty .getTypeInformation ().getRequiredMapValueType ()
439
- : ClassTypeInformation .OBJECT ) : targetProperty .getTypeInformation ().getActualType ());
449
+ : (targetProperty .isMap ()
450
+ ? (targetProperty .getTypeInformation ().getMapValueType () != null
451
+ ? targetProperty .getTypeInformation ().getRequiredMapValueType () : ClassTypeInformation .OBJECT )
452
+ : targetProperty .getTypeInformation ().getActualType ());
440
453
441
454
writeInternal (entity .getKeySpace (), pUpdate .getPropertyPath (), pUpdate .getValue (), ti , sink );
442
455
return ;
@@ -1160,4 +1173,177 @@ public int compareTo(Part that) {
1160
1173
}
1161
1174
}
1162
1175
1176
+ /**
1177
+ * Value object representing a Redis Hash/Object identifier composed from keyspace and object id in the form of
1178
+ * {@literal keyspace:id}.
1179
+ *
1180
+ * @author Mark Paluch
1181
+ * @since 1.8.10
1182
+ */
1183
+ @ AllArgsConstructor (access = AccessLevel .PRIVATE )
1184
+ @ Getter
1185
+ public static class KeyspaceIdentifier {
1186
+
1187
+ public static final String PHANTOM = "phantom" ;
1188
+ public static final String DELIMITTER = ":" ;
1189
+ public static final String PHANTOM_SUFFIX = DELIMITTER + PHANTOM ;
1190
+
1191
+ private String keyspace ;
1192
+ private String id ;
1193
+ private boolean phantomKey ;
1194
+
1195
+ /**
1196
+ * Parse a {@code key} into {@link KeyspaceIdentifier}.
1197
+ *
1198
+ * @param key the key representation.
1199
+ * @return {@link BinaryKeyspaceIdentifier} for binary key.
1200
+ */
1201
+ public static KeyspaceIdentifier of (String key ) {
1202
+
1203
+ Assert .isTrue (isValid (key ), String .format ("Invalid key %s" , key ));
1204
+
1205
+ boolean phantomKey = key .endsWith (PHANTOM_SUFFIX );
1206
+ int keyspaceEndIndex = key .indexOf (DELIMITTER );
1207
+ String keyspace = key .substring (0 , keyspaceEndIndex );
1208
+ String id ;
1209
+
1210
+ if (phantomKey ) {
1211
+ id = key .substring (keyspaceEndIndex + 1 , key .length () - PHANTOM_SUFFIX .length ());
1212
+ } else {
1213
+ id = key .substring (keyspaceEndIndex + 1 );
1214
+ }
1215
+
1216
+ return new KeyspaceIdentifier (keyspace , id , phantomKey );
1217
+ }
1218
+
1219
+ /**
1220
+ * Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the
1221
+ * form of {@literal keyspace:id}.
1222
+ *
1223
+ * @param key the key.
1224
+ * @return {@literal true} if the key is valid.
1225
+ */
1226
+ public static boolean isValid (String key ) {
1227
+
1228
+ if (key == null ) {
1229
+ return false ;
1230
+ }
1231
+
1232
+ int keyspaceEndIndex = key .indexOf (DELIMITTER );
1233
+
1234
+ return keyspaceEndIndex > 0 && key .length () > keyspaceEndIndex ;
1235
+ }
1236
+ }
1237
+
1238
+ /**
1239
+ * Value object representing a binary Redis Hash/Object identifier composed from keyspace and object id in the form of
1240
+ * {@literal keyspace:id}.
1241
+ *
1242
+ * @author Mark Paluch
1243
+ * @since 1.8.10
1244
+ */
1245
+ @ AllArgsConstructor (access = AccessLevel .PRIVATE )
1246
+ @ Getter
1247
+ public static class BinaryKeyspaceIdentifier {
1248
+
1249
+ public static final byte [] PHANTOM = KeyspaceIdentifier .PHANTOM .getBytes ();
1250
+ public static final byte DELIMITTER = ':' ;
1251
+ public static final byte [] PHANTOM_SUFFIX = ByteUtils .concat (new byte [] { DELIMITTER }, PHANTOM );
1252
+
1253
+ private byte [] keyspace ;
1254
+ private byte [] id ;
1255
+ private boolean phantomKey ;
1256
+
1257
+ /**
1258
+ * Parse a binary {@code key} into {@link BinaryKeyspaceIdentifier}.
1259
+ *
1260
+ * @param key the binary key representation.
1261
+ * @return {@link BinaryKeyspaceIdentifier} for binary key.
1262
+ */
1263
+ public static BinaryKeyspaceIdentifier of (byte [] key ) {
1264
+
1265
+ Assert .isTrue (isValid (key ), String .format ("Invalid key %s" , new String (key )));
1266
+
1267
+ boolean phantomKey = startsWith (key , PHANTOM_SUFFIX , key .length - PHANTOM_SUFFIX .length );
1268
+
1269
+ int keyspaceEndIndex = find (key , DELIMITTER );
1270
+ byte [] keyspace = getKeyspace (key , keyspaceEndIndex );
1271
+ byte [] id = getId (key , phantomKey , keyspaceEndIndex );
1272
+
1273
+ return new BinaryKeyspaceIdentifier (keyspace , id , phantomKey );
1274
+ }
1275
+
1276
+ /**
1277
+ * Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the
1278
+ * form of {@literal keyspace:id}.
1279
+ *
1280
+ * @param key the key.
1281
+ * @return {@literal true} if the key is valid.
1282
+ */
1283
+ public static boolean isValid (byte [] key ) {
1284
+
1285
+ if (key == null ) {
1286
+ return false ;
1287
+ }
1288
+
1289
+ int keyspaceEndIndex = find (key , DELIMITTER );
1290
+
1291
+ return keyspaceEndIndex > 0 && key .length > keyspaceEndIndex ;
1292
+ }
1293
+
1294
+ private static byte [] getId (byte [] key , boolean phantomKey , int keyspaceEndIndex ) {
1295
+
1296
+ int idSize ;
1297
+
1298
+ if (phantomKey ) {
1299
+ idSize = (key .length - PHANTOM_SUFFIX .length ) - (keyspaceEndIndex + 1 );
1300
+ } else {
1301
+
1302
+ idSize = key .length - (keyspaceEndIndex + 1 );
1303
+ }
1304
+
1305
+ byte [] id = new byte [idSize ];
1306
+ System .arraycopy (key , keyspaceEndIndex + 1 , id , 0 , idSize );
1307
+
1308
+ return id ;
1309
+ }
1310
+
1311
+ private static byte [] getKeyspace (byte [] key , int keyspaceEndIndex ) {
1312
+
1313
+ byte [] keyspace = new byte [keyspaceEndIndex ];
1314
+ System .arraycopy (key , 0 , keyspace , 0 , keyspaceEndIndex );
1315
+
1316
+ return keyspace ;
1317
+ }
1318
+
1319
+ private static boolean startsWith (byte [] haystack , byte [] prefix , int offset ) {
1320
+
1321
+ int to = offset ;
1322
+ int prefixOffset = 0 ;
1323
+ int prefixLength = prefix .length ;
1324
+
1325
+ if ((offset < 0 ) || (offset > haystack .length - prefixLength )) {
1326
+ return false ;
1327
+ }
1328
+
1329
+ while (--prefixLength >= 0 ) {
1330
+ if (haystack [to ++] != prefix [prefixOffset ++]) {
1331
+ return false ;
1332
+ }
1333
+ }
1334
+
1335
+ return true ;
1336
+ }
1337
+
1338
+ private static int find (byte [] haystack , byte needle ) {
1339
+
1340
+ for (int i = 0 ; i < haystack .length ; i ++) {
1341
+ if (haystack [i ] == needle ) {
1342
+ return i ;
1343
+ }
1344
+ }
1345
+
1346
+ return -1 ;
1347
+ }
1348
+ }
1163
1349
}
0 commit comments