Skip to content

Commit fa9b31c

Browse files
committed
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.
1 parent 68bfd55 commit fa9b31c

File tree

2 files changed

+86
-15
lines changed

2 files changed

+86
-15
lines changed

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,10 @@ private RepositoryInvokingDeserializer(RepositoryInvokerFactory factory, Persist
763763
*/
764764
@Override
765765
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
766-
return invoker.invokeFindById(p.getValueAsString());
766+
767+
Object id = p.getCurrentToken().isNumeric() ? p.getValueAsLong() : p.getValueAsString();
768+
769+
return invoker.invokeFindById(id).orElse(null);
767770
}
768771
}
769772

@@ -785,23 +788,22 @@ public void serialize(Object value, JsonGenerator gen, SerializerProvider provid
785788
gen.writeStartArray();
786789

787790
for (Object element : (Collection<?>) value) {
788-
gen.writeString(getLookupKey(element));
791+
gen.writeObject(getLookupKey(element));
789792
}
790793

791794
gen.writeEndArray();
792795

793796
} else {
794-
gen.writeString(getLookupKey(value));
797+
gen.writeObject(getLookupKey(value));
795798
}
796799
}
797800

798-
private String getLookupKey(Object value) {
799-
800-
Optional<EntityLookup<Object>> map = lookups.getPluginFor(value.getClass()).map(CastUtils::cast);
801+
private Object getLookupKey(Object value) {
801802

802-
return map
803+
return lookups.getPluginFor(value.getClass()) //
804+
.<EntityLookup<Object>> map(CastUtils::cast)
803805
.orElseThrow(() -> new IllegalArgumentException("No EntityLookup found for " + value.getClass().getName()))
804-
.getResourceIdentifier(value).toString();
806+
.getResourceIdentifier(value);
805807
}
806808
}
807809
}

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
package org.springframework.data.rest.webmvc.json;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
1920
import static org.mockito.Mockito.*;
2021

22+
import lombok.Getter;
23+
2124
import java.io.IOException;
2225
import java.net.URI;
2326
import java.util.Arrays;
2427
import java.util.Collections;
28+
import java.util.Optional;
2529

2630
import org.junit.Before;
2731
import org.junit.Test;
@@ -32,9 +36,11 @@
3236
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
3337
import org.springframework.data.mapping.PersistentProperty;
3438
import org.springframework.data.mapping.context.PersistentEntities;
39+
import org.springframework.data.repository.support.RepositoryInvoker;
3540
import org.springframework.data.repository.support.RepositoryInvokerFactory;
3641
import org.springframework.data.rest.core.UriToEntityConverter;
3742
import org.springframework.data.rest.core.mapping.ResourceMappings;
43+
import org.springframework.data.rest.core.support.EntityLookup;
3844
import org.springframework.data.rest.core.support.SelfLinkProvider;
3945
import org.springframework.data.rest.core.util.Java8PluginRegistry;
4046
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
@@ -49,6 +55,8 @@
4955
import org.springframework.hateoas.UriTemplate;
5056
import org.springframework.hateoas.mvc.ResourceProcessorInvoker;
5157

58+
import com.fasterxml.jackson.annotation.JsonInclude;
59+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
5260
import com.fasterxml.jackson.annotation.JsonProperty;
5361
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5462
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -69,6 +77,7 @@ public class PersistentEntityJackson2ModuleUnitTests {
6977
@Mock EntityLinks entityLinks;
7078
@Mock ResourceMappings mappings;
7179
@Mock SelfLinkProvider selfLinks;
80+
@Mock RepositoryInvokerFactory factory;
7281

7382
PersistentEntities persistentEntities;
7483
ObjectMapper mapper;
@@ -90,9 +99,9 @@ public void setUp() {
9099
SimpleModule module = new SimpleModule();
91100

92101
module.setSerializerModifier(new AssociationOmittingSerializerModifier(persistentEntities, associations,
93-
nestedEntitySerializer, new LookupObjectSerializer(Java8PluginRegistry.empty())));
94-
module.setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(persistentEntities, associations,
95-
converter, mock(RepositoryInvokerFactory.class)));
102+
nestedEntitySerializer, new LookupObjectSerializer(Java8PluginRegistry.of(Arrays.asList(new HomeLookup())))));
103+
module.setDeserializerModifier(
104+
new AssociationUriResolvingDeserializerModifier(persistentEntities, associations, converter, factory));
96105

97106
this.mapper = new ObjectMapper();
98107
this.mapper.registerModule(module);
@@ -135,20 +144,80 @@ public void resolvesReferenceToSubtypeCorrectly() throws IOException {
135144
assertThat(petOwner.getPet()).isNotNull();
136145
}
137146

138-
static class PetOwner {
147+
@Test // DATAREST-1321
148+
public void allowsNumericIdsForLookupTypes() throws Exception {
139149

140-
Pet pet;
150+
RepositoryInvoker invoker = mock(RepositoryInvoker.class);
151+
when(invoker.invokeFindById(any(Long.class))).thenReturn(Optional.of(new Home()));
152+
153+
when(factory.getInvokerFor(Home.class)).thenReturn(invoker);
154+
155+
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
156+
.getRequiredPersistentProperty("home");
157+
158+
when(associations.isLookupType(property)).thenReturn(true);
159+
160+
PetOwner petOwner = mapper.readValue("{\"home\": 1 }", PetOwner.class);
161+
162+
assertThat(petOwner).isNotNull();
163+
assertThat(petOwner.getHome()).isInstanceOf(Home.class);
164+
}
165+
166+
@Test // DATAREST-1321
167+
public void serializesNonStringLookupValues() throws Exception {
168+
169+
// Given Pet defined as lookup type
170+
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
171+
.getRequiredPersistentProperty("home");
172+
when(associations.isLookupType(property)).thenReturn(true);
173+
174+
// When a Pet is rendered
175+
PetOwner owner = new PetOwner();
176+
owner.home = new Home();
141177

142-
public Pet getPet() {
143-
return pet;
178+
String result = mapper.writeValueAsString(owner);
179+
180+
// The it appears as numeric value
181+
assertThat(JsonPath.parse(result).read("$.home", Integer.class)) //
182+
.isEqualTo(41);
183+
}
184+
185+
/**
186+
* @author Oliver Gierke
187+
*/
188+
private static class HomeLookup implements EntityLookup<Home> {
189+
190+
@Override
191+
public Object getResourceIdentifier(Home entity) {
192+
return 41;
193+
}
194+
195+
@Override
196+
public boolean supports(Class<?> delimiter) {
197+
return delimiter.equals(Home.class);
198+
}
199+
200+
@Override
201+
public Optional<Home> lookupEntity(Object id) {
202+
return Optional.of(new Home());
144203
}
145204
}
146205

206+
@Getter
207+
@JsonInclude(Include.NON_NULL)
208+
static class PetOwner {
209+
210+
Pet pet;
211+
Home home;
212+
}
213+
147214
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS)
148215
static class Pet {}
149216

150217
static class Cat extends Pet {}
151218

219+
static class Home {}
220+
152221
static class Sample {
153222
public @JsonProperty("foo") String name;
154223
}

0 commit comments

Comments
 (0)