Skip to content

Commit 86af638

Browse files
committed
DATAREST-957 - Improved PUT handling for transient properties not backed by a field.
When copying the transient properties of an aggregate, we now try field based access first and fall back to accessor based copying in case both a setter and getter are exposed on the type. Previously we always expected a field to be present which doesn't necessarily has to be the case. Related ticket: DATAREST-986.
1 parent e7077cd commit 86af638

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,23 @@ public void doWithPersistentProperty(PersistentProperty<?> property) {
211211
*/
212212
private static void copyRemainingProperties(MappedProperties properties, Object source, Object target) {
213213

214-
PropertyAccessor sourceAccessor = PropertyAccessorFactory.forDirectFieldAccess(source);
215-
PropertyAccessor targetAccessor = PropertyAccessorFactory.forDirectFieldAccess(target);
214+
PropertyAccessor sourceFieldAccessor = PropertyAccessorFactory.forDirectFieldAccess(source);
215+
PropertyAccessor sourcePropertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(source);
216+
PropertyAccessor targetFieldAccessor = PropertyAccessorFactory.forDirectFieldAccess(target);
217+
PropertyAccessor targetPropertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(target);
216218

217219
for (String property : properties.getSpringDataUnmappedProperties()) {
218-
targetAccessor.setPropertyValue(property, sourceAccessor.getPropertyValue(property));
220+
221+
// If there's a field we can just copy it.
222+
if (targetFieldAccessor.isWritableProperty(property)) {
223+
targetFieldAccessor.setPropertyValue(property, sourceFieldAccessor.getPropertyValue(property));
224+
continue;
225+
}
226+
227+
// Otherwise only copy if there's both a getter and setter.
228+
if (targetPropertyAccessor.isWritableProperty(property) && sourcePropertyAccessor.isReadableProperty(property)) {
229+
targetPropertyAccessor.setPropertyValue(property, sourcePropertyAccessor.getPropertyValue(property));
230+
}
219231
}
220232
}
221233

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/DomainObjectReaderUnitTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public void setUp() {
9292
mappingContext.getPersistentEntity(Outer.class);
9393
mappingContext.getPersistentEntity(Parent.class);
9494
mappingContext.getPersistentEntity(Product.class);
95+
mappingContext.getPersistentEntity(TransientReadOnlyProperty.class);
9596
mappingContext.afterPropertiesSet();
9697

9798
PersistentEntities entities = new PersistentEntities(Collections.singleton(mappingContext));
@@ -489,6 +490,15 @@ public void readsComplexMap() throws Exception {
489490
assertThat(result.map.get(Locale.GERMAN), is(new LocalizedValue("schlussendlich")));
490491
}
491492

493+
@Test // DATAREST-987
494+
public void handlesTransientPropertyWithoutFieldProperly() throws Exception {
495+
496+
ObjectMapper mapper = new ObjectMapper();
497+
JsonNode node = mapper.readTree("{ \"name\" : \"Foo\" }");
498+
499+
reader.readPut((ObjectNode) node, new TransientReadOnlyProperty(), mapper);
500+
}
501+
492502
@SuppressWarnings("unchecked")
493503
private static <T> T as(Object source, Class<T> type) {
494504

@@ -614,4 +624,15 @@ static class Product {
614624
static class LocalizedValue {
615625
String value;
616626
}
627+
628+
@JsonAutoDetect(getterVisibility = Visibility.ANY)
629+
static class TransientReadOnlyProperty {
630+
631+
@Transient
632+
public String getName() {
633+
return null;
634+
}
635+
636+
public void setName(String name) {}
637+
}
617638
}

0 commit comments

Comments
 (0)