Skip to content

DATAGEODE-170 - Add entity level pdx serializer resolvers for Mapping… #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
* @author Oliver Gierke
* @author David Turanski
* @author John Blum
* @author Jeff Cherng
* @see org.apache.geode.pdx.PdxReader
* @see org.apache.geode.pdx.PdxSerializer
* @see org.apache.geode.pdx.PdxWriter
Expand Down Expand Up @@ -249,6 +250,9 @@ public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionServ
this.entityInstantiators = new EntityInstantiators();

this.pdxSerializerResolvers.addAll(Arrays.asList(
PdxSerializerResolvers.ENTITY,
PdxSerializerResolvers.ENTITY_NAME,
PdxSerializerResolvers.ENTITY_TYPE,
PdxSerializerResolvers.PROPERTY,
PdxSerializerResolvers.PROPERTY_NAME,
PdxSerializerResolvers.PROPERTY_TYPE
Expand Down Expand Up @@ -478,6 +482,10 @@ Object doFromData(Class<?> type, PdxReader reader) {

GemfirePersistentEntity<?> entity = getPersistentEntity(type);

if(resolveCustomPdxSerializer(entity) != null){
return resolveCustomPdxSerializer(entity).fromData(type, reader);
}

Object instance = resolveEntityInstantiator(entity)
.createInstance(entity, new PersistentEntityParameterValueProvider<>(entity,
new GemfirePropertyValueProvider(reader), null));
Expand Down Expand Up @@ -564,6 +572,10 @@ boolean doToData(Object value, PdxWriter writer) {

GemfirePersistentEntity<?> entity = getPersistentEntity(value);

if(resolveCustomPdxSerializer(entity) != null){
return resolveCustomPdxSerializer(entity).toData(value, writer);
}

// Entity will be null for simple types (e.g. int, Long, String, etc).
if (entity != null) {

Expand Down Expand Up @@ -634,6 +646,26 @@ boolean isReadable(GemfirePersistentProperty persistentProperty) {
return !persistentProperty.isTransient();
}

/**
* Returns a custom PDX serializer for the given {@link PersistentEntity entity}.
*
* @param entity {@link PersistentEntity} used to lookup the custom PDX serializer.
* @return a custom {@link PdxSerializer} for the given entity {@link PersistentEntity},
* or {@literal null} if no custom {@link PdxSerializer} could be found.
* @see org.apache.geode.pdx.PdxSerializer
*/
@Nullable
protected PdxSerializer resolveCustomPdxSerializer(@NonNull PersistentEntity<?, ? extends PersistentProperty<?>> entity) {

Map<?, PdxSerializer> customPdxSerializers = getCustomPdxSerializers();

return this.pdxSerializerResolvers.stream()
.map(it -> it.resolve(customPdxSerializers, entity))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

/**
* Returns a custom PDX serializer for the given {@link PersistentProperty entity persistent property}.
*
Expand Down Expand Up @@ -685,36 +717,102 @@ public interface PdxSerializerResolver {

@Nullable
PdxSerializer resolve(@NonNull Map<?, PdxSerializer> customPdxSerializers,
@NonNull PersistentProperty<?> property);
@NonNull Object param);

}

public enum PdxSerializerResolvers implements PdxSerializerResolver {

ENTITY {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentEntity){
PersistentEntity entity = (PersistentEntity) param;
pdxSerializer = customPdxSerializers.get(entity);
}
return pdxSerializer;
}
},

ENTITY_NAME {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentEntity){
PersistentEntity entity = (PersistentEntity) param;
pdxSerializer = customPdxSerializers.get(toFullyQualifiedEntityName(entity));
}
return pdxSerializer;
}
},

ENTITY_TYPE {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentEntity){
PersistentEntity entity = (PersistentEntity) param;
pdxSerializer = customPdxSerializers.get(entity.getType());
}
return pdxSerializer;
}
},

PROPERTY {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, PersistentProperty<?> property) {
return customPdxSerializers.get(property);
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentProperty){
PersistentProperty property = (PersistentProperty) param;
pdxSerializer = customPdxSerializers.get(property);
}
return pdxSerializer;
}
},

PROPERTY_NAME {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, PersistentProperty<?> property) {
return customPdxSerializers.get(toFullyQualifiedPropertyName(property));
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentProperty){
PersistentProperty property = (PersistentProperty) param;
pdxSerializer = customPdxSerializers.get(toFullyQualifiedPropertyName(property));
}
return pdxSerializer;
}
},

PROPERTY_TYPE {

@Override
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, PersistentProperty<?> property) {
return customPdxSerializers.get(property.getType());
public PdxSerializer resolve(Map<?, PdxSerializer> customPdxSerializers, Object param) {
PdxSerializer pdxSerializer = null;
if(param instanceof PersistentProperty){
PersistentProperty property = (PersistentProperty) param;
pdxSerializer = customPdxSerializers.get(property.getType());
}
return pdxSerializer;
}
};

/**
* Converts the entity {@link PersistentEntity} to a {@link String fully-qualified name}.
*
* @param entity {@link PersistentEntity}.
* @return the {@link String fully-qualified name of the entity {@link PersistentEntity}.
* @see org.springframework.data.mapping.PersistentEntity
*/
@NonNull
static String toFullyQualifiedEntityName(@NonNull PersistentEntity<?, ? extends PersistentProperty<?>> entity) {
return entity.getType().getName();
}

/**
* Converts the entity {@link PersistentProperty} to a {@link String fully-qualified property name}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.repository.sample.Address;
import org.springframework.data.gemfire.repository.sample.Person;
import org.springframework.data.gemfire.test.support.MapBuilder;

import lombok.AllArgsConstructor;
import lombok.Data;
Expand All @@ -67,6 +68,7 @@
*
* @author Oliver Gierke
* @author John Blum
* @author Jeff Cherng
*/
public class MappingPdxSerializerIntegrationTests {

Expand Down Expand Up @@ -271,8 +273,33 @@ public void serializationUsesCustomPropertyNameBasedPdxSerializer() {
.fromData(eq(String.class), isA(PdxReader.class));
}

@Test
public void serializationUsesCustomPropertyTypeBasedPdxSerializer() {
@Test // DATAGEODE-170
public void serializationUsesCustomPdxSerializers() {

PdxSerializer mockCustomerSerializer = mock(PdxSerializer.class);

when(mockCustomerSerializer.toData(any(), any(PdxWriter.class))).thenAnswer(invocation -> {

Customer customer = invocation.getArgument(0);

PdxWriter pdxWriter = invocation.getArgument(1);

pdxWriter.writeObject("creditCard", customer.getCreditCard());
pdxWriter.writeString("name", customer.getName());

return true;
});

when(mockCustomerSerializer.fromData(any(Class.class), any(PdxReader.class))).thenAnswer(invocation -> {

PdxReader pdxReader = invocation.getArgument(1);

CreditCard creditCard = (CreditCard) pdxReader.readObject("creditCard");

String name = pdxReader.readString("name");

return Customer.newCustomer(creditCard, name);
});

PdxSerializer mockCreditCardSerializer = mock(PdxSerializer.class);

Expand Down Expand Up @@ -312,7 +339,10 @@ public void serializationUsesCustomPropertyTypeBasedPdxSerializer() {
.map(regionService -> ((Cache) regionService).getPdxSerializer())
.filter(pdxSerializer -> pdxSerializer instanceof MappingPdxSerializer)
.ifPresent(pdxSerializer -> ((MappingPdxSerializer) pdxSerializer)
.setCustomPdxSerializers(Collections.singletonMap(CreditCard.class, mockCreditCardSerializer)));
.setCustomPdxSerializers(MapBuilder.<Object, PdxSerializer>newMapBuilder()
.put(Customer.class, mockCustomerSerializer)
.put(CreditCard.class, mockCreditCardSerializer)
.build()));

CreditCard creditCard = CreditCard.of(LocalDate.of(2020, Month.FEBRUARY, 12),
"8842-6789-4186-7981", CreditCard.Type.VISA);
Expand All @@ -335,8 +365,10 @@ public void serializationUsesCustomPropertyTypeBasedPdxSerializer() {
assertThat(jonDoeLoaded.getCreditCard().getNumber()).isEqualTo("xxxx-7981");
assertThat(jonDoeLoaded.getCreditCard().getType()).isEqualTo(jonDoe.getCreditCard().getType());

verify(mockCustomerSerializer, atLeastOnce()).toData(eq(jonDoe), isA(PdxWriter.class));
verify(mockCustomerSerializer, times(1))
.fromData(eq(Customer.class), isA(PdxReader.class));
verify(mockCreditCardSerializer, atLeastOnce()).toData(eq(creditCard), isA(PdxWriter.class));

verify(mockCreditCardSerializer, times(1))
.fromData(eq(CreditCard.class), isA(PdxReader.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
*
* @author Oliver Gierke
* @author John Blum
* @author Jeff Cherng
* @see org.junit.Rule
* @see org.junit.Test
* @see org.junit.runner.RunWith
Expand Down Expand Up @@ -119,6 +120,10 @@ public void setUp() {
this.pdxSerializer = spy(new MappingPdxSerializer(this.mappingContext, this.conversionService));
}

private String toFullyQualifiedEntityName(PersistentEntity<?, ? extends PersistentProperty<?>> entity) {
return MappingPdxSerializer.PdxSerializerResolvers.toFullyQualifiedEntityName(entity);
}

private String toFullyQualifiedPropertyName(PersistentProperty<?> property) {
return MappingPdxSerializer.PdxSerializerResolvers.toFullyQualifiedPropertyName(property);
}
Expand Down Expand Up @@ -392,7 +397,7 @@ public void isWritableWithTransientPropertyReturnsFalse() {
verify(mockProperty, times(1)).isTransient();
}

@Test
@Test // DATAGEODE-170
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsNull() {

Expand All @@ -401,9 +406,29 @@ public void resolveCustomPdxSerializerReturnsNull() {
PersistentProperty addressProperty = personEntity.getPersistentProperty("address");

assertThat(this.pdxSerializer.getCustomPdxSerializers()).isEmpty();
assertThat(this.pdxSerializer.resolveCustomPdxSerializer(personEntity)).isNull();
assertThat(this.pdxSerializer.resolveCustomPdxSerializer(addressProperty)).isNull();
}

@Test // DATAGEODE-170
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForEntity() {

PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
PdxSerializer mockEntitySerializer = mock(PdxSerializer.class);
PdxSerializer mockTypedSerializer = mock(PdxSerializer.class);

PersistentEntity personEntity = this.mappingContext.getPersistentEntity(Person.class);

this.pdxSerializer.setCustomPdxSerializers(MapBuilder.<Object, PdxSerializer>newMapBuilder()
.put(personEntity, mockEntitySerializer)
.put(toFullyQualifiedEntityName(personEntity), mockNamedSerializer)
.put(Person.class, mockTypedSerializer)
.build());

assertThat(this.pdxSerializer.resolveCustomPdxSerializer(personEntity)).isEqualTo(mockEntitySerializer);
}

@Test
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForProperty() {
Expand All @@ -425,6 +450,25 @@ public void resolveCustomPdxSerializerReturnsPdxSerializerForProperty() {
assertThat(this.pdxSerializer.resolveCustomPdxSerializer(addressProperty)).isEqualTo(mockPropertySerializer);
}

@Test // DATAGEODE-170
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForEntityName() {

PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
PdxSerializer mockTypedSerializer = mock(PdxSerializer.class);

PersistentEntity personEntity = this.mappingContext.getPersistentEntity(Person.class);

// PersistentProperty addressProperty = personEntity.getPersistentProperty("address");

this.pdxSerializer.setCustomPdxSerializers(MapBuilder.<Object, PdxSerializer>newMapBuilder()
.put(toFullyQualifiedEntityName(personEntity), mockNamedSerializer)
.put(Person.class, mockTypedSerializer)
.build());

assertThat(this.pdxSerializer.resolveCustomPdxSerializer(personEntity)).isEqualTo(mockNamedSerializer);
}

@Test
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForPropertyName() {
Expand All @@ -444,6 +488,25 @@ public void resolveCustomPdxSerializerReturnsPdxSerializerForPropertyName() {
assertThat(this.pdxSerializer.resolveCustomPdxSerializer(addressProperty)).isEqualTo(mockNamedSerializer);
}

@Test // DATAGEODE-170
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForEntityType() {

PdxSerializer mockNamedSerializer = mock(PdxSerializer.class);
PdxSerializer mockTypedSerializer = mock(PdxSerializer.class);

Map<Object, PdxSerializer> customPdxSerializers = new HashMap<>();

PersistentEntity personEntity = this.mappingContext.getPersistentEntity(Person.class);

customPdxSerializers.put("example.Type.person", mockNamedSerializer);
customPdxSerializers.put(Person.class, mockTypedSerializer);

this.pdxSerializer.setCustomPdxSerializers(customPdxSerializers);

assertThat(this.pdxSerializer.resolveCustomPdxSerializer(personEntity)).isEqualTo(mockTypedSerializer);
}

@Test
@SuppressWarnings("all")
public void resolveCustomPdxSerializerReturnsPdxSerializerForPropertyType() {
Expand Down Expand Up @@ -509,6 +572,19 @@ public void resolveTypeWithNullType() {
assertThat(this.pdxSerializer.resolveType(null)).isNull();
}

@Test // DATAGEODE-170
public void toFullyQualifiedEntityName() {

PersistentEntity mockEntity = mock(PersistentEntity.class);

when(mockEntity.getType()).thenReturn(Person.class);

assertThat(MappingPdxSerializer.PdxSerializerResolvers.toFullyQualifiedEntityName(mockEntity))
.isEqualTo(Person.class.getName());

verify(mockEntity, times(1)).getType();
}

@Test
public void toFullyQualifiedPropertyName() {

Expand Down