DATAREST-1030 - Reinstantiate PATCH behaviour for associations.

We now completely opt out of merging values if the property is a linkable association.
This commit is contained in:
Oliver Gierke
2017-04-10 19:08:03 +02:00
parent 06c2b746c4
commit 359513fe9b
2 changed files with 78 additions and 7 deletions

View File

@@ -232,7 +232,7 @@ public class DomainObjectReader {
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(target);
Optional<Object> 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()) {

View File

@@ -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<Tag>(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> T as(Object source, Class<T> 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<Tag> tags = new ArrayList<Tag>();
}
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
static class Tag {
@Id UUID id = UUID.randomUUID();
String name;
}
@RequiredArgsConstructor
static class SelectValueByIdSerializer<T> extends JsonDeserializer<T> {
private final Map<? extends Object, T> 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);
}
}
}