From 3fb9ad51d697dd105c9efcb9a7fbb12ba0ab661f Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 8 Jan 2019 11:43:11 +0100 Subject: [PATCH] DATAREST-1321 - Lookup types can now produce non-String reference values. Previously we assumed lookup types to always result in String based values. We now loosen that constraint to also allow other scalar types, mostly targeting numeric types like long and integer. --- .../json/PersistentEntityJackson2Module.java | 12 +-- ...rsistentEntityJackson2ModuleUnitTests.java | 85 +++++++++++++++++-- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java index 24d5e40ff..0f9ccf0be 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java @@ -764,7 +764,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { */ @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { - return invoker.invokeFindOne(p.getValueAsString()); + return invoker.invokeFindOne(p.getCurrentToken().isNumeric() ? p.getValueAsLong() : p.getValueAsString()); } } @@ -772,7 +772,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { public static class LookupObjectSerializer extends ToStringSerializer { private static final long serialVersionUID = -3033458643050330913L; - private final PluginRegistry, Class> lookups; + private final PluginRegistry, Class> lookups; /* * (non-Javadoc) @@ -786,21 +786,21 @@ public class PersistentEntityJackson2Module extends SimpleModule { gen.writeStartArray(); for (Object element : (Collection) value) { - gen.writeString(getLookupKey(element)); + gen.writeObject(getLookupKey(element)); } gen.writeEndArray(); } else { - gen.writeString(getLookupKey(value)); + gen.writeObject(getLookupKey(value)); } } @SuppressWarnings("unchecked") - private String getLookupKey(Object value) { + private Object getLookupKey(Object value) { EntityLookup lookup = (EntityLookup) lookups.getPluginFor(value.getClass()); - return lookup.getResourceIdentifier(value).toString(); + return lookup.getResourceIdentifier(value); } } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java index 443f67cb1..6c5f2f0bb 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java @@ -19,7 +19,10 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import lombok.Getter; + import java.io.IOException; +import java.io.Serializable; import java.net.URI; import java.util.Arrays; import java.util.Collections; @@ -28,11 +31,13 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.core.convert.TypeDescriptor; 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.repository.support.RepositoryInvoker; import org.springframework.data.repository.support.RepositoryInvokerFactory; import org.springframework.data.rest.core.UriToEntityConverter; import org.springframework.data.rest.core.mapping.ResourceMappings; @@ -51,6 +56,8 @@ import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mvc.ResourceProcessorInvoker; import org.springframework.plugin.core.OrderAwarePluginRegistry; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; @@ -59,7 +66,7 @@ import com.jayway.jsonpath.JsonPath; /** * Unit tests for {@link PersistentEntityJackson2Module}. - * + * * @author Oliver Gierke * @author Valentin Rentschler */ @@ -71,6 +78,7 @@ public class PersistentEntityJackson2ModuleUnitTests { @Mock EntityLinks entityLinks; @Mock ResourceMappings mappings; @Mock SelfLinkProvider selfLinks; + @Mock RepositoryInvokerFactory factory; PersistentEntities persistentEntities; ObjectMapper mapper; @@ -89,14 +97,14 @@ public class PersistentEntityJackson2ModuleUnitTests { NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities, new EmbeddedResourcesAssembler(persistentEntities, associations, mock(ExcerptProjector.class)), invoker); - OrderAwarePluginRegistry, Class> lookups = OrderAwarePluginRegistry.create(); + OrderAwarePluginRegistry, Class> lookups = OrderAwarePluginRegistry + .create(Arrays.asList(new HomeLookup())); SimpleModule module = new SimpleModule(); - module.setSerializerModifier(new AssociationOmittingSerializerModifier(persistentEntities, associations, nestedEntitySerializer, new LookupObjectSerializer(lookups))); - module.setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(persistentEntities, associations, - converter, mock(RepositoryInvokerFactory.class))); + module.setDeserializerModifier( + new AssociationUriResolvingDeserializerModifier(persistentEntities, associations, converter, factory)); this.mapper = new ObjectMapper(); this.mapper.registerModule(module); @@ -138,13 +146,70 @@ public class PersistentEntityJackson2ModuleUnitTests { assertThat(petOwner.getPet(), is(notNullValue())); } + @Test // DATAREST-1321 + public void allowsNumericIdsForLookupTypes() throws Exception { + + RepositoryInvoker invoker = mock(RepositoryInvoker.class); + when(invoker.invokeFindOne(Mockito.any(Long.class))).thenReturn(new Home()); + + when(factory.getInvokerFor(Home.class)).thenReturn(invoker); + + PersistentProperty property = persistentEntities.getPersistentEntity(PetOwner.class) + .getPersistentProperty("home"); + + when(associations.isLookupType(property)).thenReturn(true); + + PetOwner petOwner = mapper.readValue("{\"home\": 1 }", PetOwner.class); + + assertThat(petOwner, is(notNullValue())); + assertThat(petOwner.getHome(), is(instanceOf(Home.class))); + } + + @Test // DATAREST-1321 + public void serializesNonStringLookupValues() throws Exception { + // Given Pet defined as lookup type + + PersistentProperty property = persistentEntities.getPersistentEntity(PetOwner.class) + .getPersistentProperty("home"); + when(associations.isLookupType(property)).thenReturn(true); + + // When a Pet is rendered + PetOwner owner = new PetOwner(); + owner.home = new Home(); + + String result = mapper.writeValueAsString(owner); + + // The it appears as numeric value + assertThat(JsonPath.parse(result).read("$.home", Integer.class), is(41)); + } + + /** + * @author Oliver Gierke + */ + private static class HomeLookup implements EntityLookup { + + @Override + public Serializable getResourceIdentifier(Home entity) { + return 41; + } + + @Override + public boolean supports(Class delimiter) { + return delimiter.equals(Home.class); + } + + @Override + public Home lookupEntity(Serializable id) { + return new Home(); + } + } + + @Getter + @JsonInclude(Include.NON_NULL) static class PetOwner { Pet pet; - - public Pet getPet() { - return pet; - } + Home home; } @JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS) @@ -152,6 +217,8 @@ public class PersistentEntityJackson2ModuleUnitTests { static class Cat extends Pet {} + static class Home {} + static class Sample { public @JsonProperty("foo") String name; }