From 359513fe9bdcd679f1ff0b1154d65b973201b800 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 10 Apr 2017 19:08:03 +0200 Subject: [PATCH] DATAREST-1030 - Reinstantiate PATCH behaviour for associations. We now completely opt out of merging values if the property is a linkable association. --- .../rest/webmvc/json/DomainObjectReader.java | 6 +- .../json/DomainObjectReaderUnitTests.java | 79 ++++++++++++++++++- 2 files changed, 78 insertions(+), 7 deletions(-) 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 bca937e1b..2a60b0e1c 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 @@ -232,7 +232,7 @@ public class DomainObjectReader { PersistentPropertyAccessor accessor = entity.getPropertyAccessor(target); Optional rawValue = accessor.getProperty(property); - if (!rawValue.isPresent()) { + if (!rawValue.isPresent() || associationLinks.isLinkableAssociation(property)) { continue; } @@ -249,10 +249,6 @@ public class DomainObjectReader { if (child.isObject()) { - if (associationLinks.isLinkableAssociation(property)) { - return; - } - ObjectNode objectNode = (ObjectNode) child; if (property.isMap()) { 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 899964271..5ffc7ed72 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 @@ -23,9 +23,11 @@ import static org.mockito.Mockito.*; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.Value; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -38,10 +40,12 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.UUID; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.CreatedDate; @@ -51,6 +55,7 @@ import org.springframework.data.annotation.Reference; import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.Version; import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.mapping.ResourceMappings; @@ -59,9 +64,14 @@ import org.springframework.data.rest.webmvc.mapping.Associations; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Charsets; @@ -79,6 +89,7 @@ public class DomainObjectReaderUnitTests { @Mock ResourceMappings mappings; DomainObjectReader reader; + PersistentEntities entities; @Before public void setUp() { @@ -98,10 +109,10 @@ public class DomainObjectReaderUnitTests { mappingContext.getPersistentEntity(TransientReadOnlyProperty.class); mappingContext.getPersistentEntity(CollectionOfEnumWithMethods.class); mappingContext.getPersistentEntity(SampleWithReference.class); + mappingContext.getPersistentEntity(Note.class); mappingContext.afterPropertiesSet(); - PersistentEntities entities = new PersistentEntities(Collections.singleton(mappingContext)); - + this.entities = new PersistentEntities(Collections.singleton(mappingContext)); this.reader = new DomainObjectReader(entities, new Associations(mappings, mock(RepositoryRestConfiguration.class))); } @@ -497,6 +508,35 @@ public class DomainObjectReaderUnitTests { assertThat(result.nested).isSameAs(originalCollection); } + @Test // DATAREST-1030 + public void patchWithReferenceToRelatedEntityIsResolvedCorrectly() throws Exception { + + Associations associations = mock(Associations.class); + PersistentProperty any = ArgumentMatchers.any(PersistentProperty.class); + when(associations.isLinkableAssociation(any)).thenReturn(true); + + DomainObjectReader reader = new DomainObjectReader(entities, associations); + + Tag first = new Tag(); + Tag second = new Tag(); + + Note note = new Note(); + note.tags.add(first); + note.tags.add(second); + + SimpleModule module = new SimpleModule(); + module.addDeserializer(Tag.class, new SelectValueByIdSerializer(Collections.singletonMap(second.id, second))); + + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(module); + + ObjectNode readTree = (ObjectNode) mapper.readTree(String.format("{ \"tags\" : [ \"%s\"]}", second.id)); + + Note result = reader.doMerge(readTree, note, mapper); + + assertThat(result.tags).contains(second); + } + @SuppressWarnings("unchecked") private static T as(Object source, Class type) { @@ -670,4 +710,39 @@ public class DomainObjectReaderUnitTests { static class Nested { int x, y; } + + // DATAREST-1030 + + @JsonAutoDetect(fieldVisibility = Visibility.ANY) + static class Note { + @Id UUID id = UUID.randomUUID(); + @Reference List tags = new ArrayList(); + } + + @JsonAutoDetect(fieldVisibility = Visibility.ANY) + static class Tag { + @Id UUID id = UUID.randomUUID(); + String name; + } + + @RequiredArgsConstructor + static class SelectValueByIdSerializer extends JsonDeserializer { + + private final Map values; + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + + String text = p.getText(); + + return values.entrySet().stream()// + .filter(it -> it.getKey().toString().equals(text))// + .map(it -> it.getValue())// + .findFirst().orElse(null); + } + } }