From 129ea2a5e38d53a53c21013e2249e5c178c73e15 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 18 Aug 2016 13:07:02 +0200 Subject: [PATCH] DATAREST-837 - DomainObjectMerger doesn't nullify read-only properties on PUT. PUT requests are supposed to replace the state of the resource with the request payload. However, Spring Data REST already handles a couple of domain object propoerties in a special way as theri values map to dedicated HTTP features: identifiers (URIs), last-modified dates (header) etc. For users, it might be worthwhile to exclude other properties from being set by applying the payload, like properties that are completely under the control of the server, e.g. creation user and date, last modifying user etc. So far, users didn't have any means to exclude those properties as the handling of PUT requests treated every missing property of the payload as null value. DomainObjectMerger now checks whether a property is actually writable before applying the implicit null value. The application can be disabled by annotation a property with @ReadOnlyProperty. --- .../rest/webmvc/json/DomainObjectReader.java | 2 +- .../json/DomainObjectReaderUnitTests.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java index c423e26a3..25b40d6d4 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java @@ -121,7 +121,7 @@ public class DomainObjectReader { @Override public void doWithPersistentProperty(PersistentProperty property) { - if (property.isIdProperty() || property.isVersionProperty()) { + if (property.isIdProperty() || property.isVersionProperty() || !property.isWritable()) { return; } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/DomainObjectReaderUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/DomainObjectReaderUnitTests.java index 08f6a32ce..7c7b9b497 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/DomainObjectReaderUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/DomainObjectReaderUnitTests.java @@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -27,7 +28,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Version; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -61,6 +64,7 @@ public class DomainObjectReaderUnitTests { mappingContext.getPersistentEntity(Person.class); mappingContext.getPersistentEntity(TypeWithGenericMap.class); mappingContext.getPersistentEntity(VersionedType.class); + mappingContext.getPersistentEntity(SampleWithCreatedDate.class); mappingContext.afterPropertiesSet(); PersistentEntities entities = new PersistentEntities(Collections.singleton(mappingContext)); @@ -169,6 +173,23 @@ public class DomainObjectReaderUnitTests { assertThat(result.version, is(1L)); } + /** + * @see DATAREST-873 + */ + @Test + public void doesNotApplyInputToReadOnlyFields() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = (ObjectNode) mapper.readTree("{}"); + + Date reference = new Date(); + + SampleWithCreatedDate sample = new SampleWithCreatedDate(); + sample.createdDate = reference; + + assertThat(reader.readPut(node, sample, mapper).createdDate, is(reference)); + } + @JsonAutoDetect(fieldVisibility = Visibility.ANY) static class SampleUser { @@ -210,4 +231,12 @@ public class DomainObjectReaderUnitTests { String firstname, lastname; } + + @JsonAutoDetect(fieldVisibility = Visibility.ANY) + static class SampleWithCreatedDate { + + @CreatedDate // + @ReadOnlyProperty // + Date createdDate; + } }