diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java index 76a74ef2..7cfe532d 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java @@ -25,6 +25,7 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.gemfire.mapping.MappingPdxSerializer; /** * The {@link EnablePdx} annotation marks a Spring {@link Configuration @Configuration} annotated {@link Class} @@ -61,6 +62,18 @@ public @interface EnablePdx { */ boolean ignoreUnreadFields() default PdxConfiguration.DEFAULT_IGNORE_UNREAD_FIELDS; + /** + * When using the Spring Data Geode's (SDG) {@link MappingPdxSerializer} most application domain {@link Class types} + * are included for Apache Geode PDX serialization, by default. However, certain {@link Class types} are excluded + * by SDG's {@link MappingPdxSerializer}, such as {@literal java.*}, {@literal javax.*}, {@literal com.gemstone.*}, + * {@literal org.apache.geode.*} and {@literal org.springframework.*} {@link Class types}. This allows the default + * behavior to be overridden when and where necessary. + * + * @return an array of {@link Class types} to be handled by the {@link MappingPdxSerializer}, possibly overriding + * the excluded {@link Class types} by default. + */ + Class[] includeDomainTypes() default {}; + /** * Configures whether the type metadata for PDX objects is persisted to disk. * diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java index bf8fe3bc..21ddd673 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java @@ -16,7 +16,9 @@ package org.springframework.data.gemfire.config.annotation; import java.lang.annotation.Annotation; +import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; import org.apache.geode.cache.GemFireCache; import org.apache.geode.pdx.PdxSerializer; @@ -36,6 +38,7 @@ import org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFact import org.springframework.data.gemfire.mapping.GemfireMappingContext; import org.springframework.data.gemfire.mapping.MappingPdxSerializer; import org.springframework.data.gemfire.support.NoOpBeanFactoryPostProcessor; +import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.lang.NonNull; import org.springframework.util.StringUtils; @@ -57,6 +60,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.data.gemfire.CacheFactoryBean * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport * @see org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFactoryPostProcessor + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer * @see org.springframework.data.gemfire.support.NoOpBeanFactoryPostProcessor * @since 2.1.0 @@ -76,6 +80,8 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements private Boolean persistent; private Boolean readSerialized; + private Class[] includeDomainTypes = {}; + private String diskStoreName; private String serializerBeanName; @@ -106,6 +112,8 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements ? enablePdxAttributes.getBoolean("ignoreUnreadFields") : DEFAULT_IGNORE_UNREAD_FIELDS)); + setIncludeDomainTypes(enablePdxAttributes.getClassArray("includeDomainTypes")); + setPersistent(resolveProperty(pdxProperty("persistent"), enablePdxAttributes.containsKey("persistent") ? enablePdxAttributes.getBoolean("persistent") @@ -139,6 +147,14 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements return Boolean.TRUE.equals(this.ignoreUnreadFields); } + void setIncludeDomainTypes(Class[] includeDomainTypes) { + this.includeDomainTypes = includeDomainTypes; + } + + protected Class[] getIncludeDomainTypes() { + return ArrayUtils.nullSafeArray(this.includeDomainTypes, Class.class); + } + void setPersistent(Boolean persistent) { this.persistent = persistent; } @@ -189,7 +205,7 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements * @see org.springframework.data.gemfire.CacheFactoryBean * @see Geode PDX Serialization */ - protected void configurePdx(CacheFactoryBean cacheFactoryBean) { + protected void configurePdx(@NonNull CacheFactoryBean cacheFactoryBean) { getDiskStoreName().ifPresent(cacheFactoryBean::setPdxDiskStoreName); @@ -240,15 +256,20 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements * @see org.apache.geode.pdx.PdxSerializer * @see #getBeanFactory() */ - @NonNull - protected PdxSerializer resolvePdxSerializer() { + protected @NonNull PdxSerializer resolvePdxSerializer() { BeanFactory beanFactory = getBeanFactory(); - return getSerializerBeanName() + PdxSerializer serializer = getSerializerBeanName() .filter(beanFactory::containsBean) .map(beanName -> beanFactory.getBean(beanName, PdxSerializer.class)) .orElseGet(this::newPdxSerializer); + + if (serializer instanceof MappingPdxSerializer mappingSerializer) { + mappingSerializer.setIncludeTypeFilters(buildIncludeTypeFilters()); + } + + return serializer; } /** @@ -258,11 +279,23 @@ public class PdxConfiguration extends AbstractAnnotationConfigSupport implements * @return a new instance of {@link PdxSerializer}. * @see org.apache.geode.pdx.PdxSerializer */ - @NonNull @SuppressWarnings("unchecked") - protected T newPdxSerializer() { + protected @NonNull T newPdxSerializer() { return (T) MappingPdxSerializer.create(resolveMappingContext().orElse(null), resolveConversionService().orElse(null)); } + + private @NonNull Predicate> buildIncludeTypeFilters() { + + Predicate> includeTypeFilter = type -> false; + + for (Class domainType : getIncludeDomainTypes()) { + if (Objects.nonNull(domainType)) { + includeTypeFilter = includeTypeFilter.or(type -> domainType.isAssignableFrom(type)); + } + } + + return includeTypeFilter; + } } diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java index dd67a58a..6b40326c 100644 --- a/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java @@ -33,6 +33,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import com.gemstone.gemfire.TestGemStoneGemFireType; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Spy; @@ -92,6 +94,7 @@ public class EnablePdxConfigurationUnitTests { annotationAttributes.put("diskStoreName", "MockDiskStore"); annotationAttributes.put("ignoreUnreadFields", Boolean.TRUE); + annotationAttributes.put("includeDomainTypes", new Class[] { TestGemStoneGemFireType.class }); annotationAttributes.put("persistent", Boolean.TRUE); annotationAttributes.put("readSerialized", Boolean.TRUE); annotationAttributes.put("serializerBeanName", "MockPdxSerializer"); @@ -106,6 +109,7 @@ public class EnablePdxConfigurationUnitTests { assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); assertThat(this.pdxConfiguration.getDiskStoreName().orElse(null)).isEqualTo("MockDiskStore"); assertThat(this.pdxConfiguration.isIgnoreUnreadFields()).isTrue(); + assertThat(this.pdxConfiguration.getIncludeDomainTypes()).containsExactly(TestGemStoneGemFireType.class); assertThat(this.pdxConfiguration.isPersistent()).isTrue(); assertThat(this.pdxConfiguration.isReadSerialized()).isTrue(); assertThat(this.pdxConfiguration.getSerializerBeanName().orElse(null)).isEqualTo("MockPdxSerializer"); @@ -126,6 +130,7 @@ public class EnablePdxConfigurationUnitTests { assertThat(this.pdxConfiguration.getDiskStoreName().isPresent()).isFalse(); assertThat(this.pdxConfiguration.isIgnoreUnreadFields()).isFalse(); + assertThat(this.pdxConfiguration.getIncludeDomainTypes()).isEmpty(); assertThat(this.pdxConfiguration.isPersistent()).isFalse(); assertThat(this.pdxConfiguration.isReadSerialized()).isFalse(); assertThat(this.pdxConfiguration.getSerializerBeanName().isPresent()).isFalse(); @@ -168,6 +173,7 @@ public class EnablePdxConfigurationUnitTests { verify(this.pdxConfiguration, times(1)).getDiskStoreName(); verify(this.pdxConfiguration, times(1)).isIgnoreUnreadFields(); + verify(this.pdxConfiguration, never()).getIncludeDomainTypes(); verify(this.pdxConfiguration, times(1)).isPersistent(); verify(this.pdxConfiguration, times(1)).isReadSerialized(); verify(this.pdxConfiguration, times(1)).getSerializerBeanName();