From 16b6071e598a32dd9c4cb1586afaf0fd776493c3 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 12 Feb 2018 18:41:01 -0800 Subject: [PATCH] DATAGEODE-82 - Extend MappingPdxSerializer to allow registering custom PdxSerializers based on fully qualified property name. --- .../gemfire/mapping/MappingPdxSerializer.java | 261 ++++++++++++----- .../MappingPdxSerializerIntegrationTests.java | 210 +++++++++++++- .../MappingPdxSerializerUnitTests.java | 266 +++++++++++++----- 3 files changed, 584 insertions(+), 153 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 af4dd0c9..4ee71714 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java @@ -32,32 +32,37 @@ import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.SpELContext; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * GemFire {@link PdxSerializer} implementation that uses a Spring Data GemFire {@link GemfireMappingContext} - * to read and write entities. + * GemFire {@link PdxSerializer} implementation using the Spring Data GemFire {@link GemfireMappingContext} + * to read and write entities from/to GemFire PDX bytes. * * @author Oliver Gierke * @author David Turanski * @author John Blum + * @see org.apache.geode.pdx.PdxReader + * @see org.apache.geode.pdx.PdxSerializer + * @see org.apache.geode.pdx.PdxWriter * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ApplicationContextAware * @see org.springframework.core.convert.ConversionService * @see org.springframework.data.convert.EntityInstantiator * @see org.springframework.data.convert.EntityInstantiators * @see org.springframework.data.mapping.PersistentEntity + * @see org.springframework.data.mapping.PersistentProperty * @see org.springframework.data.mapping.PersistentPropertyAccessor * @see org.springframework.data.mapping.model.ConvertingPropertyAccessor - * @see org.apache.geode.pdx.PdxReader - * @see org.apache.geode.pdx.PdxSerializer - * @see org.apache.geode.pdx.PdxWriter + * @see org.springframework.data.mapping.model.PersistentEntityParameterValueProvider * @since 1.2.0 */ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAware { @@ -70,51 +75,115 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw private final Logger logger = LoggerFactory.getLogger(getClass()); - private Map, PdxSerializer> customSerializers; + private Map customPdxSerializers; - // TODO: decide what to do with this; the SpELContext is not used + // TODO: decide what to do with this; SpELContext is not used private SpELContext context; + public static MappingPdxSerializer newMappingPdxSerializer() { + return create(newMappingContext(), newConversionService()); + } + + public static MappingPdxSerializer create(@Nullable ConversionService conversionService) { + return create(newMappingContext(), conversionService); + } + + public static MappingPdxSerializer create(@Nullable GemfireMappingContext mappingContext) { + return create(mappingContext, newConversionService()); + } + /** - * Factory method to construct a new instance of the {@link MappingPdxSerializer} initialized with the given - * {@link GemfireMappingContext} and Spring {@link ConversionService}. If either the {@link GemfireMappingContext} - * or Spring {@link ConversionService} are {@literal null}, then this factory method will construct default - * instances of each. + * Factory method used to construct a new instance of the {@link MappingPdxSerializer} initialized with + * the given {@link GemfireMappingContext mapping context} and {@link ConversionService conversion service}. * - * @param mappingContext {@link GemfireMappingContext} used by this {@link PdxSerializer} to handle mappings - * between application domain object types and PDX Serialization meta-data/data. - * @param conversionService Spring's {@link ConversionService} used to convert PDX deserialized data to application - * object property types. + * If either the {@link GemfireMappingContext mapping context} or the {@link ConversionService conversion service} + * are {@literal null}, then this factory method will provide default instances for each. + * + * @param mappingContext {@link GemfireMappingContext} used by the {@link MappingPdxSerializer} to map + * between application domain object types and PDX serialized bytes based on the entity mapping meta-data. + * @param conversionService {@link ConversionService} used by the {@link MappingPdxSerializer} to convert + * PDX serialized data to application object property types. * @return an initialized instance of the {@link MappingPdxSerializer}. * @see org.springframework.core.convert.ConversionService * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer */ - public static MappingPdxSerializer create(GemfireMappingContext mappingContext, - ConversionService conversionService) { + public static MappingPdxSerializer create(@Nullable GemfireMappingContext mappingContext, + @Nullable ConversionService conversionService) { - mappingContext = mappingContext != null ? mappingContext : new GemfireMappingContext(); - conversionService = conversionService != null ? conversionService : new DefaultConversionService(); - - return new MappingPdxSerializer(mappingContext, conversionService); + return new MappingPdxSerializer( + resolveMappingContext(mappingContext), + resolveConversionService(conversionService) + ); } /** - * Creates a new {@link MappingPdxSerializer} using the default {@link GemfireMappingContext} + * Constructs a new {@link ConversionService}. + * + * @return a new {@link ConversionService}. + * @see org.springframework.core.convert.ConversionService + */ + private static ConversionService newConversionService() { + return new DefaultConversionService(); + } + + /** + * Resolves the {@link ConversionService} used for conversions. + * + * @param conversionService {@link ConversionService} to evaluate. + * @return the given {@link ConversionService} if not {@literal null} or a new {@link ConversionService}. + * @see org.springframework.core.convert.ConversionService + * @see #newConversionService() + */ + private static ConversionService resolveConversionService(ConversionService conversionService) { + return conversionService != null ? conversionService : newConversionService(); + } + + /** + * Constructs a new {@link GemfireMappingContext}. + * + * @return a new {@link GemfireMappingContext}. + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + */ + private static GemfireMappingContext newMappingContext() { + return new GemfireMappingContext(); + } + + /** + * Resolves the {@link GemfireMappingContext mapping context} used to provide mapping meta-data. + * + * @param mappingContext {@link GemfireMappingContext} to evaluate. + * @return the given {@link GemfireMappingContext mapping context} if not {@literal null} + * or a new {@link GemfireMappingContext mapping context}. + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see #newMappingContext() + */ + private static GemfireMappingContext resolveMappingContext(GemfireMappingContext mappingContext) { + return mappingContext != null ? mappingContext : newMappingContext(); + } + + /** + * Constructs a new instance of {@link MappingPdxSerializer} using a default {@link GemfireMappingContext} * and {@link DefaultConversionService}. * + * @see #newConversionService() + * @see #newMappingContext() * @see org.springframework.core.convert.support.DefaultConversionService * @see org.springframework.data.gemfire.mapping.GemfireMappingContext */ public MappingPdxSerializer() { - this(new GemfireMappingContext(), new DefaultConversionService()); + this(newMappingContext(), newConversionService()); } /** - * Creates a new {@link MappingPdxSerializer} using the given + * Constructs a new instance of {@link MappingPdxSerializer} initialized with the given * {@link GemfireMappingContext} and {@link ConversionService}. * - * @param mappingContext must not be {@literal null}. - * @param conversionService must not be {@literal null}. + * @param mappingContext {@link GemfireMappingContext} used by the {@link MappingPdxSerializer} to map + * between application domain object types and PDX serialized bytes based on the entity mapping meta-data. + * @param conversionService {@link ConversionService} used by the {@link MappingPdxSerializer} to convert + * PDX serialized data to application object property types. + * @throws IllegalArgumentException if either the {@link GemfireMappingContext} or the {@link ConversionService} + * is {@literal null}. */ public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionService conversionService) { @@ -124,7 +193,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw this.mappingContext = mappingContext; this.conversionService = conversionService; this.entityInstantiators = new EntityInstantiators(); - this.customSerializers = Collections.emptyMap(); + this.customPdxSerializers = Collections.emptyMap(); this.context = new SpELContext(PdxReaderPropertyAccessor.INSTANCE); } @@ -143,56 +212,106 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * Returns a reference to the configured {@link ConversionService} used to convert data store types * to application domain object types. * - * @return a refernce to the configured {@link ConversionService}. + * @return a reference to the configured {@link ConversionService}. * @see org.springframework.core.convert.ConversionService */ + @NonNull protected ConversionService getConversionService() { return this.conversionService; } /** - * Configures custom {@link PdxSerializer PDX serializers} used to serialize - * specific application {@link Class class types}. + * Configures custom {@link PdxSerializer PDX serializers} used to customize the serialization for specific + * application {@link Class domain types}. * - * @param customSerializers mapping of application domain object {@link Class class types} - * to {@link PdxSerializer PDX serializers} used to handle those {@link Class class types}. - * @throws IllegalArgumentException if the {@link Map} is {@literal null}. + * @param customPdxSerializers {@link Map mapping} containing custom {@link PdxSerializer PDX serializers} + * used to customize the serialization of specific application {@link Class domain types}. + * @throws IllegalArgumentException if the {@link Map custom PDX serializer mapping} is {@literal null}. * @see org.apache.geode.pdx.PdxSerializer * @see java.util.Map */ + public void setCustomPdxSerializers(@NonNull Map customPdxSerializers) { + + Assert.notNull(customPdxSerializers, "Custom PdxSerializers are required"); + + this.customPdxSerializers = customPdxSerializers; + } + + /** + * @deprecated please use ({@link #setCustomPdxSerializers(Map)} instead. + */ + @Deprecated public void setCustomSerializers(Map, PdxSerializer> customSerializers) { - - Assert.notNull(customSerializers, "Custom PdxSerializers are required"); - - this.customSerializers = customSerializers; + setCustomPdxSerializers(customSerializers); } /** - * Returns a {@link Map mapping} of application domain object {@link Class types} - * to custom {@link PdxSerializer PdxSerializers} used to handle custom serialization logic - * for the application domain objects. + * Returns a {@link Map mapping} of application {@link Class domain types} to custom + * {@link PdxSerializer PDX serializers} used to customize the serialization + * for specific application {@link Class domain types}. * - * @return a {@link Map mapping} of application domain object {@link Class types} - * to custom {@link PdxSerializer PdxSerializers}. + * @return a {@link Map mapping} of application {@link Class domain types} + * to custom {@link PdxSerializer PDX serializers}. * @see org.apache.geode.pdx.PdxSerializer * @see java.util.Map */ - protected Map, PdxSerializer> getCustomSerializers() { - return Collections.unmodifiableMap(this.customSerializers); + @NonNull + protected Map getCustomPdxSerializers() { + return Collections.unmodifiableMap(this.customPdxSerializers); } /** - * Looks up and returns a custom PdxSerializer based on the class type of the object to (de)serialize. - * - * @param type the Class type of the object to (de)serialize. - * @return a "custom" PdxSerializer for the given class type or null if no custom PdxSerializer - * for the given class type was registered. - * @see #getCustomSerializers() - * @see org.apache.geode.pdx.PdxSerializer - * @see java.lang.Class + * @deprecated please use {@link #getCustomPdxSerializers()} instead. */ + @Deprecated + @SuppressWarnings("unchecked") + protected Map, PdxSerializer> getCustomSerializers() { + return (Map, PdxSerializer>) getCustomPdxSerializers(); + } + + /** + * Returns a custom PDX serializer for the given {@link PersistentProperty entity persistent property}. + * + * @param property {@link PersistentProperty} of the entity used to lookup the custom PDX serializer. + * @return a custom {@link PdxSerializer} for the given entity {@link PersistentProperty}, + * or {@literal null} if no custom {@link PdxSerializer} could be found. + * @see org.apache.geode.pdx.PdxSerializer + */ + @Nullable + protected PdxSerializer getCustomPdxSerializer(@NonNull PersistentProperty property) { + + Map customPdxSerializers = getCustomPdxSerializers(); + + PdxSerializer customPdxSerializer = customPdxSerializers.get(property); + + customPdxSerializer = customPdxSerializer != null ? customPdxSerializer + : customPdxSerializers.get(toFullyQualifiedPropertyName(property)); + + customPdxSerializer = customPdxSerializer != null ? customPdxSerializer + : customPdxSerializers.get(property.getType()); + + return customPdxSerializer; + } + + /** + * Converts the entity {@link PersistentProperty} to a {@link String fully-qualified property name}. + * + * @param property {@link PersistentProperty} of the entity. + * @return the {@link String fully-qualified property name of the entity {@link PersistentProperty}. + * @see org.springframework.data.mapping.PersistentProperty + */ + @NonNull + String toFullyQualifiedPropertyName(@NonNull PersistentProperty property) { + return property.getOwner().getType().getName().concat(".").concat(property.getName()); + } + + /** + * @deprecated please use {@link #getCustomPdxSerializer(PersistentProperty)} instead. + */ + @Nullable + @Deprecated protected PdxSerializer getCustomSerializer(Class type) { - return getCustomSerializers().get(type); + return getCustomPdxSerializers().get(type); } /** @@ -203,7 +322,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * read by this {@link PdxSerializer}; must not be {@literal null}. * @see org.springframework.data.convert.EntityInstantiator */ - public void setGemfireInstantiators(EntityInstantiators entityInstantiators) { + public void setGemfireInstantiators(@NonNull EntityInstantiators entityInstantiators) { Assert.notNull(entityInstantiators, "EntityInstantiators are required"); @@ -219,7 +338,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * @see org.springframework.data.convert.EntityInstantiator * @see java.util.Map */ - public void setGemfireInstantiators(Map, EntityInstantiator> gemfireInstantiators) { + public void setGemfireInstantiators(@NonNull Map, EntityInstantiator> gemfireInstantiators) { setGemfireInstantiators(new EntityInstantiators(gemfireInstantiators)); } @@ -253,6 +372,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * @return a reference to the configured {@link Logger}. * @see org.slf4j.Logger */ + @NonNull protected Logger getLogger() { return this.logger; } @@ -264,6 +384,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * @return a reference to the configured {@link GemfireMappingContext mapping context} for Pivotal GemFire. * @see org.springframework.data.gemfire.mapping.GemfireMappingContext */ + @NonNull protected GemfireMappingContext getMappingContext() { return this.mappingContext; } @@ -276,7 +397,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity * @see #getPersistentEntity(Class) */ - protected GemfirePersistentEntity getPersistentEntity(Object entity) { + protected GemfirePersistentEntity getPersistentEntity(@NonNull Object entity) { return getPersistentEntity(entity.getClass()); } @@ -308,19 +429,19 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw if (isWritable(entity, persistentProperty)) { - PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType()); + PdxSerializer customPdxSerializer = getCustomPdxSerializer(persistentProperty); Object value = null; try { if (getLogger().isDebugEnabled()) { getLogger().debug(String.format("Setting property [%1$s] for entity [%2$s] of type [%3$s] from PDX%4$s", - persistentProperty.getName(), instance, type, (customSerializer != null ? - String.format(" using custom PdxSerializer [%1$s]", customSerializer) : ""))); + persistentProperty.getName(), instance, type, (customPdxSerializer != null ? + String.format(" using custom PdxSerializer [%1$s]", customPdxSerializer) : ""))); } - value = (customSerializer != null - ? customSerializer.fromData(persistentProperty.getType(), reader) + value = (customPdxSerializer != null + ? customPdxSerializer.fromData(persistentProperty.getType(), reader) : reader.readField(persistentProperty.getName())); if (getLogger().isDebugEnabled()) { @@ -332,8 +453,8 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw catch (Exception cause) { throw new MappingException(String.format( "While setting value [%1$s] of property [%2$s] for entity of type [%3$s] from PDX%4$s", - value, persistentProperty.getName(), type, (customSerializer != null ? - String.format(" using custom PdxSerializer [%14s]", customSerializer) : "")), cause); + value, persistentProperty.getName(), type, (customPdxSerializer != null ? + String.format(" using custom PdxSerializer [%1$s]", customPdxSerializer) : "")), cause); } } }); @@ -365,7 +486,7 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw if (isReadable(persistentProperty)) { - PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType()); + PdxSerializer customPdxSerializer = getCustomPdxSerializer(persistentProperty); Object propertyValue = null; @@ -376,12 +497,12 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw if (getLogger().isDebugEnabled()) { getLogger().debug(String.format("Serializing entity [%1$s] property [%2$s] value [%3$s] of type [%4$s] to PDX%5$s", entity.getType().getName(), persistentProperty.getName(), propertyValue, - ObjectUtils.nullSafeClassName(propertyValue), (customSerializer != null - ? String.format(" using custom PdxSerializer [%s]", customSerializer) : ""))); + ObjectUtils.nullSafeClassName(propertyValue), (customPdxSerializer != null + ? String.format(" using custom PdxSerializer [%s]", customPdxSerializer) : ""))); } - if (customSerializer != null) { - customSerializer.toData(propertyValue, writer); + if (customPdxSerializer != null) { + customPdxSerializer.toData(propertyValue, writer); } else { writer.writeField(persistentProperty.getName(), propertyValue, @@ -392,9 +513,9 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw throw new MappingException(String.format( "While serializing entity [%1$s] property [%2$s] value [%3$s] of type [%4$s] to PDX%5$s", entity.getType().getName(), persistentProperty.getName(), propertyValue, - ObjectUtils.nullSafeClassName(propertyValue), (customSerializer != null + ObjectUtils.nullSafeClassName(propertyValue), (customPdxSerializer != null ? String.format(" using custom PdxSerializer [%1$s].", - customSerializer.getClass().getName()) : "")), cause); + customPdxSerializer.getClass().getName()) : "")), cause); } } }); 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 a9efcc61..b591c235 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTests.java @@ -17,11 +17,24 @@ package org.springframework.data.gemfire.mapping; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.Month; +import java.util.Base64; +import java.util.Collections; +import java.util.Optional; import org.apache.geode.DataSerializable; import org.apache.geode.Instantiator; @@ -29,17 +42,24 @@ import org.apache.geode.cache.Cache; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.Region; +import org.apache.geode.pdx.PdxReader; +import org.apache.geode.pdx.PdxSerializer; +import org.apache.geode.pdx.PdxWriter; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Transient; import org.springframework.data.gemfire.GemfireUtils; import org.springframework.data.gemfire.repository.sample.Address; import org.springframework.data.gemfire.repository.sample.Person; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.Setter; /** @@ -57,8 +77,7 @@ public class MappingPdxSerializerIntegrationTests { @BeforeClass public static void setUp() { - MappingPdxSerializer serializer = - new MappingPdxSerializer(new GemfireMappingContext(), new DefaultConversionService()); + MappingPdxSerializer serializer = MappingPdxSerializer.newMappingPdxSerializer(); cache = new CacheFactory() .set("name", MappingPdxSerializerIntegrationTests.class.getSimpleName()) @@ -78,6 +97,11 @@ public class MappingPdxSerializerIntegrationTests { GemfireUtils.close(cache); } + @After + public void clearRegion() { + region.removeAll(region.keySet()); + } + @Test public void handlesEntityWithReadOnlyProperty() { @@ -126,7 +150,7 @@ public class MappingPdxSerializerIntegrationTests { @Test @SuppressWarnings("unchecked") - public void serializesAndDeserializesEntityCorrectly() { + public void serializesAndDeserializesEntity() { Address address = new Address(); @@ -137,6 +161,7 @@ public class MappingPdxSerializerIntegrationTests { Person person = new Person(1L, "Oliver", "Gierke"); person.address = address; + region.put(1L, person); Object result = region.get(1L); @@ -181,6 +206,138 @@ public class MappingPdxSerializerIntegrationTests { assertThat(reference.property.getValue()).isEqualTo("foo"); } + @Test + public void serializationUsesCustomPropertyNameBasedPdxSerializer() { + + PdxSerializer mockPasswordSerializer = mock(PdxSerializer.class); + + when(mockPasswordSerializer.toData(any(), any(PdxWriter.class))).thenAnswer(invocation -> { + + String password = invocation.getArgument(0); + + PdxWriter pdxWriter = invocation.getArgument(1); + + pdxWriter.writeByteArray("password", Base64.getEncoder().encode(password.getBytes())); + + return true; + }); + + when(mockPasswordSerializer.fromData(any(Class.class), any(PdxReader.class))).thenAnswer(invocation -> { + + PdxReader pdxReader = invocation.getArgument(1); + + return new String(pdxReader.readByteArray("password")); + }); + + User jonDoe = User.newUser("jdoe", "p@55w0rd!"); + + assertThat(jonDoe).isNotNull(); + assertThat(jonDoe.getName()).isEqualTo("jdoe"); + assertThat(jonDoe.getPassword()).isEqualTo("p@55w0rd!"); + + Optional.of(region.getRegionService()) + .filter(regionService -> regionService instanceof Cache) + .map(regionService -> ((Cache) regionService).getPdxSerializer()) + .filter(pdxSerializer -> pdxSerializer instanceof MappingPdxSerializer) + .ifPresent(pdxSerializer -> { + + String passwordPropertyName = User.class.getName().concat(".password"); + + ((MappingPdxSerializer) pdxSerializer) + .setCustomPdxSerializers(Collections.singletonMap(passwordPropertyName, mockPasswordSerializer)); + + }); + + region.put(4L, jonDoe); + + Object result = region.get(4L); + + assertThat(result).isInstanceOf(User.class); + assertThat(result).isNotSameAs(jonDoe); + + User jonDoeLoaded = (User) result; + + assertThat(jonDoeLoaded.getName()).isEqualTo(jonDoe.getName()); + assertThat(jonDoeLoaded.getPassword()).describedAs("Password was [%s]", jonDoeLoaded.getPassword()) + .isNotEqualTo(jonDoe.getPassword()); + assertThat(new String(Base64.getDecoder().decode(jonDoeLoaded.getPassword()))).isEqualTo(jonDoe.getPassword()); + + verify(mockPasswordSerializer, atLeastOnce()).toData(eq("p@55w0rd!"), isA(PdxWriter.class)); + + verify(mockPasswordSerializer, times(1)) + .fromData(eq(String.class), isA(PdxReader.class)); + } + + @Test + public void serializationUsesCustomPropertyTypeBasedPdxSerializer() { + + PdxSerializer mockCreditCardSerializer = mock(PdxSerializer.class); + + when(mockCreditCardSerializer.toData(any(), any(PdxWriter.class))).thenAnswer(invocation -> { + + CreditCard creditCard = invocation.getArgument(0); + + PdxWriter pdxWriter = invocation.getArgument(1); + + pdxWriter.writeLong("creditCard.expirationDate", creditCard.getExpirationDate().toEpochDay()); + pdxWriter.writeByteArray("creditCard.number", + Base64.getEncoder().encode(creditCard.getNumber().getBytes())); + pdxWriter.writeString("creditCard.type", creditCard.getType().name()); + + return true; + }); + + when(mockCreditCardSerializer.fromData(any(Class.class), any(PdxReader.class))).thenAnswer(invocation -> { + + PdxReader pdxReader = invocation.getArgument(1); + + LocalDate creditCardExpirationDate = + LocalDate.ofEpochDay(pdxReader.readLong("creditCard.expirationDate")); + + String creditCardNumber = + new String(Base64.getDecoder().decode(pdxReader.readByteArray("creditCard.number"))); + + creditCardNumber = "xxxx-".concat(creditCardNumber.substring(creditCardNumber.length() - 4)); + + CreditCard.Type creditCardType = CreditCard.Type.valueOf(pdxReader.readString("creditCard.type")); + + return CreditCard.of(creditCardExpirationDate, creditCardNumber, creditCardType); + }); + + Optional.of(region.getRegionService()) + .filter(regionService -> regionService instanceof Cache) + .map(regionService -> ((Cache) regionService).getPdxSerializer()) + .filter(pdxSerializer -> pdxSerializer instanceof MappingPdxSerializer) + .ifPresent(pdxSerializer -> ((MappingPdxSerializer) pdxSerializer) + .setCustomPdxSerializers(Collections.singletonMap(CreditCard.class, mockCreditCardSerializer))); + + CreditCard creditCard = CreditCard.of(LocalDate.of(2020, Month.FEBRUARY, 12), + "8842-6789-4186-7981", CreditCard.Type.VISA); + + Customer jonDoe = Customer.newCustomer(creditCard, "Jon Doe"); + + region.put(8L, jonDoe); + + Object result = region.get(8L); + + assertThat(result).isInstanceOf(Customer.class); + assertThat(result).isNotSameAs(jonDoe); + + Customer jonDoeLoaded = (Customer) result; + + assertThat(jonDoeLoaded.getName()).isEqualTo(jonDoe.getName()); + assertThat(jonDoeLoaded.getCreditCard()).isNotEqualTo(jonDoe.getCreditCard()); + assertThat(jonDoeLoaded.getCreditCard().getExpirationDate()) + .isEqualTo(jonDoe.getCreditCard().getExpirationDate()); + assertThat(jonDoeLoaded.getCreditCard().getNumber()).isEqualTo("xxxx-7981"); + assertThat(jonDoeLoaded.getCreditCard().getType()).isEqualTo(jonDoe.getCreditCard().getType()); + + verify(mockCreditCardSerializer, atLeastOnce()).toData(eq(creditCard), isA(PdxWriter.class)); + + verify(mockCreditCardSerializer, times(1)) + .fromData(eq(CreditCard.class), isA(PdxReader.class)); + } + @SuppressWarnings({ "serial", "unused" }) public static class PersonWithDataSerializableProperty extends Person { @@ -246,10 +403,11 @@ public class MappingPdxSerializerIntegrationTests { @Setter String name; - // TODO: if there is not setter, then effectively this field/property is read-only + // TODO: if there is no setter, then effectively this field/property is read-only // and should not require the @ReadOnlyProperty @ReadOnlyProperty Object processId; + } @Getter @Setter @@ -261,5 +419,47 @@ public class MappingPdxSerializerIntegrationTests { @Transient private Object valueTwo; + + } + + @Data + @AllArgsConstructor(staticName = "newUser") + static class User { + + String name; + String password; + + @SuppressWarnings("unused") + User() {} + + } + + @Data + @AllArgsConstructor(staticName = "newCustomer") + static class Customer { + + CreditCard creditCard; + String name; + + @SuppressWarnings("unused") + Customer() {} + + } + + @Data + @RequiredArgsConstructor(staticName = "of") + static class CreditCard { + + @NonNull LocalDate expirationDate; + @NonNull String number; + @NonNull Type type; + + enum Type { + + AMERICAN_EXPRESS, + MASTER_CARD, + VISA, + + } } } 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 413c69ec..2cf60f34 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -49,8 +50,10 @@ import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiators; 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 org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.model.ParameterValueProvider; /** @@ -86,9 +89,6 @@ public class MappingPdxSerializerUnitTests { @Mock PdxReader mockReader; - @Mock - PdxSerializer mockAddressSerializer; - @Mock PdxWriter mockWriter; @@ -98,7 +98,10 @@ public class MappingPdxSerializerUnitTests { this.conversionService = new GenericConversionService(); this.mappingContext = new GemfireMappingContext(); this.pdxSerializer = new MappingPdxSerializer(this.mappingContext, this.conversionService); - this.pdxSerializer.setCustomSerializers(Collections.singletonMap(Address.class, this.mockAddressSerializer)); + } + + private String toFullyQualifiedPropertyName(PersistentProperty persistentProperty) { + return this.pdxSerializer.toFullyQualifiedPropertyName(persistentProperty); } @Test @@ -107,21 +110,22 @@ public class MappingPdxSerializerUnitTests { MappingPdxSerializer pdxSerializer = new MappingPdxSerializer(); assertThat(pdxSerializer.getConversionService()).isInstanceOf(DefaultConversionService.class); - assertThat(pdxSerializer.getCustomSerializers()).isEmpty(); + assertThat(pdxSerializer.getCustomPdxSerializers()).isEmpty(); assertThat(pdxSerializer.getGemfireInstantiators()).isInstanceOf(EntityInstantiators.class); assertThat(pdxSerializer.getMappingContext()).isInstanceOf(GemfireMappingContext.class); } @Test - public void constructMappingPdxSerializer() { + public void constructMappingPdxSerializerWithProvidedMappingContextAndConversionService() { ConversionService mockConversionService = mock(ConversionService.class); + GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); MappingPdxSerializer pdxSerializer = new MappingPdxSerializer(mockMappingContext, mockConversionService); assertThat(pdxSerializer.getConversionService()).isEqualTo(mockConversionService); - assertThat(pdxSerializer.getCustomSerializers()).isEmpty(); + assertThat(pdxSerializer.getCustomPdxSerializers()).isEmpty(); assertThat(pdxSerializer.getGemfireInstantiators()).isInstanceOf(EntityInstantiators.class); assertThat(pdxSerializer.getMappingContext()).isEqualTo(mockMappingContext); } @@ -160,6 +164,7 @@ public class MappingPdxSerializerUnitTests { public void createMappingPdxSerializer() { ConversionService mockConversionService = mock(ConversionService.class); + GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); MappingPdxSerializer pdxSerializer = MappingPdxSerializer.create(mockMappingContext, mockConversionService); @@ -170,19 +175,19 @@ public class MappingPdxSerializerUnitTests { } @Test - public void createWithNullConversionService() { + public void createMappingPdxSerializerWithNullConversionService() { GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); MappingPdxSerializer pdxSerializer = MappingPdxSerializer.create(mockMappingContext, null); assertThat(pdxSerializer).isNotNull(); - assertThat(pdxSerializer.getConversionService()).isInstanceOf(ConversionService.class); + assertThat(pdxSerializer.getConversionService()).isInstanceOf(DefaultConversionService.class); assertThat(pdxSerializer.getMappingContext()).isEqualTo(mockMappingContext); } @Test - public void createWithNullMappingContext() { + public void createMappingPdxSerializerWithNullMappingContext() { ConversionService mockConversionService = mock(ConversionService.class); @@ -194,31 +199,31 @@ public class MappingPdxSerializerUnitTests { } @Test - public void createWithNullConversionServiceAndNullMappingContext() { + public void createMappingPdxSerializerWithNullConversionServiceAndNullMappingContext() { MappingPdxSerializer pdxSerializer = MappingPdxSerializer.create(null, null); assertThat(pdxSerializer).isNotNull(); - assertThat(pdxSerializer.getConversionService()).isInstanceOf(ConversionService.class); + assertThat(pdxSerializer.getConversionService()).isInstanceOf(DefaultConversionService.class); assertThat(pdxSerializer.getMappingContext()).isInstanceOf(GemfireMappingContext.class); } @Test - public void setCustomSerializersWithMappingOfClassTypesToPdxSerializers() { + public void setCustomPdxSerializersWithMappingOfClassTypesToPdxSerializers() { - Map, PdxSerializer> customSerializers = + Map, PdxSerializer> customPdxSerializers = Collections.singletonMap(Person.class, mock(PdxSerializer.class)); - this.pdxSerializer.setCustomSerializers(customSerializers); + this.pdxSerializer.setCustomPdxSerializers(customPdxSerializers); - assertThat(this.pdxSerializer.getCustomSerializers()).isEqualTo(customSerializers); + assertThat(this.pdxSerializer.getCustomPdxSerializers()).isEqualTo(customPdxSerializers); } @Test(expected = IllegalArgumentException.class) - public void setCustomSerializersToNull() { + public void setCustomPdxSerializersToNull() { try { - this.pdxSerializer.setCustomSerializers(null); + this.pdxSerializer.setCustomPdxSerializers(null); } catch (IllegalArgumentException expected) { @@ -230,29 +235,114 @@ public class MappingPdxSerializerUnitTests { } @Test + @SuppressWarnings("all") + public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForProperty() { + + PdxSerializer mockNamedSerializer = mock(PdxSerializer.class); + PdxSerializer mockPropertySerializer = 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(addressProperty, mockPropertySerializer) + .put(toFullyQualifiedPropertyName(addressProperty), mockNamedSerializer) + .put(Address.class, mockTypedSerializer) + .build()); + + assertThat(this.pdxSerializer.getCustomPdxSerializer(addressProperty)).isEqualTo(mockPropertySerializer); + } + + @Test + @SuppressWarnings("all") + public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForPropertyName() { + + 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(toFullyQualifiedPropertyName(addressProperty), mockNamedSerializer) + .put(Address.class, mockTypedSerializer) + .build()); + + assertThat(this.pdxSerializer.getCustomPdxSerializer(addressProperty)).isEqualTo(mockNamedSerializer); + } + + @Test + @SuppressWarnings("all") + public void getCustomPdxSerializerForMappedPersistentPropertyReturnsSerializerForPropertyType() { + + PdxSerializer mockNamedSerializer = mock(PdxSerializer.class); + PdxSerializer mockTypedSerializer = mock(PdxSerializer.class); + + Map customPdxSerializers = new HashMap<>(); + + PersistentEntity personEntity = this.mappingContext.getPersistentEntity(Person.class); + + PersistentProperty addressProperty = personEntity.getPersistentProperty("address"); + + customPdxSerializers.put("example.Type.address", mockNamedSerializer); + customPdxSerializers.put(Address.class, mockTypedSerializer); + + this.pdxSerializer.setCustomPdxSerializers(customPdxSerializers); + + assertThat(this.pdxSerializer.getCustomPdxSerializer(addressProperty)).isEqualTo(mockTypedSerializer); + } + + @Test + @SuppressWarnings("all") + public void getCustomPdxSerializerForUnmappedPersistentPropertyReturnsNull() { + + PersistentEntity personEntity = this.mappingContext.getPersistentEntity(Person.class); + + PersistentProperty addressProperty = personEntity.getPersistentProperty("address"); + + assertThat(this.pdxSerializer.getCustomPdxSerializers()).isEmpty(); + assertThat(this.pdxSerializer.getCustomPdxSerializer(addressProperty)).isNull(); + } + + @Test + @SuppressWarnings("deprecation") public void getCustomSerializerForMappedType() { PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); - Map, PdxSerializer> customSerializers = Collections.singletonMap(Person.class, mockPdxSerializer); - - this.pdxSerializer.setCustomSerializers(customSerializers); + this.pdxSerializer.setCustomPdxSerializers(Collections.singletonMap(Person.class, mockPdxSerializer)); assertThat(this.pdxSerializer.getCustomSerializer(Person.class)).isEqualTo(mockPdxSerializer); } @Test - public void getCustomSerializerForNonMappedType() { - - PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); - - Map, PdxSerializer> customSerializers = Collections.singletonMap(Person.class, mockPdxSerializer); - - this.pdxSerializer.setCustomSerializers(customSerializers); - + @SuppressWarnings("deprecation") + public void getCustomSerializerForUnmappedTypeReturnsNull() { + assertThat(this.pdxSerializer.getCustomPdxSerializers()).isEmpty(); assertThat(this.pdxSerializer.getCustomSerializer(Address.class)).isNull(); } + @Test + public void toFullyQualifiedPropertyName() { + + PersistentEntity mockEntity = mock(PersistentEntity.class); + PersistentProperty mockProperty = mock(PersistentProperty.class); + + when(mockProperty.getName()).thenReturn("mockProperty"); + when(mockProperty.getOwner()).thenReturn(mockEntity); + when(mockEntity.getType()).thenReturn(Person.class); + + assertThat(this.pdxSerializer.toFullyQualifiedPropertyName(mockProperty)) + .isEqualTo(Person.class.getName().concat(".mockProperty")); + + verify(mockEntity, times(1)).getType(); + verify(mockProperty, times(1)).getName(); + verify(mockProperty, times(1)).getOwner(); + } + @Test public void setGemfireInstantiatorsWithEntityInstantiators() { @@ -309,14 +399,11 @@ public class MappingPdxSerializerUnitTests { EntityInstantiator mockEntityInstantiator = mock(EntityInstantiator.class); - Map, EntityInstantiator> entityInstantiators = - Collections.singletonMap(Person.class, mockEntityInstantiator); - PersistentEntity mockEntity = mock(PersistentEntity.class); when(mockEntity.getType()).thenReturn(Person.class); - this.pdxSerializer.setGemfireInstantiators(entityInstantiators); + this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockEntityInstantiator)); assertThat(this.pdxSerializer.getInstantiatorFor(mockEntity)).isEqualTo(mockEntityInstantiator); @@ -329,14 +416,11 @@ public class MappingPdxSerializerUnitTests { EntityInstantiator mockEntityInstantiator = mock(EntityInstantiator.class); - Map, EntityInstantiator> entityInstantiators = - Collections.singletonMap(Person.class, mockEntityInstantiator); - PersistentEntity mockEntity = mock(PersistentEntity.class); when(mockEntity.getType()).thenReturn(Address.class); - this.pdxSerializer.setGemfireInstantiators(entityInstantiators); + this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockEntityInstantiator)); assertThat(this.pdxSerializer.getInstantiatorFor(mockEntity)).isNotEqualTo(mockEntityInstantiator); @@ -439,7 +523,7 @@ public class MappingPdxSerializerUnitTests { @Test @SuppressWarnings("unchecked") - public void fromDataDeserializesPdxAndMapsToApplicationDomainObject() { + public void fromDataDeserializesPdxBytesAndMapsToApplicationDomainObject() { Address expectedAddress = new Address(); @@ -447,16 +531,19 @@ public class MappingPdxSerializerUnitTests { expectedAddress.city = "Portland"; expectedAddress.zipCode = "12345"; - when(mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) + PdxSerializer mockAddressSerializer = mock(PdxSerializer.class); + + when(this.mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) .thenReturn(new Person(null, null, null)); - when(mockReader.readField(eq("id"))).thenReturn(1L); - when(mockReader.readField(eq("firstname"))).thenReturn("Jon"); - when(mockReader.readField(eq("lastname"))).thenReturn("Doe"); - when(mockAddressSerializer.fromData(eq(Address.class), eq(mockReader))).thenReturn(expectedAddress); + when(this.mockReader.readField(eq("id"))).thenReturn(1L); + when(this.mockReader.readField(eq("firstname"))).thenReturn("Jon"); + when(this.mockReader.readField(eq("lastname"))).thenReturn("Doe"); + when(mockAddressSerializer.fromData(eq(Address.class), eq(this.mockReader))).thenReturn(expectedAddress); - this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator)); + this.pdxSerializer.setCustomPdxSerializers(Collections.singletonMap(Address.class, mockAddressSerializer)); + this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, this.mockInstantiator)); - Object obj = this.pdxSerializer.fromData(Person.class, mockReader); + Object obj = this.pdxSerializer.fromData(Person.class, this.mockReader); assertThat(obj).isInstanceOf(Person.class); @@ -467,26 +554,27 @@ public class MappingPdxSerializerUnitTests { assertThat(jonDoe.getFirstname()).isEqualTo("Jon"); assertThat(jonDoe.getLastname()).isEqualTo("Doe"); - verify(mockInstantiator, times(1)) + verify(this.mockInstantiator, times(1)) .createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class)); - verify(mockReader, times(1)).readField(eq("id")); - verify(mockReader, times(1)).readField(eq("firstname")); - verify(mockReader, times(1)).readField(eq("lastname")); - verify(mockAddressSerializer, times(1)).fromData(eq(Address.class), eq(mockReader)); + verify(this.mockReader, times(1)).readField(eq("id")); + verify(this.mockReader, times(1)).readField(eq("firstname")); + verify(this.mockReader, times(1)).readField(eq("lastname")); + verify(mockAddressSerializer, times(1)) + .fromData(eq(Address.class), eq(this.mockReader)); } @Test(expected = MappingException.class) @SuppressWarnings("unchecked") - public void fromDataHandlesExceptionProperly() { + public void fromDataHandlesException() { - when(mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) + when(this.mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) .thenReturn(new Person(null, null, null)); - when(mockReader.readField(eq("id"))).thenThrow(newIllegalArgumentException("test")); + when(this.mockReader.readField(eq("id"))).thenThrow(newIllegalArgumentException("test")); try { - this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator)); - this.pdxSerializer.fromData(Person.class, mockReader); + this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, this.mockInstantiator)); + this.pdxSerializer.fromData(Person.class, this.mockReader); } catch (MappingException expected) { @@ -498,10 +586,10 @@ public class MappingPdxSerializerUnitTests { throw expected; } finally { - verify(mockInstantiator, times(1)) + verify(this.mockInstantiator, times(1)) .createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class)); - verify(mockReader, times(1)).readField(eq("id")); + verify(this.mockReader, times(1)).readField(eq("id")); } } @@ -515,20 +603,23 @@ public class MappingPdxSerializerUnitTests { address.city = "London"; address.zipCode = "01234"; + PdxSerializer mockAddressSerializer = mock(PdxSerializer.class); + Person person = new Person(1L, "Oliver", "Gierke"); person.address = address; - when(mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) + when(this.mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class))) .thenReturn(person); - this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator)); - this.pdxSerializer.fromData(Person.class, mockReader); + this.pdxSerializer.setCustomPdxSerializers(Collections.singletonMap(Address.class, mockAddressSerializer)); + this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, this.mockInstantiator)); + this.pdxSerializer.fromData(Person.class, this.mockReader); GemfirePersistentEntity persistentEntity = - Optional.ofNullable(mappingContext.getPersistentEntity(Person.class)).orElse(null); + Optional.ofNullable(this.mappingContext.getPersistentEntity(Person.class)).orElse(null); - verify(mockInstantiator, times(1)) + verify(this.mockInstantiator, times(1)) .createInstance(eq(persistentEntity), any(ParameterValueProvider.class)); verify(mockAddressSerializer, times(1)) @@ -544,23 +635,32 @@ public class MappingPdxSerializerUnitTests { address.city = "Portland"; address.zipCode = "12345"; + PdxSerializer mockAddressSerializer = mock(PdxSerializer.class); + Person jonDoe = new Person(1L, "Jon", "Doe"); jonDoe.address = address; - this.pdxSerializer.setCustomSerializers(Collections.singletonMap(Address.class, mockAddressSerializer)); + this.pdxSerializer.setCustomPdxSerializers(Collections.singletonMap(Address.class, mockAddressSerializer)); - assertThat(this.pdxSerializer.toData(jonDoe, mockWriter)).isTrue(); + assertThat(this.pdxSerializer.toData(jonDoe, this.mockWriter)).isTrue(); - verify(mockAddressSerializer, times(1)).toData(eq(address), eq(mockWriter)); - verify(mockWriter, times(1)).writeField(eq("id"), eq(1L), eq(Long.class)); - verify(mockWriter, times(1)).writeField(eq("firstname"), eq("Jon"), eq(String.class)); - verify(mockWriter, times(1)).writeField(eq("lastname"), eq("Doe"), eq(String.class)); - verify(mockWriter, times(1)).markIdentityField(eq("id")); + verify(mockAddressSerializer, times(1)).toData(eq(address), eq(this.mockWriter)); + + verify(this.mockWriter, times(1)) + .writeField(eq("id"), eq(1L), eq(Long.class)); + + verify(this.mockWriter, times(1)) + .writeField(eq("firstname"), eq("Jon"), eq(String.class)); + + verify(this.mockWriter, times(1)) + .writeField(eq("lastname"), eq("Doe"), eq(String.class)); + + verify(this.mockWriter, times(1)).markIdentityField(eq("id")); } @Test(expected = MappingException.class) - public void toDataHandlesExceptionProperly() { + public void toDataHandlesException() { Address address = new Address(); @@ -572,18 +672,19 @@ public class MappingPdxSerializerUnitTests { jonDoe.address = address; - when(mockWriter.writeField(eq("address"), eq(address), eq(Address.class))) + when(this.mockWriter.writeField(eq("address"), eq(address), eq(Address.class))) .thenThrow(newIllegalArgumentException("test")); try { - this.pdxSerializer.setCustomSerializers(Collections.emptyMap()); - this.pdxSerializer.toData(jonDoe, mockWriter); + this.pdxSerializer.setCustomPdxSerializers(Collections.emptyMap()); + this.pdxSerializer.toData(jonDoe, this.mockWriter); } catch (MappingException expected) { assertThat(expected).hasMessage("While serializing entity [%1$s] property [address]" + " value [100 Main St. Portland, 12345] of type [%2$s] to PDX", Person.class.getName(), Address.class.getName()); + assertThat(expected).hasCauseInstanceOf(IllegalArgumentException.class); assertThat(expected.getCause()).hasMessage("test"); assertThat(expected.getCause()).hasNoCause(); @@ -591,11 +692,20 @@ public class MappingPdxSerializerUnitTests { throw expected; } finally { - verify(mockWriter, atMost(1)).writeField(eq("id"), eq(1L), eq(Long.class)); - verify(mockWriter, atMost(1)).writeField(eq("firstname"), eq("Jon"), eq(String.class)); - verify(mockWriter, atMost(1)).writeField(eq("lastname"), eq("Doe"), eq(String.class)); - verify(mockWriter, times(1)).writeField(eq("address"), eq(address), eq(Address.class)); - verify(mockWriter, never()).markIdentityField(anyString()); + + verify(this.mockWriter, atMost(1)) + .writeField(eq("id"), eq(1L), eq(Long.class)); + + verify(this.mockWriter, atMost(1)) + .writeField(eq("firstname"), eq("Jon"), eq(String.class)); + + verify(this.mockWriter, atMost(1)) + .writeField(eq("lastname"), eq("Doe"), eq(String.class)); + + verify(this.mockWriter, times(1)) + .writeField(eq("address"), eq(address), eq(Address.class)); + + verify(this.mockWriter, never()).markIdentityField(anyString()); } } }