19
19
import io .r2dbc .spi .Row ;
20
20
import io .r2dbc .spi .RowMetadata ;
21
21
22
+ import java .util .ArrayList ;
22
23
import java .util .Collection ;
23
24
import java .util .Collections ;
24
25
import java .util .LinkedHashMap ;
26
+ import java .util .List ;
25
27
import java .util .Map ;
26
28
import java .util .Optional ;
27
29
import java .util .function .BiFunction ;
28
30
31
+ import org .springframework .core .CollectionFactory ;
29
32
import org .springframework .core .convert .ConversionService ;
30
33
import org .springframework .dao .InvalidDataAccessApiUsageException ;
31
34
import org .springframework .data .convert .CustomConversions ;
49
52
import org .springframework .lang .Nullable ;
50
53
import org .springframework .util .Assert ;
51
54
import org .springframework .util .ClassUtils ;
55
+ import org .springframework .util .CollectionUtils ;
52
56
53
57
/**
54
58
* Converter for R2DBC.
@@ -164,13 +168,76 @@ private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersi
164
168
}
165
169
166
170
Object value = row .get (identifier );
167
- return getPotentiallyConvertedSimpleRead (value , property .getTypeInformation (). getType ());
171
+ return readValue (value , property .getTypeInformation ());
168
172
169
173
} catch (Exception o_O ) {
170
174
throw new MappingException (String .format ("Could not read property %s from result set!" , property ), o_O );
171
175
}
172
176
}
173
177
178
+ public Object readValue (@ Nullable Object value , TypeInformation <?> type ) {
179
+
180
+ if (null == value ) {
181
+ return null ;
182
+ }
183
+
184
+ if (getConversions ().hasCustomReadTarget (value .getClass (), type .getType ())) {
185
+ return getConversionService ().convert (value , type .getType ());
186
+ } else if (value instanceof Collection || value .getClass ().isArray ()) {
187
+ return readCollectionOrArray (asCollection (value ), type );
188
+ } else {
189
+ return getPotentiallyConvertedSimpleRead (value , type .getType ());
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Reads the given value into a collection of the given {@link TypeInformation}.
195
+ *
196
+ * @param source must not be {@literal null}.
197
+ * @param targetType must not be {@literal null}.
198
+ * @return the converted {@link Collection} or array, will never be {@literal null}.
199
+ */
200
+ @ SuppressWarnings ("unchecked" )
201
+ private Object readCollectionOrArray (Collection <?> source , TypeInformation <?> targetType ) {
202
+
203
+ Assert .notNull (targetType , "Target type must not be null!" );
204
+
205
+ Class <?> collectionType = targetType .isSubTypeOf (Collection .class ) //
206
+ ? targetType .getType () //
207
+ : List .class ;
208
+
209
+ TypeInformation <?> componentType = targetType .getComponentType () != null //
210
+ ? targetType .getComponentType () //
211
+ : ClassTypeInformation .OBJECT ;
212
+ Class <?> rawComponentType = componentType .getType ();
213
+
214
+ Collection <Object > items = targetType .getType ().isArray () //
215
+ ? new ArrayList <>(source .size ()) //
216
+ : CollectionFactory .createCollection (collectionType , rawComponentType , source .size ());
217
+
218
+ if (source .isEmpty ()) {
219
+ return getPotentiallyConvertedSimpleRead (items , targetType .getType ());
220
+ }
221
+
222
+ for (Object element : source ) {
223
+
224
+ if (!Object .class .equals (rawComponentType ) && element instanceof Collection ) {
225
+ if (!rawComponentType .isArray () && !ClassUtils .isAssignable (Iterable .class , rawComponentType )) {
226
+ throw new MappingException (String .format (
227
+ "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions" ,
228
+ element , element .getClass (), rawComponentType ));
229
+ }
230
+ }
231
+ if (element instanceof List ) {
232
+ items .add (readCollectionOrArray ((Collection <Object >) element , componentType ));
233
+ } else {
234
+ items .add (getPotentiallyConvertedSimpleRead (element , rawComponentType ));
235
+ }
236
+ }
237
+
238
+ return getPotentiallyConvertedSimpleRead (items , targetType .getType ());
239
+ }
240
+
174
241
/**
175
242
* Checks whether we have a custom conversion for the given simple object. Converts the given value if so, applies
176
243
* {@link Enum} handling or returns the value as is.
@@ -283,22 +350,87 @@ private void writeProperties(OutboundRow sink, RelationalPersistentEntity<?> ent
283
350
continue ;
284
351
}
285
352
286
- if (!getConversions ().isSimpleType (value .getClass ())) {
287
-
288
- RelationalPersistentEntity <?> nestedEntity = getMappingContext ().getPersistentEntity (property .getActualType ());
289
- if (nestedEntity != null ) {
290
- throw new InvalidDataAccessApiUsageException ("Nested entities are not supported" );
291
- }
353
+ if (getConversions ().isSimpleType (value .getClass ())) {
354
+ writeSimpleInternal (sink , value , property );
355
+ } else {
356
+ writePropertyInternal (sink , value , property );
292
357
}
293
-
294
- writeSimpleInternal (sink , value , property );
295
358
}
296
359
}
297
360
298
361
private void writeSimpleInternal (OutboundRow sink , Object value , RelationalPersistentProperty property ) {
299
362
sink .put (property .getColumnName (), SettableValue .from (getPotentiallyConvertedSimpleWrite (value )));
300
363
}
301
364
365
+ private void writePropertyInternal (OutboundRow sink , Object value , RelationalPersistentProperty property ) {
366
+
367
+ TypeInformation <?> valueType = ClassTypeInformation .from (value .getClass ());
368
+
369
+ if (valueType .isCollectionLike ()) {
370
+
371
+ if (valueType .getActualType () != null && valueType .getRequiredActualType ().isCollectionLike ()) {
372
+
373
+ // pass-thru nested collections
374
+ writeSimpleInternal (sink , value , property );
375
+ return ;
376
+ }
377
+
378
+ List <Object > collectionInternal = createCollection (asCollection (value ), property );
379
+ sink .put (property .getColumnName (), SettableValue .from (collectionInternal ));
380
+ return ;
381
+ }
382
+
383
+ throw new InvalidDataAccessApiUsageException ("Nested entities are not supported" );
384
+ }
385
+
386
+ /**
387
+ * Writes the given {@link Collection} using the given {@link RelationalPersistentProperty} information.
388
+ *
389
+ * @param collection must not be {@literal null}.
390
+ * @param property must not be {@literal null}.
391
+ * @return
392
+ */
393
+ protected List <Object > createCollection (Collection <?> collection , RelationalPersistentProperty property ) {
394
+ return writeCollectionInternal (collection , property .getTypeInformation (), new ArrayList <>());
395
+ }
396
+
397
+ /**
398
+ * Populates the given {@link Collection sink} with converted values from the given {@link Collection source}.
399
+ *
400
+ * @param source the collection to create a {@link Collection} for, must not be {@literal null}.
401
+ * @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
402
+ * @param sink the {@link Collection} to write to.
403
+ * @return
404
+ */
405
+ @ SuppressWarnings ("unchecked" )
406
+ private List <Object > writeCollectionInternal (Collection <?> source , @ Nullable TypeInformation <?> type ,
407
+ Collection <?> sink ) {
408
+
409
+ TypeInformation <?> componentType = null ;
410
+
411
+ List <Object > collection = sink instanceof List ? (List <Object >) sink : new ArrayList <>(sink );
412
+
413
+ if (type != null ) {
414
+ componentType = type .getComponentType ();
415
+ }
416
+
417
+ for (Object element : source ) {
418
+
419
+ Class <?> elementType = element == null ? null : element .getClass ();
420
+
421
+ if (elementType == null || getConversions ().isSimpleType (elementType )) {
422
+ collection .add (getPotentiallyConvertedSimpleWrite (element ,
423
+ componentType != null ? componentType .getType () : Object .class ));
424
+ } else if (element instanceof Collection || elementType .isArray ()) {
425
+ collection .add (writeCollectionInternal (asCollection (element ), componentType , new ArrayList <>()));
426
+ } else {
427
+ throw new InvalidDataAccessApiUsageException ("Nested entities are not supported" );
428
+ }
429
+ }
430
+
431
+ return collection ;
432
+ }
433
+
302
434
private void writeNullInternal (OutboundRow sink , RelationalPersistentProperty property ) {
303
435
304
436
sink .put (property .getColumnName (), SettableValue .empty (getPotentiallyConvertedSimpleNullType (property .getType ())));
@@ -321,19 +453,38 @@ private Class<?> getPotentiallyConvertedSimpleNullType(Class<?> type) {
321
453
}
322
454
323
455
/**
324
- * Checks whether we have a custom conversion registered for the given value into an arbitrary simple Mongo type.
325
- * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is.
456
+ * Checks whether we have a custom conversion registered for the given value into an arbitrary simple type. Returns
457
+ * the converted value if so. If not, we perform special enum handling or simply return the value as is.
326
458
*
327
459
* @param value
328
460
* @return
329
461
*/
330
462
@ Nullable
331
463
private Object getPotentiallyConvertedSimpleWrite (@ Nullable Object value ) {
464
+ return getPotentiallyConvertedSimpleWrite (value , Object .class );
465
+ }
466
+
467
+ /**
468
+ * Checks whether we have a custom conversion registered for the given value into an arbitrary simple type. Returns
469
+ * the converted value if so. If not, we perform special enum handling or simply return the value as is.
470
+ *
471
+ * @param value
472
+ * @return
473
+ */
474
+ @ Nullable
475
+ private Object getPotentiallyConvertedSimpleWrite (@ Nullable Object value , Class <?> typeHint ) {
332
476
333
477
if (value == null ) {
334
478
return null ;
335
479
}
336
480
481
+ if (Object .class != typeHint ) {
482
+
483
+ if (getConversionService ().canConvert (value .getClass (), typeHint )) {
484
+ value = getConversionService ().convert (value , typeHint );
485
+ }
486
+ }
487
+
337
488
Optional <Class <?>> customTarget = getConversions ().getCustomWriteTarget (value .getClass ());
338
489
339
490
if (customTarget .isPresent ()) {
@@ -350,7 +501,18 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
350
501
@ Override
351
502
public Object getArrayValue (ArrayColumns arrayColumns , RelationalPersistentProperty property , Object value ) {
352
503
353
- Class <?> targetType = arrayColumns .getArrayType (property .getActualType ());
504
+ Class <?> actualType = null ;
505
+ if (value instanceof Collection ) {
506
+ actualType = CollectionUtils .findCommonElementType ((Collection <?>) value );
507
+ } else if (value .getClass ().isArray ()) {
508
+ actualType = value .getClass ().getComponentType ();
509
+ }
510
+
511
+ if (actualType == null ) {
512
+ actualType = property .getActualType ();
513
+ }
514
+
515
+ Class <?> targetType = arrayColumns .getArrayType (actualType );
354
516
355
517
if (!property .isArray () || !targetType .isAssignableFrom (value .getClass ())) {
356
518
@@ -427,6 +589,23 @@ private <R> RelationalPersistentEntity<R> getRequiredPersistentEntity(Class<R> t
427
589
return (RelationalPersistentEntity <R >) getMappingContext ().getRequiredPersistentEntity (type );
428
590
}
429
591
592
+ /**
593
+ * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
594
+ * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
595
+ * collection for everything else.
596
+ *
597
+ * @param source
598
+ * @return
599
+ */
600
+ private static Collection <?> asCollection (Object source ) {
601
+
602
+ if (source instanceof Collection ) {
603
+ return (Collection <?>) source ;
604
+ }
605
+
606
+ return source .getClass ().isArray () ? CollectionUtils .arrayToList (source ) : Collections .singleton (source );
607
+ }
608
+
430
609
private static Map <String , ColumnMetadata > createMetadataMap (RowMetadata metadata ) {
431
610
432
611
Map <String , ColumnMetadata > columns = new LinkedHashMap <>();
0 commit comments