Skip to content

Commit 2738982

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 132807c commit 2738982

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

+15-3
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,23 @@ public void doWithPersistentProperty(PersistentProperty<?> property) {
204204
*/
205205
private static void copyRemainingProperties(MappedProperties properties, Object source, Object target) {
206206

207-
PropertyAccessor sourceAccessor = PropertyAccessorFactory.forDirectFieldAccess(source);
208-
PropertyAccessor targetAccessor = PropertyAccessorFactory.forDirectFieldAccess(target);
207+
PropertyAccessor sourceFieldAccessor = PropertyAccessorFactory.forDirectFieldAccess(source);
208+
PropertyAccessor sourcePropertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(source);
209+
PropertyAccessor targetFieldAccessor = PropertyAccessorFactory.forDirectFieldAccess(target);
210+
PropertyAccessor targetPropertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(target);
209211

210212
for (String property : properties.getSpringDataUnmappedProperties()) {
211-
targetAccessor.setPropertyValue(property, sourceAccessor.getPropertyValue(property));
213+
214+
// If there's a field we can just copy it.
215+
if (targetFieldAccessor.isWritableProperty(property)) {
216+
targetFieldAccessor.setPropertyValue(property, sourceFieldAccessor.getPropertyValue(property));
217+
continue;
218+
}
219+
220+
// Otherwise only copy if there's both a getter and setter.
221+
if (targetPropertyAccessor.isWritableProperty(property) && sourcePropertyAccessor.isReadableProperty(property)) {
222+
targetPropertyAccessor.setPropertyValue(property, sourcePropertyAccessor.getPropertyValue(property));
223+
}
212224
}
213225
}
214226

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

+21
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));
@@ -441,6 +442,15 @@ public void readsComplexMap() throws Exception {
441442
assertThat(result.map.get(Locale.GERMAN), is(new LocalizedValue("schlussendlich")));
442443
}
443444

445+
@Test // DATAREST-987
446+
public void handlesTransientPropertyWithoutFieldProperly() throws Exception {
447+
448+
ObjectMapper mapper = new ObjectMapper();
449+
JsonNode node = mapper.readTree("{ \"name\" : \"Foo\" }");
450+
451+
reader.readPut((ObjectNode) node, new TransientReadOnlyProperty(), mapper);
452+
}
453+
444454
@SuppressWarnings("unchecked")
445455
private static <T> T as(Object source, Class<T> type) {
446456

@@ -564,4 +574,15 @@ static class Product {
564574
static class LocalizedValue {
565575
String value;
566576
}
577+
578+
@JsonAutoDetect(getterVisibility = Visibility.ANY)
579+
static class TransientReadOnlyProperty {
580+
581+
@Transient
582+
public String getName() {
583+
return null;
584+
}
585+
586+
public void setName(String name) {}
587+
}
567588
}

0 commit comments

Comments
 (0)