Fix mapping URIs to @JsonProperty annotated associations
Normally, when creating and updating (POST/PUT) an entity via the REST endpoints, you can use the URL of the relation target. (e.g.: send `{"package": "/packages/1"}` when `package` is a JPA `@OneToOne` relation).
Now this also takes into account when the JPA relation is annotated with `@JsonProperty` to change the serialized name.
Add unit tests for linkable associations.
Issue: #2165
This commit is contained in:
committed by
Oliver Drotbohm
parent
8d9855b57e
commit
5335fe61e9
@@ -24,6 +24,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -82,6 +83,8 @@ import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
||||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
@@ -410,6 +413,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
* non-optional associations can be populated on resource creation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Lars Vierbergen
|
||||
*/
|
||||
public static class AssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {
|
||||
|
||||
@@ -444,7 +448,18 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
while (properties.hasNext()) {
|
||||
|
||||
SettableBeanProperty property = properties.next();
|
||||
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getName());
|
||||
// To find the PersistentProperty name in case there is a @JsonProperty annotation
|
||||
// on the field. Both BeanPropertyDefinition#getName() and BeanPropertyDefinition#getInternalName()
|
||||
// don't return the actual name of the field, so we look up the AnnotatedField itself to retrieve
|
||||
// the real name from, so it can be used for PersistentProperty lookup
|
||||
String persistentPropertyName = beanDesc.findProperties().stream()
|
||||
.filter(propertyDefinition -> property.getName().equals(propertyDefinition.getName()))
|
||||
.map(BeanPropertyDefinition::getField).filter(Objects::nonNull).map(AnnotatedField::getName).findFirst()
|
||||
// Fall back to the JSON name in case we can't find a BeanPropertyDefinition,
|
||||
// so things can be mapped by convention in case they are immutable objects and are
|
||||
// using constructor injection
|
||||
.orElse(property.getName());
|
||||
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(persistentPropertyName);
|
||||
|
||||
if (persistentProperty == null) {
|
||||
continue;
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -96,6 +97,7 @@ class PersistentEntityJackson2ModuleUnitTests {
|
||||
|
||||
KeyValueMappingContext<?, ?> mappingContext = new KeyValueMappingContext<>();
|
||||
mappingContext.getPersistentEntity(Sample.class);
|
||||
mappingContext.getPersistentEntity(Package.class);
|
||||
mappingContext.getPersistentEntity(SampleWithAdditionalGetters.class);
|
||||
mappingContext.getPersistentEntity(PersistentEntityJackson2ModuleUnitTests.PetOwner.class);
|
||||
mappingContext.getPersistentEntity(Immutable.class);
|
||||
@@ -157,6 +159,41 @@ class PersistentEntityJackson2ModuleUnitTests {
|
||||
assertThat(petOwner.getPet()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsUrlsForLinkableAssociation() throws Exception {
|
||||
|
||||
when(converter.convert(UriTemplate.of("/homes/1").expand(), TypeDescriptor.valueOf(URI.class),
|
||||
TypeDescriptor.valueOf(Home.class))).thenReturn(new Home());
|
||||
|
||||
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
|
||||
.getRequiredPersistentProperty("home");
|
||||
|
||||
when(associations.isLinkableAssociation(property)).thenReturn(true);
|
||||
|
||||
PetOwner petOwner = mapper.readValue("{\"home\": \"/homes/1\" }", PetOwner.class);
|
||||
|
||||
assertThat(petOwner).isNotNull();
|
||||
assertThat(petOwner.getHome()).isInstanceOf(Home.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsUrlsForRenamedLinkableAssociation() throws IOException {
|
||||
|
||||
when(converter.convert(UriTemplate.of("/packages/1").expand(), TypeDescriptor.valueOf(URI.class),
|
||||
TypeDescriptor.valueOf(Package.class))).thenReturn(new Package());
|
||||
|
||||
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
|
||||
.getRequiredPersistentProperty("_package");
|
||||
|
||||
when(associations.isLinkableAssociation(property)).thenReturn(true);
|
||||
|
||||
PetOwner petOwner = mapper.readValue("{\"package\":\"/packages/1\"}", PetOwner.class);
|
||||
|
||||
assertThat(petOwner).isNotNull();
|
||||
assertThat(petOwner.getPackage()).isNotNull();
|
||||
}
|
||||
|
||||
|
||||
@Test // DATAREST-1321
|
||||
void allowsNumericIdsForLookupTypes() throws Exception {
|
||||
|
||||
@@ -260,8 +297,17 @@ class PersistentEntityJackson2ModuleUnitTests {
|
||||
|
||||
Pet pet;
|
||||
Home home;
|
||||
|
||||
@Getter(value = AccessLevel.NONE)
|
||||
@JsonProperty("package") Package _package;
|
||||
|
||||
public Package getPackage() {
|
||||
return _package;
|
||||
}
|
||||
}
|
||||
|
||||
static class Package {}
|
||||
|
||||
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS)
|
||||
static class Pet {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user