22
22
import java .util .ArrayList ;
23
23
import java .util .Arrays ;
24
24
import java .util .Collection ;
25
+ import java .util .Collections ;
25
26
import java .util .HashMap ;
27
+ import java .util .HashSet ;
26
28
import java .util .Iterator ;
29
+ import java .util .List ;
27
30
import java .util .Map ;
28
31
import java .util .Map .Entry ;
32
+ import java .util .Set ;
29
33
34
+ import org .springframework .beans .PropertyAccessor ;
35
+ import org .springframework .beans .PropertyAccessorFactory ;
36
+ import org .springframework .core .CollectionFactory ;
37
+ import org .springframework .core .convert .ConversionService ;
38
+ import org .springframework .core .convert .support .DefaultConversionService ;
30
39
import org .springframework .data .mapping .PersistentEntity ;
31
40
import org .springframework .data .mapping .PersistentProperty ;
32
41
import org .springframework .data .mapping .PersistentPropertyAccessor ;
33
42
import org .springframework .data .mapping .SimplePropertyHandler ;
34
43
import org .springframework .data .mapping .context .PersistentEntities ;
44
+ import org .springframework .data .mapping .model .ConvertingPropertyAccessor ;
35
45
import org .springframework .data .rest .webmvc .mapping .Associations ;
36
46
import org .springframework .data .util .ClassTypeInformation ;
37
47
import org .springframework .data .util .TypeInformation ;
38
48
import org .springframework .http .converter .HttpMessageNotReadableException ;
39
49
import org .springframework .util .Assert ;
50
+ import org .springframework .util .ObjectUtils ;
40
51
41
52
import com .fasterxml .jackson .databind .BeanDescription ;
42
53
import com .fasterxml .jackson .databind .JsonNode ;
@@ -92,6 +103,7 @@ public <T> T read(InputStream source, T target, ObjectMapper mapper) {
92
103
* @param mapper
93
104
* @return
94
105
*/
106
+ @ SuppressWarnings ("unchecked" )
95
107
public <T > T readPut (final ObjectNode source , T target , final ObjectMapper mapper ) {
96
108
97
109
Assert .notNull (source , "ObjectNode must not be null!" );
@@ -104,8 +116,50 @@ public <T> T readPut(final ObjectNode source, T target, final ObjectMapper mappe
104
116
105
117
Assert .notNull (entity , "No PersistentEntity found for " .concat (type .getName ()).concat ("!" ));
106
118
119
+ try {
120
+
121
+ Object intermediate = mapper .readerFor (target .getClass ()).readValue (source );
122
+ return (T ) mergeForPut (intermediate , target , mapper );
123
+
124
+ } catch (Exception o_O ) {
125
+ throw new HttpMessageNotReadableException ("Could not read payload!" , o_O );
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Merges the state of given source object onto the target one preserving PUT semantics.
131
+ *
132
+ * @param source can be {@literal null}.
133
+ * @param target can be {@literal null}.
134
+ * @param mapper must not be {@literal null}.
135
+ * @return
136
+ */
137
+ private <T > T mergeForPut (T source , T target , final ObjectMapper mapper ) {
138
+
139
+ Assert .notNull (mapper , "ObjectMapper must not be null!" );
140
+
141
+ if (target == null || source == null ) {
142
+ return source ;
143
+ }
144
+
145
+ Class <? extends Object > type = target .getClass ();
146
+
147
+ final PersistentEntity <?, ?> entity = entities .getPersistentEntity (type );
148
+
149
+ if (entity == null ) {
150
+ return source ;
151
+ }
152
+
153
+ Assert .notNull (entity , "No PersistentEntity found for " .concat (type .getName ()).concat ("!" ));
154
+
107
155
final MappedProperties properties = MappedProperties .fromJacksonProperties (entity , mapper );
108
156
157
+ ConversionService conversionService = new DefaultConversionService ();
158
+ final PersistentPropertyAccessor targetAccessor = entity .getPropertyAccessor (target );
159
+ final ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor (targetAccessor ,
160
+ conversionService );
161
+ final PersistentPropertyAccessor sourceAccessor = entity .getPropertyAccessor (source );
162
+
109
163
entity .doWithProperties (new SimplePropertyHandler () {
110
164
111
165
/*
@@ -119,18 +173,50 @@ public void doWithPersistentProperty(PersistentProperty<?> property) {
119
173
return ;
120
174
}
121
175
122
- String mappedName = properties .getMappedName (property );
176
+ if (!properties .isMappedProperty (property )) {
177
+ return ;
178
+ }
123
179
124
- boolean isMappedProperty = mappedName != null ;
125
- boolean noValueInSource = !source .has (mappedName );
180
+ Object sourceValue = sourceAccessor .getProperty (property );
181
+ Object targetValue = targetAccessor .getProperty (property );
182
+ Object result = null ;
126
183
127
- if (isMappedProperty && noValueInSource ) {
128
- source .putNull (mappedName );
184
+ if (property .isMap ()) {
185
+ result = mergeMaps (property , sourceValue , targetValue , mapper );
186
+ } else if (property .isCollectionLike ()) {
187
+ result = mergeCollections (property , sourceValue , targetValue , mapper );
188
+ } else if (property .isEntity ()) {
189
+ result = mergeForPut (sourceValue , targetValue , mapper );
190
+ } else {
191
+ result = sourceValue ;
129
192
}
193
+
194
+ convertingAccessor .setProperty (property , result );
130
195
}
131
196
});
132
197
133
- return merge (source , target , mapper );
198
+ // Need to copy unmapped properties as the PersistentProperty model currently does not contain any transient
199
+ // properties
200
+ copyRemainingProperties (properties , source , target );
201
+
202
+ return target ;
203
+ }
204
+
205
+ /**
206
+ * Copies the unmapped properties of the given {@link MappedProperties} from the source object to the target instance.
207
+ *
208
+ * @param properties must not be {@literal null}.
209
+ * @param source must not be {@literal null}.
210
+ * @param target must not be {@literal null}.
211
+ */
212
+ private static void copyRemainingProperties (MappedProperties properties , Object source , Object target ) {
213
+
214
+ PropertyAccessor sourceAccessor = PropertyAccessorFactory .forDirectFieldAccess (source );
215
+ PropertyAccessor targetAccessor = PropertyAccessorFactory .forDirectFieldAccess (target );
216
+
217
+ for (String property : properties .getSpringDataUnmappedProperties ()) {
218
+ targetAccessor .setPropertyValue (property , sourceAccessor .getPropertyValue (property ));
219
+ }
134
220
}
135
221
136
222
public <T > T merge (ObjectNode source , T target , ObjectMapper mapper ) {
@@ -152,7 +238,7 @@ public <T> T merge(ObjectNode source, T target, ObjectMapper mapper) {
152
238
* @throws Exception
153
239
*/
154
240
@ SuppressWarnings ("unchecked" )
155
- private <T > T doMerge (ObjectNode root , T target , ObjectMapper mapper ) throws Exception {
241
+ <T > T doMerge (ObjectNode root , T target , ObjectMapper mapper ) throws Exception {
156
242
157
243
Assert .notNull (root , "Root ObjectNode must not be null!" );
158
244
Assert .notNull (target , "Target object instance must not be null!" );
@@ -355,6 +441,72 @@ private void doMergeNestedMap(Map<Object, Object> source, ObjectNode node, Objec
355
441
}
356
442
}
357
443
444
+ @ SuppressWarnings ("unchecked" )
445
+ private Map <Object , Object > mergeMaps (PersistentProperty <?> property , Object source , Object target ,
446
+ ObjectMapper mapper ) {
447
+
448
+ Map <Object , Object > sourceMap = (Map <Object , Object >) source ;
449
+
450
+ if (sourceMap == null ) {
451
+ return null ;
452
+ }
453
+
454
+ Map <Object , Object > targetMap = (Map <Object , Object >) target ;
455
+ Map <Object , Object > result = targetMap == null ? CollectionFactory .createMap (Map .class , sourceMap .size ())
456
+ : CollectionFactory .createApproximateMap (targetMap , sourceMap .size ());
457
+
458
+ for (Entry <Object , Object > entry : sourceMap .entrySet ()) {
459
+
460
+ Object targetValue = targetMap == null ? null : targetMap .get (entry .getKey ());
461
+ result .put (entry .getKey (), mergeForPut (entry .getValue (), targetValue , mapper ));
462
+ }
463
+
464
+ return result ;
465
+ }
466
+
467
+ private Collection <Object > mergeCollections (PersistentProperty <?> property , Object source , Object target ,
468
+ ObjectMapper mapper ) {
469
+
470
+ Collection <Object > sourceCollection = asCollection (source );
471
+
472
+ if (sourceCollection == null ) {
473
+ return null ;
474
+ }
475
+
476
+ Collection <Object > targetCollection = asCollection (target );
477
+ Collection <Object > result = targetCollection == null
478
+ ? CollectionFactory .createCollection (Collection .class , sourceCollection .size ())
479
+ : CollectionFactory .createApproximateCollection (targetCollection , sourceCollection .size ());
480
+
481
+ Iterator <Object > sourceIterator = sourceCollection .iterator ();
482
+ Iterator <Object > targetIterator = targetCollection == null ? Collections .emptyIterator ()
483
+ : targetCollection .iterator ();
484
+
485
+ while (sourceIterator .hasNext ()) {
486
+
487
+ Object sourceElement = sourceIterator .next ();
488
+ Object targetElement = targetIterator .hasNext () ? targetIterator .next () : null ;
489
+
490
+ result .add (mergeForPut (sourceElement , targetElement , mapper ));
491
+ }
492
+
493
+ return result ;
494
+ }
495
+
496
+ @ SuppressWarnings ("unchecked" )
497
+ private static Collection <Object > asCollection (Object source ) {
498
+
499
+ if (source == null ) {
500
+ return null ;
501
+ } else if (source instanceof Collection ) {
502
+ return (Collection <Object >) source ;
503
+ } else if (source .getClass ().isArray ()) {
504
+ return Arrays .asList (ObjectUtils .toObjectArray (source ));
505
+ } else {
506
+ return Collections .singleton (source );
507
+ }
508
+ }
509
+
358
510
/**
359
511
* Returns the given source instance as {@link Collection} or creates a new one for the given type.
360
512
*
@@ -402,13 +554,15 @@ private static Class<?> typeOrObject(TypeInformation<?> type) {
402
554
* Simple value object to capture a mapping of Jackson mapped field names and {@link PersistentProperty} instances.
403
555
*
404
556
* @author Oliver Gierke
557
+ * @author Mark Paluch
405
558
*/
406
559
static class MappedProperties {
407
560
408
561
private static final ClassIntrospector INTROSPECTOR = new BasicClassIntrospector ();
409
562
410
- private final Map <PersistentProperty <?>, String > propertyToFieldName ;
563
+ private final Map <PersistentProperty <?>, BeanPropertyDefinition > propertyToFieldName ;
411
564
private final Map <String , PersistentProperty <?>> fieldNameToProperty ;
565
+ private final Set <BeanPropertyDefinition > unmappedProperties ;
412
566
413
567
/**
414
568
* Creates a new {@link MappedProperties} instance for the given {@link PersistentEntity} and
@@ -422,16 +576,19 @@ private MappedProperties(PersistentEntity<?, ?> entity, BeanDescription descript
422
576
Assert .notNull (entity , "Entity must not be null!" );
423
577
Assert .notNull (description , "BeanDescription must not be null!" );
424
578
425
- this .propertyToFieldName = new HashMap <PersistentProperty <?>, String >();
579
+ this .propertyToFieldName = new HashMap <PersistentProperty <?>, BeanPropertyDefinition >();
426
580
this .fieldNameToProperty = new HashMap <String , PersistentProperty <?>>();
581
+ this .unmappedProperties = new HashSet <BeanPropertyDefinition >();
427
582
428
583
for (BeanPropertyDefinition property : description .findProperties ()) {
429
584
430
585
PersistentProperty <?> persistentProperty = entity .getPersistentProperty (property .getInternalName ());
431
586
432
587
if (persistentProperty != null ) {
433
- propertyToFieldName .put (persistentProperty , property . getName () );
588
+ propertyToFieldName .put (persistentProperty , property );
434
589
fieldNameToProperty .put (property .getName (), persistentProperty );
590
+ } else {
591
+ unmappedProperties .add (property );
435
592
}
436
593
}
437
594
}
@@ -459,7 +616,7 @@ public String getMappedName(PersistentProperty<?> property) {
459
616
460
617
Assert .notNull (property , "PersistentProperty must not be null!" );
461
618
462
- return propertyToFieldName .get (property );
619
+ return propertyToFieldName .get (property ). getName () ;
463
620
}
464
621
465
622
/**
@@ -483,5 +640,38 @@ public PersistentProperty<?> getPersistentProperty(String fieldName) {
483
640
484
641
return fieldNameToProperty .get (fieldName );
485
642
}
643
+
644
+ /**
645
+ * Returns all properties only known to Jackson.
646
+ *
647
+ * @return the names of all properties that are not known to Spring Data but appear in the Jackson metamodel.
648
+ */
649
+ public Iterable <String > getSpringDataUnmappedProperties () {
650
+
651
+ if (unmappedProperties .isEmpty ()) {
652
+ return Collections .emptySet ();
653
+ }
654
+
655
+ List <String > result = new ArrayList <String >(unmappedProperties .size ());
656
+
657
+ for (BeanPropertyDefinition definitions : unmappedProperties ) {
658
+ result .add (definitions .getInternalName ());
659
+ }
660
+
661
+ return result ;
662
+ }
663
+
664
+ /**
665
+ * Returns whether the given {@link PersistentProperty} is mapped, i.e. known to both Jackson and Spring Data.
666
+ *
667
+ * @param property must not be {@literal null}.
668
+ * @return
669
+ */
670
+ public boolean isMappedProperty (PersistentProperty <?> property ) {
671
+
672
+ Assert .notNull (property , "PersistentProperty must not be null!" );
673
+
674
+ return propertyToFieldName .containsKey (property );
675
+ }
486
676
}
487
677
}
0 commit comments