From b7a4061ab984b4b2f0cdf44498ba22c686a28e94 Mon Sep 17 00:00:00 2001 From: Jeff Cherng Date: Sun, 10 Feb 2019 15:25:36 -0600 Subject: [PATCH] DATAGEODE-170 - Add entity level pdx serializer resolvers for MappingPdxSerializer Allows MappingPdxSerializer to serialize/deserialize entity object by defining custom pdx serializer at entity level. --- .../gemfire/mapping/MappingPdxSerializer.java | 112 ++++++++++++++++-- .../MappingPdxSerializerIntegrationTests.java | 40 ++++++- .../MappingPdxSerializerUnitTests.java | 78 +++++++++++- 3 files changed, 218 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java index 5980896c0..fd9fcc2f7 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java @@ -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 @@ -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 @@ -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)); @@ -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) { @@ -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> entity) { + + Map 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}. * @@ -685,36 +717,102 @@ public interface PdxSerializerResolver { @Nullable PdxSerializer resolve(@NonNull Map customPdxSerializers, - @NonNull PersistentProperty property); + @NonNull Object param); } public enum PdxSerializerResolvers implements PdxSerializerResolver { + ENTITY { + + @Override + public PdxSerializer resolve(Map 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 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 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 customPdxSerializers, PersistentProperty property) { - return customPdxSerializers.get(property); + public PdxSerializer resolve(Map 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 customPdxSerializers, PersistentProperty property) { - return customPdxSerializers.get(toFullyQualifiedPropertyName(property)); + public PdxSerializer resolve(Map 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 customPdxSerializers, PersistentProperty property) { - return customPdxSerializers.get(property.getType()); + public PdxSerializer resolve(Map 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> entity) { + return entity.getType().getName(); + } + /** * Converts the entity {@link PersistentProperty} to a {@link String fully-qualified property name}. * diff --git a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java index 126c85af7..414b219cb 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java @@ -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; @@ -67,6 +68,7 @@ * * @author Oliver Gierke * @author John Blum + * @author Jeff Cherng */ public class MappingPdxSerializerIntegrationTests { @@ -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); @@ -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.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); @@ -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)); } diff --git a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java index 9bad97aef..b98daaf5a 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java @@ -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 @@ -119,6 +120,10 @@ public void setUp() { this.pdxSerializer = spy(new MappingPdxSerializer(this.mappingContext, this.conversionService)); } + private String toFullyQualifiedEntityName(PersistentEntity> entity) { + return MappingPdxSerializer.PdxSerializerResolvers.toFullyQualifiedEntityName(entity); + } + private String toFullyQualifiedPropertyName(PersistentProperty property) { return MappingPdxSerializer.PdxSerializerResolvers.toFullyQualifiedPropertyName(property); } @@ -392,7 +397,7 @@ public void isWritableWithTransientPropertyReturnsFalse() { verify(mockProperty, times(1)).isTransient(); } - @Test + @Test // DATAGEODE-170 @SuppressWarnings("all") public void resolveCustomPdxSerializerReturnsNull() { @@ -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.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() { @@ -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.newMapBuilder() + .put(toFullyQualifiedEntityName(personEntity), mockNamedSerializer) + .put(Person.class, mockTypedSerializer) + .build()); + + assertThat(this.pdxSerializer.resolveCustomPdxSerializer(personEntity)).isEqualTo(mockNamedSerializer); + } + @Test @SuppressWarnings("all") public void resolveCustomPdxSerializerReturnsPdxSerializerForPropertyName() { @@ -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 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() { @@ -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() {