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 df72cd43b..30bbe0784 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 @@ -65,6 +65,7 @@ * @author Mathias Düsterhöft * @author Thomas Mrozinski * @author Lars Vierbergen + * @author Steve Rutherford * @since 2.2 */ @SuppressWarnings("NullAway") @@ -282,7 +283,13 @@ T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exception { if (!mappedProperties.isWritableField(fieldName)) { - i.remove(); + // Allow the version field to pass through so that the underlying store's optimistic locking + // mechanism can detect version mismatches (GH-1689). The id field is still stripped to + // prevent clients from changing the identity of the resource. + PersistentProperty nonWritable = mappedProperties.getPersistentProperty(fieldName); + if (nonWritable == null || !nonWritable.isVersionProperty()) { + i.remove(); + } continue; } 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 f50726c28..1e347cc73 100755 --- 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 @@ -73,6 +73,7 @@ * @author Mathias Düsterhöft * @author Ken Dombeck * @author Thomas Mrozinski + * @author Steve Rutherford */ @ExtendWith(MockitoExtension.class) class DomainObjectReaderUnitTests { @@ -208,6 +209,42 @@ void doesNotWipeIdAndVersionPropertyForPut() throws Exception { assertThat(result.version).isEqualTo(1L); } + @Test // GH-1689 + void appliesVersionFromClientForPatch() throws Exception { + + VersionedType existing = new VersionedType(); + existing.id = 1L; + existing.version = 1L; + existing.firstname = "Dave"; + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = (ObjectNode) mapper.readTree("{ \"version\" : 2, \"lastname\" : \"Matthews\" }"); + + VersionedType result = reader.doMerge(node, existing, mapper); + + assertThat(result.lastname).isEqualTo("Matthews"); + assertThat(result.id).isEqualTo(1L); + assertThat(result.version).isEqualTo(2L); + } + + @Test // GH-1689 + void doesNotAllowMutatingVersionViaPutBody() throws Exception { + + VersionedType existing = new VersionedType(); + existing.id = 1L; + existing.version = 1L; + existing.firstname = "Dave"; + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = (ObjectNode) mapper.readTree("{ \"version\" : 9999, \"lastname\" : \"Matthews\" }"); + + VersionedType result = reader.readPut(node, existing, mapper); + + assertThat(result.lastname).isEqualTo("Matthews"); + assertThat(result.id).isEqualTo(1L); + assertThat(result.version).isEqualTo(1L); + } + @Test // GH-59 void doesNotAllowMutatingIdAndVersionViaPutBody() throws Exception {