diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java index 2055ac4e..b5ecb753 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java @@ -33,17 +33,12 @@ import org.apache.geode.cache.TransactionWriter; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.server.CacheServer; import org.apache.geode.cache.util.GatewayConflictResolver; -import org.apache.geode.pdx.PdxSerializer; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.CacheFactoryBean; @@ -51,9 +46,6 @@ import org.springframework.data.gemfire.config.annotation.support.AbstractAnnota import org.springframework.data.gemfire.config.support.CustomEditorBeanFactoryPostProcessor; import org.springframework.data.gemfire.config.support.DefinedIndexesApplicationListener; import org.springframework.data.gemfire.config.support.DiskStoreDirectoryBeanPostProcessor; -import org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFactoryPostProcessor; -import org.springframework.data.gemfire.mapping.GemfireMappingContext; -import org.springframework.data.gemfire.mapping.MappingPdxSerializer; import org.springframework.data.gemfire.util.PropertiesBuilder; import org.springframework.util.StringUtils; @@ -72,7 +64,6 @@ import org.springframework.util.StringUtils; * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.server.CacheServer - * @see org.apache.geode.pdx.PdxSerializer * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.beans.factory.config.BeanDefinition * @see org.springframework.beans.factory.support.BeanDefinitionBuilder @@ -80,7 +71,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.ImportAware - * @see org.springframework.core.convert.ConversionService + * @see org.springframework.core.annotation.AnnotationAttributes * @see org.springframework.core.io.Resource * @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.data.gemfire.CacheFactoryBean @@ -89,19 +80,20 @@ import org.springframework.util.StringUtils; * @see org.springframework.data.gemfire.config.support.CustomEditorBeanFactoryPostProcessor * @see org.springframework.data.gemfire.config.support.DefinedIndexesApplicationListener * @see org.springframework.data.gemfire.config.support.DiskStoreDirectoryBeanPostProcessor - * @see org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFactoryPostProcessor - * @see org.springframework.data.gemfire.mapping.GemfireMappingContext - * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer * @since 1.9.0 */ @Configuration @SuppressWarnings("unused") public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { - private static final AtomicBoolean CUSTOM_EDITORS_REGISTERED = new AtomicBoolean(false); - private static final AtomicBoolean DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED = new AtomicBoolean(false); - private static final AtomicBoolean DISK_STORE_DIRECTORY_BEAN_POST_PROCESSOR_REGISTERED = new AtomicBoolean(false); - private static final AtomicBoolean PDX_DISK_STORE_AWARE_BEAN_FACTORY_POST_PROCESSOR_REGISTERED = new AtomicBoolean(false); + private static final AtomicBoolean CUSTOM_EDITORS_BEAN_FACTORY_POST_PROCESSOR_REGISTERED = + new AtomicBoolean(false); + + private static final AtomicBoolean DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED = + new AtomicBoolean(false); + + private static final AtomicBoolean DISK_STORE_DIRECTORY_BEAN_POST_PROCESSOR_REGISTERED = + new AtomicBoolean(false); protected static final boolean DEFAULT_CLOSE = true; protected static final boolean DEFAULT_COPY_ON_READ = false; @@ -117,13 +109,9 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi private boolean copyOnRead = DEFAULT_COPY_ON_READ; private boolean useBeanFactoryLocator = DEFAULT_USE_BEAN_FACTORY_LOCATOR; - private Boolean pdxIgnoreUnreadFields; - private Boolean pdxPersistent; - private Boolean pdxReadSerialized; - private DynamicRegionSupport dynamicRegionSupport; - private Integer mcastPort = 0; + private Integer mcastPort = DEFAULT_MCAST_PORT; private Float criticalHeapPercentage; private Float criticalOffHeapPercentage; @@ -132,14 +120,9 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi private GatewayConflictResolver gatewayConflictResolver; - @Autowired(required = false) - private GemfireMappingContext mappingContext; - private List jndiDataSources; private List transactionListeners; - private PdxSerializer pdxSerializer; - private PropertiesBuilder customGemFireProperties = PropertiesBuilder.create(); private Resource cacheXml; @@ -147,7 +130,6 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi private String locators = DEFAULT_LOCATORS; private String logLevel = DEFAULT_LOG_LEVEL; private String name; - private String pdxDiskStoreName; private String startLocator; private TransactionWriter transactionWriter; @@ -197,8 +179,7 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi configureInfrastructure(importMetadata); configureCache(importMetadata); - configurePdx(importMetadata); - configureTheRest(importMetadata); + configureOptional(importMetadata); } /** @@ -216,16 +197,14 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi registerDiskStoreDirectoryBeanPostProcessor(importMetadata); } - /* (non-Javadoc) */ private void registerCustomEditorBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { - if (CUSTOM_EDITORS_REGISTERED.compareAndSet(false, true)) { + if (CUSTOM_EDITORS_BEAN_FACTORY_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { register(BeanDefinitionBuilder.rootBeanDefinition(CustomEditorBeanFactoryPostProcessor.class) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); } } - /* (non-Javadoc) */ private void registerDefinedIndexesApplicationListener(AnnotationMetadata importMetadata) { if (DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED.compareAndSet(false, true)) { @@ -234,7 +213,6 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi } } - /* (non-Javadoc) */ private void registerDiskStoreDirectoryBeanPostProcessor(AnnotationMetadata importMetadata) { if (DISK_STORE_DIRECTORY_BEAN_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { @@ -303,117 +281,14 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi } /** - * Configures Pivotal GemFire/Apache Geode cache PDX Serialization. - * - * @param importMetadata {@link AnnotationMetadata} containing PDX meta-data used to configure the cache - * with PDX de/serialization capabilities. - * @see org.springframework.core.type.AnnotationMetadata - * @see Pivotal GemFire PDX Serialization - */ - protected void configurePdx(AnnotationMetadata importMetadata) { - - String enablePdxTypeName = EnablePdx.class.getName(); - - if (importMetadata.hasAnnotation(enablePdxTypeName)) { - - AnnotationAttributes enablePdxAttributes = getAnnotationAttributes(importMetadata, enablePdxTypeName); - - setPdxDiskStoreName(resolveProperty(pdxProperty("disk-store-name"), - (String) enablePdxAttributes.get("diskStoreName"))); - - setPdxIgnoreUnreadFields(resolveProperty(pdxProperty("ignore-unread-fields"), - Boolean.TRUE.equals(enablePdxAttributes.get("ignoreUnreadFields")))); - - setPdxPersistent(resolveProperty(pdxProperty("persistent"), - Boolean.TRUE.equals(enablePdxAttributes.get("persistent")))); - - setPdxReadSerialized(resolveProperty(pdxProperty("read-serialized"), - Boolean.TRUE.equals(enablePdxAttributes.get("readSerialized")))); - - setPdxSerializer(resolvePdxSerializer((String) enablePdxAttributes.get("serializerBeanName"))); - - registerPdxDiskStoreAwareBeanFactoryPostProcessor(importMetadata); - } - } - - /** - * Resolves the {@link PdxSerializer} used to configure the cache for PDX De/Serialization. - * - * @param pdxSerializerBeanName {@link String} containing the name of a Spring bean - * implementing the {@link PdxSerializer} interface. - * @return the resolved {@link PdxSerializer} from configuration. - * @see org.apache.geode.pdx.PdxSerializer - * @see #newPdxSerializer(BeanFactory) - * @see #getBeanFactory() - */ - protected PdxSerializer resolvePdxSerializer(String pdxSerializerBeanName) { - - BeanFactory beanFactory = getBeanFactory(); - - return Optional.ofNullable(pdxSerializerBeanName) - .filter(beanFactory::containsBean) - .map(beanName -> beanFactory.getBean(beanName, PdxSerializer.class)) - .orElseGet(() -> Optional.ofNullable(getPdxSerializer()).orElseGet(() -> newPdxSerializer(beanFactory))); - } - - /** - * Constructs a new instance of {@link PdxSerializer}. - * - * @param {@link Class} type of the {@link PdxSerializer}. - * @return a new instance of {@link PdxSerializer}. - * @see org.apache.geode.pdx.PdxSerializer - * @see #newPdxSerializer(BeanFactory) - */ - @SuppressWarnings("unchecked") - protected T newPdxSerializer() { - return newPdxSerializer(getBeanFactory()); - } - - /** - * Constructs a new instance of {@link MappingPdxSerializer}. - * - * @param {@link Class} type of the {@link PdxSerializer}; this method returns a {@link MappingPdxSerializer}. - * @param beanFactory {@link BeanFactory} used to get an instance of {@link ConversionService} - * used by the {@link MappingPdxSerializer}. - * @return a new instance of {@link MappingPdxSerializer}. - * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer - * @see org.springframework.beans.factory.BeanFactory - * @see org.apache.geode.pdx.PdxSerializer - */ - @SuppressWarnings("unchecked") - protected T newPdxSerializer(BeanFactory beanFactory) { - - Optional conversionService = Optional.ofNullable(beanFactory) - .filter(it -> it instanceof ConfigurableBeanFactory) - .map(it -> ((ConfigurableBeanFactory) it).getConversionService()); - - return (T) MappingPdxSerializer.create(this.mappingContext, conversionService.orElse(null)); - } - - /* (non-Javadoc) */ - private void registerPdxDiskStoreAwareBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { - - Optional.ofNullable(getPdxDiskStoreName()) - .filter(StringUtils::hasText) - .ifPresent(pdxDiskStoreName -> { - - if (PDX_DISK_STORE_AWARE_BEAN_FACTORY_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { - register(BeanDefinitionBuilder.rootBeanDefinition(PdxDiskStoreAwareBeanFactoryPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .addConstructorArgValue(pdxDiskStoreName) - .getBeanDefinition()); - } - }); - } - - /** +======= +>>>>>>> 09d8cc7... DATAGEODE-117 - Move configuration of PDX to a PdxConfiguration class imported by the EnablePdx annotation. * Callback method allowing developers to configure other cache or application specific configuration settings. * * @param importMetadata {@link AnnotationMetadata} containing meta-data used to configure the cache or application. * @see org.springframework.core.type.AnnotationMetadata */ - protected void configureTheRest(AnnotationMetadata importMetadata) { - } + protected void configureOptional(AnnotationMetadata importMetadata) { } /** * Constructs a new, initialized instance of {@link CacheFactoryBean} based on the Spring application's @@ -477,11 +352,6 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi gemfireCache.setGatewayConflictResolver(getGatewayConflictResolver()); gemfireCache.setJndiDataSources(getJndiDataSources()); gemfireCache.setProperties(gemfireProperties()); - gemfireCache.setPdxDiskStoreName(getPdxDiskStoreName()); - gemfireCache.setPdxIgnoreUnreadFields(getPdxIgnoreUnreadFields()); - gemfireCache.setPdxPersistent(getPdxPersistent()); - gemfireCache.setPdxReadSerialized(getPdxReadSerialized()); - gemfireCache.setPdxSerializer(getPdxSerializer()); gemfireCache.setTransactionListeners(getTransactionListeners()); gemfireCache.setTransactionWriter(getTransactionWriter()); @@ -551,7 +421,7 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi protected boolean isTypedCacheApplication(Class annotationType, AnnotationMetadata importMetadata) { - return (annotationType.equals(getAnnotationType()) && importMetadata.hasAnnotation(getAnnotationTypeName())); + return annotationType.equals(getAnnotationType()) && importMetadata.hasAnnotation(getAnnotationTypeName()); } /** @@ -687,14 +557,6 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi return Optional.ofNullable(this.logLevel).orElse(DEFAULT_LOG_LEVEL); } - void setMappingContext(GemfireMappingContext mappingContext) { - this.mappingContext = mappingContext; - } - - protected GemfireMappingContext mappingContext() { - return this.mappingContext; - } - void setMcastPort(Integer mcastPort) { this.mcastPort = mcastPort; this.locators = DEFAULT_LOCATORS; @@ -712,46 +574,6 @@ public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfi return Optional.ofNullable(this.name).filter(StringUtils::hasText).orElseGet(this::toString); } - void setPdxDiskStoreName(String pdxDiskStoreName) { - this.pdxDiskStoreName = pdxDiskStoreName; - } - - protected String getPdxDiskStoreName() { - return this.pdxDiskStoreName; - } - - void setPdxIgnoreUnreadFields(Boolean pdxIgnoreUnreadFields) { - this.pdxIgnoreUnreadFields = pdxIgnoreUnreadFields; - } - - protected Boolean getPdxIgnoreUnreadFields() { - return this.pdxIgnoreUnreadFields; - } - - void setPdxPersistent(Boolean pdxPersistent) { - this.pdxPersistent = pdxPersistent; - } - - protected Boolean getPdxPersistent() { - return this.pdxPersistent; - } - - void setPdxReadSerialized(Boolean pdxReadSerialized) { - this.pdxReadSerialized = pdxReadSerialized; - } - - protected Boolean getPdxReadSerialized() { - return this.pdxReadSerialized; - } - - void setPdxSerializer(PdxSerializer pdxSerializer) { - this.pdxSerializer = pdxSerializer; - } - - protected PdxSerializer getPdxSerializer() { - return this.pdxSerializer; - } - void setStartLocator(String startLocator) { this.startLocator = startLocator; } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java index 1b928563..bf4df845 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java @@ -172,7 +172,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { * @see org.apache.geode.cache.server.CacheServer */ @Override - protected void configureTheRest(AnnotationMetadata importMetadata) { + protected void configureOptional(AnnotationMetadata importMetadata) { super.configureCache(importMetadata); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java index 5ae01401..4b30378c 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java @@ -154,7 +154,6 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { return gemfireCache; } - /* (non-Javadoc) */ @SuppressWarnings("all") private List resolveClientCacheConfigurers() { diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java index 052531e4..97b299b8 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePdx.java @@ -25,6 +25,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.type.AnnotationMetadata; /** @@ -34,13 +35,16 @@ import org.springframework.core.type.AnnotationMetadata; * * @author John Blum * @see java.lang.annotation.Annotation + * @see org.springframework.context.annotation.Import * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration#configurePdx(AnnotationMetadata) + * @see org.springframework.data.gemfire.config.annotation.PdxConfiguration * @since 1.9.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented +@Import(PdxConfiguration.class) @SuppressWarnings("unused") public @interface EnablePdx { @@ -49,7 +53,7 @@ public @interface EnablePdx { * * Use the {@literal spring.data.gemfire.pdx.disk-store-name} property in {@literal application.properties}. */ - String diskStoreName() default ""; + String diskStoreName() default PdxConfiguration.DEFAULT_PDX_DISK_STORE_NAME; /** * Configures whether pdx ignores fields that were unread during deserialization. @@ -58,7 +62,7 @@ public @interface EnablePdx { * * Use the {@literal spring.data.gemfire.pdx.ignore-unread-fields} property in {@literal application.properties}. */ - boolean ignoreUnreadFields() default false; + boolean ignoreUnreadFields() default PdxConfiguration.DEFAULT_IGNORE_UNREAD_FIELDS; /** * Configures whether the type metadata for PDX objects is persisted to disk. @@ -67,7 +71,7 @@ public @interface EnablePdx { * * Use the {@literal spring.data.gemfire.pdx.persistent} property in {@literal application.properties}. */ - boolean persistent() default false; + boolean persistent() default PdxConfiguration.DEFAULT_PERSISTENT; /** * Configures the object preference to {@link org.apache.geode.pdx.PdxInstance} type or {@link Object}. @@ -76,11 +80,13 @@ public @interface EnablePdx { * * Use the {@literal spring.data.gemfire.pdx.read-serialized} property in {@literal application.properties}. */ - boolean readSerialized() default false; + boolean readSerialized() default PdxConfiguration.DEFAULT_READ_SERIALIZED; /** * Configures the PDX serializer to be used by the cache to serialize object data. + * + * Use the {@literal spring.data.gemfire.pdx.serializer-bean-name} property in {@literal application.properties}. */ - String serializerBeanName() default ""; + String serializerBeanName() default PdxConfiguration.DEFAULT_PDX_SERIALIZER_BEAN_NAME; } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java new file mode 100644 index 00000000..a3145ab6 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/PdxConfiguration.java @@ -0,0 +1,269 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.config.annotation; + +import java.lang.annotation.Annotation; +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.pdx.PdxSerializer; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFactoryPostProcessor; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.MappingPdxSerializer; +import org.springframework.data.gemfire.support.NoOpBeanFactoryPostProcessor; +import org.springframework.lang.NonNull; +import org.springframework.util.StringUtils; + +/** + * The {@link PdxConfiguration} class is a Spring {@link Configuration} class that configures PDX + * on a {@link GemFireCache} instance. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.pdx.PdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.core.annotation.AnnotationAttributes + * @see org.springframework.core.type.AnnotationMetadata + * @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.MappingPdxSerializer + * @see org.springframework.data.gemfire.support.NoOpBeanFactoryPostProcessor + * @since 2.1.0 + */ +@SuppressWarnings("unused") +@Configuration("PdxConfiguration") +public class PdxConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + public static final boolean DEFAULT_IGNORE_UNREAD_FIELDS = false; + public static final boolean DEFAULT_PERSISTENT = false; + public static final boolean DEFAULT_READ_SERIALIZED = false; + + public static final String DEFAULT_PDX_DISK_STORE_NAME = ""; + public static final String DEFAULT_PDX_SERIALIZER_BEAN_NAME = ""; + + private Boolean ignoreUnreadFields; + private Boolean persistent; + private Boolean readSerialized; + + private String diskStoreName; + private String serializerBeanName; + + @Override + protected Class getAnnotationType() { + return EnablePdx.class; + } + + @Override + protected BeanFactory getBeanFactory() { + return super.getBeanFactory(); + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + + if (isAnnotationPresent(importMetadata)) { + + AnnotationAttributes enablePdxAttributes = getAnnotationAttributes(importMetadata); + + setDiskStoreName(resolveProperty(pdxProperty("disk-store-name"), + enablePdxAttributes.containsKey("diskStoreName") + ? enablePdxAttributes.getString("diskStoreName") + : null)); + + setIgnoreUnreadFields(resolveProperty(pdxProperty("ignore-unread-fields"), + enablePdxAttributes.containsKey("ignoreUnreadFields") + ? enablePdxAttributes.getBoolean("ignoreUnreadFields") + : DEFAULT_IGNORE_UNREAD_FIELDS)); + + setPersistent(resolveProperty(pdxProperty("persistent"), + enablePdxAttributes.containsKey("persistent") + ? enablePdxAttributes.getBoolean("persistent") + : null)); + + setReadSerialized(resolveProperty(pdxProperty("read-serialized"), + enablePdxAttributes.containsKey("readSerialized") + ? enablePdxAttributes.getBoolean("readSerialized") + : null)); + + setSerializerBeanName(resolveProperty(pdxProperty("serializer-bean-name"), + enablePdxAttributes.containsKey("serializerBeanName") + ? enablePdxAttributes.getString("serializerBeanName") + : null)); + } + } + + void setDiskStoreName(String diskStoreName) { + this.diskStoreName = diskStoreName; + } + + protected Optional getDiskStoreName() { + return Optional.ofNullable(this.diskStoreName).filter(StringUtils::hasText); + } + + void setIgnoreUnreadFields(Boolean ignoreUnreadFields) { + this.ignoreUnreadFields = ignoreUnreadFields; + } + + protected boolean isIgnoreUnreadFields() { + return Boolean.TRUE.equals(this.ignoreUnreadFields); + } + + void setPersistent(Boolean persistent) { + this.persistent = persistent; + } + + protected boolean isPersistent() { + return Boolean.TRUE.equals(this.persistent); + } + + void setReadSerialized(Boolean readSerialized) { + this.readSerialized = readSerialized; + } + + protected boolean isReadSerialized() { + return Boolean.TRUE.equals(this.readSerialized); + } + + void setSerializerBeanName(String serializerBeanName) { + this.serializerBeanName = serializerBeanName; + } + + protected Optional getSerializerBeanName() { + return Optional.ofNullable(this.serializerBeanName).filter(StringUtils::hasText); + } + + @Bean + BeanFactoryPostProcessor pdxDiskStoreAwareBeanFactoryPostProcessor() { + + return getDiskStoreName() + .map(PdxDiskStoreAwareBeanFactoryPostProcessor::new) + .orElse(NoOpBeanFactoryPostProcessor.INSTANCE); + } + + @Bean + ClientCacheConfigurer clientCachePdxConfigurer() { + return (beanName, clientCacheFactoryBean) -> configurePdx(clientCacheFactoryBean); + } + + @Bean + PeerCacheConfigurer peerCachePdxConfigurer() { + return (beanName, cacheFactoryBean) -> configurePdx(cacheFactoryBean); + } + + /** + * Configures Pivotal GemFire/Apache Geode cache PDX Serialization. + * + * @param cacheFactoryBean {@link CacheFactoryBean} instance on which to configure PDX. + * with PDX de/serialization capabilities. + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see GemFire PDX Serialization + */ + protected void configurePdx(CacheFactoryBean cacheFactoryBean) { + + getDiskStoreName().ifPresent(cacheFactoryBean::setPdxDiskStoreName); + + cacheFactoryBean.setPdxIgnoreUnreadFields(isIgnoreUnreadFields()); + cacheFactoryBean.setPdxPersistent(isPersistent()); + cacheFactoryBean.setPdxReadSerialized(isReadSerialized()); + cacheFactoryBean.setPdxSerializer(resolvePdxSerializer()); + } + + /** + * Resolves the configured {@link ConversionService} from the {@link BeanFactory}. + * + * @return an {@link Optional optionally} configured {@link ConversionService}. + * @see org.springframework.core.convert.ConversionService + * @see java.util.Optional + * @see #getBeanFactory() + */ + protected Optional resolveConversionService() { + + return Optional.of(getBeanFactory()) + .filter(it -> it instanceof ConfigurableBeanFactory) + .map(it -> ((ConfigurableBeanFactory) it).getConversionService()); + } + + /** + * Returns any configured {@link GemfireMappingContext} registered in the {@link BeanFactory}. + * + * @return an {@link Optional optionally} configured {@link GemfireMappingContext}. + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see java.util.Optional + * @see #getBeanFactory() + */ + protected Optional resolveMappingContext() { + + try { + return Optional.of(getBeanFactory().getBean(GemfireMappingContext.class)); + } + catch (Throwable ignore) { + return Optional.empty(); + } + } + + /** + * Resolves the {@link PdxSerializer} used to configure the cache for PDX Serialization. + * + * implementing the {@link PdxSerializer} interface. + * @return the resolved {@link PdxSerializer} from configuration. + * @see org.apache.geode.pdx.PdxSerializer + * @see #getBeanFactory() + */ + @NonNull + @SuppressWarnings("unchecked") + protected PdxSerializer resolvePdxSerializer() { + + BeanFactory beanFactory = getBeanFactory(); + + return getSerializerBeanName() + .filter(beanFactory::containsBean) + .map(beanName -> beanFactory.getBean(beanName, PdxSerializer.class)) + .orElseGet(this::newPdxSerializer); + } + + /** + * Constructs a new instance of {@link PdxSerializer}. + * + * @param {@link Class} type of the {@link PdxSerializer}. + * @return a new instance of {@link PdxSerializer}. + * @see org.apache.geode.pdx.PdxSerializer + */ + @NonNull + @SuppressWarnings("unchecked") + protected T newPdxSerializer() { + + return (T) MappingPdxSerializer.create(resolveMappingContext().orElse(null), + resolveConversionService().orElse(null)); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/support/PdxDiskStoreAwareBeanFactoryPostProcessor.java b/src/main/java/org/springframework/data/gemfire/config/support/PdxDiskStoreAwareBeanFactoryPostProcessor.java index 7011ff10..48d069e7 100644 --- a/src/main/java/org/springframework/data/gemfire/config/support/PdxDiskStoreAwareBeanFactoryPostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/config/support/PdxDiskStoreAwareBeanFactoryPostProcessor.java @@ -61,7 +61,9 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPos * @throws IllegalArgumentException if the Pivotal GemFire PDX {@link DiskStore} name is unspecified. */ public PdxDiskStoreAwareBeanFactoryPostProcessor(String pdxDiskStoreName) { + Assert.hasText(pdxDiskStoreName, "PDX DiskStore name is required"); + this.pdxDiskStoreName = pdxDiskStoreName; } @@ -104,7 +106,7 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPos BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); // NOTE for simplicity sake, we add a bean dependency to any bean definition for a bean - // (Async Event Queue, Region, Disk Store or otherwise) that may potentially require + // (Async Event Queue, Disk Store, Region, or otherwise) that may potentially require // the PDX Disk Store to exist. // NOTE this logic could be optimized to include Disk Store, persistent Async Event Queues // and persistent Regions (either by way of 'persistent' attribute, Data Policy @@ -124,7 +126,10 @@ public class PdxDiskStoreAwareBeanFactoryPostProcessor implements BeanFactoryPos * @see org.springframework.beans.factory.config.BeanDefinition#setDependsOn(String[]) */ private void addPdxDiskStoreDependency(BeanDefinition beanDefinition) { - String[] newDependsOn = (String[]) ArrayUtils.insert(getDependsOn(beanDefinition), 0, getPdxDiskStoreName()); + + String[] newDependsOn = + (String[]) ArrayUtils.insert(getDependsOn(beanDefinition), 0, getPdxDiskStoreName()); + beanDefinition.setDependsOn(newDependsOn); } diff --git a/src/main/java/org/springframework/data/gemfire/support/NoOpBeanFactoryPostProcessor.java b/src/main/java/org/springframework/data/gemfire/support/NoOpBeanFactoryPostProcessor.java new file mode 100644 index 00000000..669ac4ce --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/NoOpBeanFactoryPostProcessor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * The {@link NoOpBeanFactoryPostProcessor} class is a Spring {@link BeanFactoryPostProcessor} implementation + * that performs no operation. + * + * @author John Blum + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public final class NoOpBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + public static final NoOpBeanFactoryPostProcessor INSTANCE = new NoOpBeanFactoryPostProcessor(); + + private NoOpBeanFactoryPostProcessor() { } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java index e74e2eb3..bbe7801f 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java @@ -18,37 +18,14 @@ package org.springframework.data.gemfire.config.annotation; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration.DEFAULT_MCAST_PORT; -import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.Map; +import java.util.Properties; -import org.apache.geode.pdx.PdxSerializer; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.data.gemfire.CacheFactoryBean; -import org.springframework.data.gemfire.mapping.GemfireMappingContext; -import org.springframework.data.gemfire.mapping.MappingPdxSerializer; -import org.springframework.util.MethodInvoker; /** * Unit tests for {@link AbstractCacheConfiguration}. @@ -66,183 +43,26 @@ import org.springframework.util.MethodInvoker; @RunWith(MockitoJUnitRunner.class) public class AbstractCacheConfigurationUnitTests { - @Mock - private BeanFactory mockBeanFactory; - - private TestCacheConfiguration cacheConfiguration; - - @Before - public void setup() { - cacheConfiguration = new TestCacheConfiguration(); - } - - @SuppressWarnings("unchecked") - private T invokeMethod(Object obj, String methodName) throws Exception { - - MethodInvoker methodInvoker = new MethodInvoker(); - - methodInvoker.setTargetObject(obj); - methodInvoker.setTargetMethod(methodName); - methodInvoker.prepare(); - - return (T) methodInvoker.invoke(); - } + @Spy + private AbstractCacheConfiguration cacheConfiguration; @Test - public void configurePdxWhenEnablePdxIsConfigured() { + public void gemfirePropertiesContainsEssentialProperties() { - AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class); - PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); + this.cacheConfiguration.setName("TestName"); + this.cacheConfiguration.setMcastPort(-1); + this.cacheConfiguration.setLogLevel("DEBUG"); + this.cacheConfiguration.setLocators("skullbox[11235]"); + this.cacheConfiguration.setStartLocator("boombox[12480]"); - Map annotationAttributes = new HashMap<>(5); + Properties gemfireProperties = this.cacheConfiguration.gemfireProperties(); - annotationAttributes.put("diskStoreName", "BlockDiskStore"); - annotationAttributes.put("ignoreUnreadFields", Boolean.FALSE); - annotationAttributes.put("persistent", Boolean.TRUE); - annotationAttributes.put("readSerialized", Boolean.TRUE); - annotationAttributes.put("serializerBeanName", "MockPdxSerializer"); - - when(mockAnnotationMetadata.hasAnnotation(eq(EnablePdx.class.getName()))).thenReturn(true); - when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnablePdx.class.getName()))).thenReturn(annotationAttributes); - when(mockBeanFactory.containsBean(eq("MockPdxSerializer"))).thenReturn(true); - when(mockBeanFactory.getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class))).thenReturn(mockPdxSerializer); - - cacheConfiguration.setBeanFactory(mockBeanFactory); - cacheConfiguration.configurePdx(mockAnnotationMetadata); - - assertThat(cacheConfiguration.getPdxDiskStoreName()).isEqualTo("BlockDiskStore"); - assertThat(cacheConfiguration.getPdxIgnoreUnreadFields()).isFalse(); - assertThat(cacheConfiguration.getPdxPersistent()).isTrue(); - assertThat(cacheConfiguration.getPdxReadSerialized()).isTrue(); - assertThat(cacheConfiguration.getPdxSerializer()).isEqualTo(mockPdxSerializer); - - verify(mockAnnotationMetadata, times(1)).hasAnnotation(eq(EnablePdx.class.getName())); - verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnablePdx.class.getName())); - verify(mockBeanFactory, times(1)).containsBean(eq("MockPdxSerializer")); - verify(mockBeanFactory, times(1)).getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class)); - } - - @Test - public void configurePdxWhenEnablePdxIsNotConfigured() { - - AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class); - - when(mockAnnotationMetadata.hasAnnotation(anyString())).thenReturn(false); - - cacheConfiguration.configurePdx(mockAnnotationMetadata); - - assertThat(cacheConfiguration.getPdxDiskStoreName()).isNull(); - assertThat(cacheConfiguration.getPdxIgnoreUnreadFields()).isNull(); - assertThat(cacheConfiguration.getPdxPersistent()).isNull(); - assertThat(cacheConfiguration.getPdxReadSerialized()).isNull(); - assertThat(cacheConfiguration.getPdxSerializer()).isNull(); - - verify(mockAnnotationMetadata, times(1)).hasAnnotation(eq(EnablePdx.class.getName())); - verifyNoMoreInteractions(mockAnnotationMetadata); - } - - @Test - public void resolvePdxSerializerUsesPdxSerializerBean() { - - PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); - - when(mockBeanFactory.containsBean(anyString())).thenReturn(true); - when(mockBeanFactory.getBean(anyString(), eq(PdxSerializer.class))).thenReturn(mockPdxSerializer); - - cacheConfiguration.setBeanFactory(mockBeanFactory); - - PdxSerializer actualPdxSerializer = cacheConfiguration.resolvePdxSerializer("MockPdxSerializer"); - - assertThat(actualPdxSerializer).isEqualTo(mockPdxSerializer); - - verify(mockBeanFactory, times(1)).containsBean(eq("MockPdxSerializer")); - verify(mockBeanFactory, times(1)).getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class)); - } - - @Test - public void resolvePdxSerializerUsesConfiguredPdxSerializer() { - - PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); - - when(mockBeanFactory.containsBean(anyString())).thenReturn(false); - - cacheConfiguration.setBeanFactory(mockBeanFactory); - cacheConfiguration.setPdxSerializer(mockPdxSerializer); - - PdxSerializer actualPdxSerializer = cacheConfiguration.resolvePdxSerializer("TestPdxSerializer"); - - assertThat(actualPdxSerializer).isEqualTo(mockPdxSerializer); - - verify(mockBeanFactory, times(1)).containsBean(eq("TestPdxSerializer")); - verify(mockBeanFactory, never()).getBean(anyString(), eq(PdxSerializer.class)); - verifyZeroInteractions(mockPdxSerializer); - } - - @Test - public void resolvePdxSerializerCallsNewMappingPdxSerializer() { - - AbstractCacheConfiguration cacheConfigurationSpy = spy(this.cacheConfiguration); - MappingPdxSerializer mockPdxSerializer = mock(MappingPdxSerializer.class); - - when(mockBeanFactory.containsBean(anyString())).thenReturn(false); - doReturn(mockPdxSerializer).when(cacheConfigurationSpy).newPdxSerializer(any(BeanFactory.class)); - - cacheConfigurationSpy.setBeanFactory(mockBeanFactory); - - PdxSerializer actualPdxSerializer = cacheConfigurationSpy.resolvePdxSerializer("TestPdxSerializer"); - - assertThat(actualPdxSerializer).isEqualTo(mockPdxSerializer); - - verify(mockBeanFactory, times(1)).containsBean(eq("TestPdxSerializer")); - verify(mockBeanFactory, never()).getBean(anyString(), eq(PdxSerializer.class)); - verify(cacheConfigurationSpy, times(1)).newPdxSerializer(eq(mockBeanFactory)); - } - - @Test - public void newPdxSerializerUsesConfiguredConversionServiceAndMappingContext() throws Exception { - - ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class); - ConversionService mockConversionService = mock(ConversionService.class); - GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); - - when(mockBeanFactory.getConversionService()).thenReturn(mockConversionService); - - cacheConfiguration.setBeanFactory(mockBeanFactory); - cacheConfiguration.setMappingContext(mockMappingContext); - - MappingPdxSerializer pdxSerializer = cacheConfiguration.newPdxSerializer(); - - assertThat(pdxSerializer).isNotNull(); - assertThat((Object) invokeMethod(pdxSerializer, "getConversionService")).isEqualTo(mockConversionService); - assertThat((Object) invokeMethod(pdxSerializer, "getMappingContext")).isEqualTo(mockMappingContext); - - verify(mockBeanFactory, times(2)).getConversionService(); - verifyZeroInteractions(mockConversionService); - verifyZeroInteractions(mockMappingContext); - } - - @Test - public void newPdxSerializerDefaultsConversionServiceAndMappingContextWhenNotConfigured() throws Exception { - - cacheConfiguration.setBeanFactory(mockBeanFactory); - - MappingPdxSerializer pdxSerializer = cacheConfiguration.newPdxSerializer(); - - assertThat(pdxSerializer).isNotNull(); - assertThat((Object) invokeMethod(pdxSerializer, "getConversionService")).isInstanceOf(ConversionService.class); - assertThat((Object) invokeMethod(pdxSerializer, "getMappingContext")).isInstanceOf(GemfireMappingContext.class); - } - - protected static class TestCacheConfiguration extends AbstractCacheConfiguration { - - @Override - protected Class getAnnotationType() { - throw new UnsupportedOperationException("Not Implemented"); - } - - @Override - protected T newCacheFactoryBean() { - throw new UnsupportedOperationException("Not Implemented"); - } + assertThat(gemfireProperties).isNotNull(); + assertThat(gemfireProperties).hasSize(5); + assertThat(gemfireProperties.getProperty("name")).isEqualTo("TestName"); + assertThat(gemfireProperties.getProperty("mcast-port")).isEqualTo(String.valueOf(DEFAULT_MCAST_PORT)); + assertThat(gemfireProperties.getProperty("log-level")).isEqualTo("DEBUG"); + assertThat(gemfireProperties.getProperty("locators")).isEqualTo("skullbox[11235]"); + assertThat(gemfireProperties.getProperty("start-locator")).isEqualTo("boombox[12480]"); } } diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java index d85cb81c..f71cd118 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableGemFirePropertiesIntegrationTests.java @@ -17,17 +17,21 @@ package org.springframework.data.gemfire.config.annotation; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import java.util.Optional; import java.util.Properties; import org.apache.geode.cache.GemFireCache; +import org.apache.geode.pdx.PdxSerializer; import org.junit.After; import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; +import org.springframework.data.gemfire.CacheFactoryBean; import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.mock.env.MockPropertySource; @@ -267,9 +271,10 @@ public class EnableGemFirePropertiesIntegrationTests { } @Test - public void nameAndGroupGemFirePropertiesAnnotationConfiguration() { + public void nameAndGroupsAnnotationBasedGemFirePropertiesConfiguration() { - this.applicationContext = newApplicationContext(TestNameAndGroupGemFirePropertiesAnnotationConfiguration.class); + this.applicationContext = + newApplicationContext(TestNameAndGroupsAnnotationBasedGemFirePropertiesConfiguration.class); assertThat(this.applicationContext).isNotNull(); assertThat(this.applicationContext.containsBean("gemfireCache")).isTrue(); @@ -319,6 +324,37 @@ public class EnableGemFirePropertiesIntegrationTests { assertThat(gemfireProperties.getProperty("off-heap-memory-size")).isEqualTo("1024g"); } + @Test + public void pdxGemFirePropertiesConfiguration() { + + PropertySource testPropertySource = new MockPropertySource("TestPropertySource") + .withProperty("spring.data.gemfire.pdx.disk-store-name", "TestDiskStore") + .withProperty("spring.data.gemfire.pdx.ignore-unread-fields", "true") + .withProperty("spring.data.gemfire.pdx.persistent", "true") + .withProperty("spring.data.gemfire.pdx.read-serialized", "true") + .withProperty("spring.data.gemfire.pdx.serializer-bean-name", "mockPdxSerializer"); + + this.applicationContext = + newApplicationContext(testPropertySource, TestPdxGemFirePropertiesConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("gemfireCache")).isTrue(); + assertThat(this.applicationContext.containsBean("mockPdxSerializer")).isTrue(); + + CacheFactoryBean gemfireCache = this.applicationContext.getBean("&gemfireCache", CacheFactoryBean.class); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getPdxDiskStoreName()).isEqualTo("TestDiskStore"); + assertThat(gemfireCache.getPdxIgnoreUnreadFields()).isTrue(); + assertThat(gemfireCache.getPdxPersistent()).isTrue(); + assertThat(gemfireCache.getPdxReadSerialized()).isTrue(); + + PdxSerializer mockPdxSerializer = this.applicationContext.getBean("mockPdxSerializer", PdxSerializer.class); + + assertThat(mockPdxSerializer).isNotNull(); + assertThat(gemfireCache.getPdxSerializer()).isEqualTo(mockPdxSerializer); + } + @Test public void redisServerGemFirePropertiesConfiguration() { @@ -515,7 +551,7 @@ public class EnableGemFirePropertiesIntegrationTests { @EnableGemFireMockObjects @PeerCacheApplication @EnableGemFireProperties(name = "TestName", groups = { "TestGroupOne", "TestGroupTwo" }) - static class TestNameAndGroupGemFirePropertiesAnnotationConfiguration { } + static class TestNameAndGroupsAnnotationBasedGemFirePropertiesConfiguration { } @EnableGemFireMockObjects @PeerCacheApplication @@ -523,6 +559,18 @@ public class EnableGemFirePropertiesIntegrationTests { @EnableOffHeap(memorySize = "64g") static class TestOffHeapGemFirePropertiesConfiguration { } + @EnableGemFireMockObjects + @PeerCacheApplication + @EnablePdx + @SuppressWarnings("unused") + static class TestPdxGemFirePropertiesConfiguration { + + @Bean + PdxSerializer mockPdxSerializer() { + return mock(PdxSerializer.class); + } + } + @EnableGemFireMockObjects @PeerCacheApplication @EnableGemFireProperties diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationIntegrationTests.java new file mode 100644 index 00000000..87ded233 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationIntegrationTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.pdx.PdxSerializer; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.DiskStoreFactoryBean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.mapping.MappingPdxSerializer; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; + +/** + * The EnablePdxConfigurationIntegrationTests class... + * + * @author John Blum + * @since 1.0.0 + */ +public class EnablePdxConfigurationIntegrationTests { + + @Autowired + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + return new AnnotationConfigApplicationContext(annotatedClasses); + } + + @Test + public void regionBeanDefinitionDependsOnPdxDiskStoreBean() { + + this.applicationContext = newApplicationContext(TestEnablePdxWithDiskStoreConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("gemfireCache")).isTrue(); + assertThat(this.applicationContext.containsBean("MockPdxSerializer")).isTrue(); + assertThat(this.applicationContext.containsBean("TestDiskStore")).isTrue(); + assertThat(this.applicationContext.containsBean("TestRegion")).isTrue(); + + CacheFactoryBean gemfireCache = this.applicationContext.getBean("&gemfireCache", CacheFactoryBean.class); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getPdxSerializer()) + .isEqualTo(this.applicationContext.getBean("MockPdxSerializer", PdxSerializer.class)); + + BeanDefinition testDiskStoreBeanDefinition = + this.applicationContext.getBeanFactory().getBeanDefinition("TestDiskStore"); + + assertThat(testDiskStoreBeanDefinition).isNotNull(); + assertThat(testDiskStoreBeanDefinition.getDependsOn()).isNullOrEmpty(); + + BeanDefinition testRegionBeanDefinition = + this.applicationContext.getBeanFactory().getBeanDefinition("TestRegion"); + + assertThat(testRegionBeanDefinition).isNotNull(); + assertThat(testRegionBeanDefinition.getDependsOn()).containsExactly("TestDiskStore"); + } + + @Test + public void regionBeanDefinitionHasNoDependencies() { + + this.applicationContext = newApplicationContext(TestEnablePdxConfigurationWithNoDiskStoreConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("gemfireCache")).isTrue(); + assertThat(this.applicationContext.containsBean("TestDiskStore")).isTrue(); + assertThat(this.applicationContext.containsBean("TestRegion")).isTrue(); + + CacheFactoryBean gemfireCache = this.applicationContext.getBean("&gemfireCache", CacheFactoryBean.class); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getPdxSerializer()).isInstanceOf(MappingPdxSerializer.class); + + BeanDefinition testDiskStoreBeanDefinition = + this.applicationContext.getBeanFactory().getBeanDefinition("TestDiskStore"); + + assertThat(testDiskStoreBeanDefinition).isNotNull(); + assertThat(testDiskStoreBeanDefinition.getDependsOn()).isNullOrEmpty(); + + BeanDefinition testRegionBeanDefinition = + this.applicationContext.getBeanFactory().getBeanDefinition("TestRegion"); + + assertThat(testRegionBeanDefinition).isNotNull(); + assertThat(testRegionBeanDefinition.getDependsOn()).isNullOrEmpty(); + + } + + @EnableGemFireMockObjects + @PeerCacheApplication + @EnablePdx(diskStoreName = "TestDiskStore", serializerBeanName = "MockPdxSerializer") + @SuppressWarnings("unused") + static class TestEnablePdxWithDiskStoreConfiguration { + + @Bean("TestDiskStore") + DiskStoreFactoryBean testPdxDiskStore(GemFireCache gemfireCache) { + + DiskStoreFactoryBean testDiskStore = new DiskStoreFactoryBean(); + + testDiskStore.setCache(gemfireCache); + + return testDiskStore; + } + + @Bean("TestRegion") + LocalRegionFactoryBean testRegion(GemFireCache gemfireCache) { + + LocalRegionFactoryBean testRegion = new LocalRegionFactoryBean<>(); + + testRegion.setCache(gemfireCache); + testRegion.setClose(false); + testRegion.setPersistent(false); + + return testRegion; + } + + @Bean("MockPdxSerializer") + PdxSerializer mockPdxSerializer() { + return mock(PdxSerializer.class); + } + } + + @EnableGemFireMockObjects + @ClientCacheApplication + @EnablePdx + @SuppressWarnings("unused") + static class TestEnablePdxConfigurationWithNoDiskStoreConfiguration { + + @Bean("TestDiskStore") + DiskStoreFactoryBean testPdxDiskStore(GemFireCache gemfireCache) { + + DiskStoreFactoryBean testDiskStore = new DiskStoreFactoryBean(); + + testDiskStore.setCache(gemfireCache); + + return testDiskStore; + } + + @Bean("TestRegion") + public ClientRegionFactoryBean testRegion(GemFireCache gemfireCache) { + + ClientRegionFactoryBean testRegion = new ClientRegionFactoryBean<>(); + + testRegion.setCache(gemfireCache); + testRegion.setClose(false); + testRegion.setShortcut(ClientRegionShortcut.LOCAL); + + return testRegion; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java new file mode 100644 index 00000000..56dad373 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnablePdxConfigurationUnitTests.java @@ -0,0 +1,310 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.geode.pdx.PdxSerializer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.MappingPdxSerializer; +import org.springframework.util.MethodInvoker; + +/** + * Unit tests for {@link EnablePdx} and {@link PdxConfiguration}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Spy + * @see org.apache.geode.pdx.PdxSerializer + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnablePdx + * @see org.springframework.data.gemfire.config.annotation.PdxConfiguration + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class EnablePdxConfigurationUnitTests { + + @Spy + private PdxConfiguration pdxConfiguration; + + @SuppressWarnings("unchecked") + private T invokeMethod(Object obj, String methodName) throws Exception { + + MethodInvoker methodInvoker = new MethodInvoker(); + + methodInvoker.setTargetObject(obj); + methodInvoker.setTargetMethod(methodName); + methodInvoker.prepare(); + + return (T) methodInvoker.invoke(); + } + + @Test + public void setImportMetadataWhenEnablePdxIsConfigured() { + + AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class); + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + Map annotationAttributes = new HashMap<>(5); + + annotationAttributes.put("diskStoreName", "MockDiskStore"); + annotationAttributes.put("ignoreUnreadFields", Boolean.TRUE); + annotationAttributes.put("persistent", Boolean.TRUE); + annotationAttributes.put("readSerialized", Boolean.TRUE); + annotationAttributes.put("serializerBeanName", "MockPdxSerializer"); + + when(mockAnnotationMetadata.hasAnnotation(eq(EnablePdx.class.getName()))).thenReturn(true); + when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnablePdx.class.getName()))) + .thenReturn(annotationAttributes); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + this.pdxConfiguration.setImportMetadata(mockAnnotationMetadata); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.getDiskStoreName().orElse(null)).isEqualTo("MockDiskStore"); + assertThat(this.pdxConfiguration.isIgnoreUnreadFields()).isTrue(); + assertThat(this.pdxConfiguration.isPersistent()).isTrue(); + assertThat(this.pdxConfiguration.isReadSerialized()).isTrue(); + assertThat(this.pdxConfiguration.getSerializerBeanName().orElse(null)).isEqualTo("MockPdxSerializer"); + + verify(mockAnnotationMetadata, times(1)).hasAnnotation(eq(EnablePdx.class.getName())); + verify(mockAnnotationMetadata, times(1)) + .getAnnotationAttributes(eq(EnablePdx.class.getName())); + } + + @Test + public void setImportMetadataWhenEnablePdxIsNotConfigured() { + + AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class); + + when(mockAnnotationMetadata.hasAnnotation(anyString())).thenReturn(false); + + this.pdxConfiguration.setImportMetadata(mockAnnotationMetadata); + + assertThat(this.pdxConfiguration.getDiskStoreName().isPresent()).isFalse(); + assertThat(this.pdxConfiguration.isIgnoreUnreadFields()).isFalse(); + assertThat(this.pdxConfiguration.isPersistent()).isFalse(); + assertThat(this.pdxConfiguration.isReadSerialized()).isFalse(); + assertThat(this.pdxConfiguration.getSerializerBeanName().isPresent()).isFalse(); + + verify(mockAnnotationMetadata, times(1)).hasAnnotation(eq(EnablePdx.class.getName())); + verifyNoMoreInteractions(mockAnnotationMetadata); + } + + @Test + public void configuresPdxForCacheFactoryBean() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); + + when(mockBeanFactory.containsBean(eq("MockPdxSerializer"))).thenReturn(true); + when(mockBeanFactory.getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class))) + .thenReturn(mockPdxSerializer); + + doReturn(Optional.of("MockPdxDiskStore")).when(this.pdxConfiguration).getDiskStoreName(); + doReturn(true).when(this.pdxConfiguration).isIgnoreUnreadFields(); + doReturn(true).when(this.pdxConfiguration).isPersistent(); + doReturn(true).when(this.pdxConfiguration).isReadSerialized(); + doReturn(Optional.of("MockPdxSerializer")).when(this.pdxConfiguration).getSerializerBeanName(); + + CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + this.pdxConfiguration.configurePdx(cacheFactoryBean); + + assertThat(cacheFactoryBean.getPdxDiskStoreName()).isEqualTo("MockPdxDiskStore"); + assertThat(cacheFactoryBean.getPdxIgnoreUnreadFields()).isTrue(); + assertThat(cacheFactoryBean.getPdxPersistent()).isTrue(); + assertThat(cacheFactoryBean.getPdxReadSerialized()).isTrue(); + assertThat(cacheFactoryBean.getPdxSerializer()).isEqualTo(mockPdxSerializer); + + verify(mockBeanFactory, times(1)).containsBean(eq("MockPdxSerializer")); + verify(mockBeanFactory, times(1)) + .getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class)); + + verify(this.pdxConfiguration, times(1)).getDiskStoreName(); + verify(this.pdxConfiguration, times(1)).isIgnoreUnreadFields(); + verify(this.pdxConfiguration, times(1)).isPersistent(); + verify(this.pdxConfiguration, times(1)).isReadSerialized(); + verify(this.pdxConfiguration, times(1)).getSerializerBeanName(); + } + + @Test + public void resolveConversionServiceWithNonConfigurableBeanFactoryReturnsNull() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.resolveConversionService().isPresent()).isFalse(); + + verifyZeroInteractions(mockBeanFactory); + } + + @Test + public void resolveConversionServiceReturnsConfiguredConversionService() { + + ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class); + + ConversionService mockConversionService = mock(ConversionService.class); + + when(mockBeanFactory.getConversionService()).thenReturn(mockConversionService); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.resolveConversionService().orElse(null)) + .isEqualTo(mockConversionService); + + verify(mockBeanFactory, atLeastOnce()).getConversionService(); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveMappingContextWhenBeanFactoryDoesNotContainMappingContextBeanReturnsNull() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + when(mockBeanFactory.getBean(any(Class.class))).thenThrow(new NoSuchBeanDefinitionException("test")); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.resolveMappingContext().isPresent()).isFalse(); + + verify(mockBeanFactory, times(1)).getBean(eq(GemfireMappingContext.class)); + } + + @Test + public void resolveMappingContextReturnsConfiguredMappingContext() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); + + when(mockBeanFactory.getBean(eq(GemfireMappingContext.class))).thenReturn(mockMappingContext); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.resolveMappingContext().orElse(null)).isEqualTo(mockMappingContext); + + verify(mockBeanFactory, times(1)).getBean(eq(GemfireMappingContext.class)); + } + + @Test + public void resolvePdxSerializerWhenBeanFactoryDoesNotContainPdxSerializerBeanReturnsMappingPdxSerializer() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + when(mockBeanFactory.containsBean(anyString())).thenReturn(false); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + this.pdxConfiguration.setSerializerBeanName("MockPdxSerializer"); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.getSerializerBeanName().orElse(null)).isEqualTo("MockPdxSerializer"); + assertThat(this.pdxConfiguration.resolvePdxSerializer()).isInstanceOf(MappingPdxSerializer.class); + + verify(mockBeanFactory, times(1)).containsBean(eq("MockPdxSerializer")); + verify(mockBeanFactory, never()).getBean(anyString(), any(PdxSerializer.class)); + } + + @Test + public void resolvePdxSerializerReturnsConfiguredPdxSerializer() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + PdxSerializer mockPdxSerializer = mock(PdxSerializer.class); + + when(mockBeanFactory.containsBean(eq("MockPdxSerializer"))).thenReturn(true); + when(mockBeanFactory.getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class))) + .thenReturn(mockPdxSerializer); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + this.pdxConfiguration.setSerializerBeanName("MockPdxSerializer"); + + assertThat(this.pdxConfiguration.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(this.pdxConfiguration.getSerializerBeanName().orElse(null)).isEqualTo("MockPdxSerializer"); + assertThat(this.pdxConfiguration.resolvePdxSerializer()).isEqualTo(mockPdxSerializer); + + verify(mockBeanFactory, times(1)).containsBean(eq("MockPdxSerializer")); + verify(mockBeanFactory, times(1)) + .getBean(eq("MockPdxSerializer"), eq(PdxSerializer.class)); + } + + @Test + public void newPdxSerializerReturnsMappingPdxSerializerConfiguredWithResolvedConversionServiceAndMappingContext() + throws Exception { + + ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class); + + ConversionService mockConversionService = mock(ConversionService.class); + + GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class); + + when(mockBeanFactory.getBean(eq(GemfireMappingContext.class))).thenReturn(mockMappingContext); + when(mockBeanFactory.getConversionService()).thenReturn(mockConversionService); + + this.pdxConfiguration.setBeanFactory(mockBeanFactory); + + PdxSerializer pdxSerializer = this.pdxConfiguration.newPdxSerializer(); + + assertThat(pdxSerializer).isInstanceOf(MappingPdxSerializer.class); + + assertThat(this.invokeMethod(pdxSerializer, "getConversionService")) + .isEqualTo(mockConversionService); + + assertThat(this.invokeMethod(pdxSerializer, "getMappingContext")) + .isEqualTo(mockMappingContext); + + verify(mockBeanFactory, times(1)).getBean(eq(GemfireMappingContext.class)); + verify(mockBeanFactory, atLeastOnce()).getConversionService(); + } +}