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 .Iterator ;
26
27
import java .util .Map ;
27
28
import java .util .Map .Entry ;
28
29
30
+ import org .springframework .beans .PropertyAccessor ;
31
+ import org .springframework .beans .PropertyAccessorFactory ;
32
+ import org .springframework .core .CollectionFactory ;
33
+ import org .springframework .core .convert .ConversionService ;
34
+ import org .springframework .core .convert .support .DefaultConversionService ;
29
35
import org .springframework .data .mapping .PersistentEntity ;
30
36
import org .springframework .data .mapping .PersistentProperty ;
31
37
import org .springframework .data .mapping .PersistentPropertyAccessor ;
32
38
import org .springframework .data .mapping .SimplePropertyHandler ;
33
39
import org .springframework .data .mapping .context .PersistentEntities ;
40
+ import org .springframework .data .mapping .model .ConvertingPropertyAccessor ;
34
41
import org .springframework .data .rest .webmvc .mapping .Associations ;
35
42
import org .springframework .data .util .ClassTypeInformation ;
36
43
import org .springframework .data .util .TypeInformation ;
37
44
import org .springframework .http .converter .HttpMessageNotReadableException ;
38
45
import org .springframework .util .Assert ;
46
+ import org .springframework .util .ObjectUtils ;
39
47
40
48
import com .fasterxml .jackson .databind .JsonNode ;
41
49
import com .fasterxml .jackson .databind .ObjectMapper ;
@@ -88,6 +96,7 @@ public <T> T read(InputStream source, T target, ObjectMapper mapper) {
88
96
* @param mapper
89
97
* @return
90
98
*/
99
+ @ SuppressWarnings ("unchecked" )
91
100
public <T > T readPut (final ObjectNode source , T target , final ObjectMapper mapper ) {
92
101
93
102
Assert .notNull (source , "ObjectNode must not be null!" );
@@ -100,8 +109,50 @@ public <T> T readPut(final ObjectNode source, T target, final ObjectMapper mappe
100
109
101
110
Assert .notNull (entity , "No PersistentEntity found for " .concat (type .getName ()).concat ("!" ));
102
111
112
+ try {
113
+
114
+ Object intermediate = mapper .readerFor (target .getClass ()).readValue (source );
115
+ return (T ) mergeForPut (intermediate , target , mapper );
116
+
117
+ } catch (Exception o_O ) {
118
+ throw new HttpMessageNotReadableException ("Could not read payload!" , o_O );
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Merges the state of given source object onto the target one preserving PUT semantics.
124
+ *
125
+ * @param source can be {@literal null}.
126
+ * @param target can be {@literal null}.
127
+ * @param mapper must not be {@literal null}.
128
+ * @return
129
+ */
130
+ private <T > T mergeForPut (T source , T target , final ObjectMapper mapper ) {
131
+
132
+ Assert .notNull (mapper , "ObjectMapper must not be null!" );
133
+
134
+ if (target == null || source == null ) {
135
+ return source ;
136
+ }
137
+
138
+ Class <? extends Object > type = target .getClass ();
139
+
140
+ final PersistentEntity <?, ?> entity = entities .getPersistentEntity (type );
141
+
142
+ if (entity == null ) {
143
+ return source ;
144
+ }
145
+
146
+ Assert .notNull (entity , "No PersistentEntity found for " .concat (type .getName ()).concat ("!" ));
147
+
103
148
final MappedProperties properties = MappedProperties .fromJacksonProperties (entity , mapper );
104
149
150
+ ConversionService conversionService = new DefaultConversionService ();
151
+ final PersistentPropertyAccessor targetAccessor = entity .getPropertyAccessor (target );
152
+ final ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor (targetAccessor ,
153
+ conversionService );
154
+ final PersistentPropertyAccessor sourceAccessor = entity .getPropertyAccessor (source );
155
+
105
156
entity .doWithProperties (new SimplePropertyHandler () {
106
157
107
158
/*
@@ -115,18 +166,50 @@ public void doWithPersistentProperty(PersistentProperty<?> property) {
115
166
return ;
116
167
}
117
168
118
- String mappedName = properties .getMappedName (property );
169
+ if (!properties .isMappedProperty (property )) {
170
+ return ;
171
+ }
119
172
120
- boolean isMappedProperty = mappedName != null ;
121
- boolean noValueInSource = !source .has (mappedName );
173
+ Object sourceValue = sourceAccessor .getProperty (property );
174
+ Object targetValue = targetAccessor .getProperty (property );
175
+ Object result = null ;
122
176
123
- if (isMappedProperty && noValueInSource ) {
124
- source .putNull (mappedName );
177
+ if (property .isMap ()) {
178
+ result = mergeMaps (property , sourceValue , targetValue , mapper );
179
+ } else if (property .isCollectionLike ()) {
180
+ result = mergeCollections (property , sourceValue , targetValue , mapper );
181
+ } else if (property .isEntity ()) {
182
+ result = mergeForPut (sourceValue , targetValue , mapper );
183
+ } else {
184
+ result = sourceValue ;
125
185
}
186
+
187
+ convertingAccessor .setProperty (property , result );
126
188
}
127
189
});
128
190
129
- return merge (source , target , mapper );
191
+ // Need to copy unmapped properties as the PersistentProperty model currently does not contain any transient
192
+ // properties
193
+ copyRemainingProperties (properties , source , target );
194
+
195
+ return target ;
196
+ }
197
+
198
+ /**
199
+ * Copies the unmapped properties of the given {@link MappedProperties} from the source object to the target instance.
200
+ *
201
+ * @param properties must not be {@literal null}.
202
+ * @param source must not be {@literal null}.
203
+ * @param target must not be {@literal null}.
204
+ */
205
+ private static void copyRemainingProperties (MappedProperties properties , Object source , Object target ) {
206
+
207
+ PropertyAccessor sourceAccessor = PropertyAccessorFactory .forDirectFieldAccess (source );
208
+ PropertyAccessor targetAccessor = PropertyAccessorFactory .forDirectFieldAccess (target );
209
+
210
+ for (String property : properties .getSpringDataUnmappedProperties ()) {
211
+ targetAccessor .setPropertyValue (property , sourceAccessor .getPropertyValue (property ));
212
+ }
130
213
}
131
214
132
215
public <T > T merge (ObjectNode source , T target , ObjectMapper mapper ) {
@@ -148,7 +231,7 @@ public <T> T merge(ObjectNode source, T target, ObjectMapper mapper) {
148
231
* @throws Exception
149
232
*/
150
233
@ SuppressWarnings ("unchecked" )
151
- private <T > T doMerge (ObjectNode root , T target , ObjectMapper mapper ) throws Exception {
234
+ <T > T doMerge (ObjectNode root , T target , ObjectMapper mapper ) throws Exception {
152
235
153
236
Assert .notNull (root , "Root ObjectNode must not be null!" );
154
237
Assert .notNull (target , "Target object instance must not be null!" );
@@ -351,6 +434,72 @@ private void doMergeNestedMap(Map<Object, Object> source, ObjectNode node, Objec
351
434
}
352
435
}
353
436
437
+ @ SuppressWarnings ("unchecked" )
438
+ private Map <Object , Object > mergeMaps (PersistentProperty <?> property , Object source , Object target ,
439
+ ObjectMapper mapper ) {
440
+
441
+ Map <Object , Object > sourceMap = (Map <Object , Object >) source ;
442
+
443
+ if (sourceMap == null ) {
444
+ return null ;
445
+ }
446
+
447
+ Map <Object , Object > targetMap = (Map <Object , Object >) target ;
448
+ Map <Object , Object > result = targetMap == null ? CollectionFactory .createMap (Map .class , sourceMap .size ())
449
+ : CollectionFactory .createApproximateMap (targetMap , sourceMap .size ());
450
+
451
+ for (Entry <Object , Object > entry : sourceMap .entrySet ()) {
452
+
453
+ Object targetValue = targetMap == null ? null : targetMap .get (entry .getKey ());
454
+ result .put (entry .getKey (), mergeForPut (entry .getValue (), targetValue , mapper ));
455
+ }
456
+
457
+ return result ;
458
+ }
459
+
460
+ private Collection <Object > mergeCollections (PersistentProperty <?> property , Object source , Object target ,
461
+ ObjectMapper mapper ) {
462
+
463
+ Collection <Object > sourceCollection = asCollection (source );
464
+
465
+ if (sourceCollection == null ) {
466
+ return null ;
467
+ }
468
+
469
+ Collection <Object > targetCollection = asCollection (target );
470
+ Collection <Object > result = targetCollection == null
471
+ ? CollectionFactory .createCollection (Collection .class , sourceCollection .size ())
472
+ : CollectionFactory .createApproximateCollection (targetCollection , sourceCollection .size ());
473
+
474
+ Iterator <Object > sourceIterator = sourceCollection .iterator ();
475
+ Iterator <Object > targetIterator = targetCollection == null ? Collections .emptyIterator ()
476
+ : targetCollection .iterator ();
477
+
478
+ while (sourceIterator .hasNext ()) {
479
+
480
+ Object sourceElement = sourceIterator .next ();
481
+ Object targetElement = targetIterator .hasNext () ? targetIterator .next () : null ;
482
+
483
+ result .add (mergeForPut (sourceElement , targetElement , mapper ));
484
+ }
485
+
486
+ return result ;
487
+ }
488
+
489
+ @ SuppressWarnings ("unchecked" )
490
+ private static Collection <Object > asCollection (Object source ) {
491
+
492
+ if (source == null ) {
493
+ return null ;
494
+ } else if (source instanceof Collection ) {
495
+ return (Collection <Object >) source ;
496
+ } else if (source .getClass ().isArray ()) {
497
+ return Arrays .asList (ObjectUtils .toObjectArray (source ));
498
+ } else {
499
+ return Collections .singleton (source );
500
+ }
501
+ }
502
+
354
503
/**
355
504
* Returns the given source instance as {@link Collection} or creates a new one for the given type.
356
505
*
0 commit comments