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