diff --git a/src/main/asciidoc/reference/bootstrap-annotations.adoc b/src/main/asciidoc/reference/bootstrap-annotations.adoc index 52b733ce..b62c5dea 100644 --- a/src/main/asciidoc/reference/bootstrap-annotations.adoc +++ b/src/main/asciidoc/reference/bootstrap-annotations.adoc @@ -1572,11 +1572,11 @@ the following CQ... [source, java] ---- @SpringBootApplication -@ClientCacheApplication(subcriptionEnabled = true); +@ClientCacheApplication(subcriptionEnabled = true) @EnableContinuousQueries class PublisherPrintApplication { - @ContinuousQuery(name = "DemandExceedsSupply" query = + @ContinuousQuery(name = "DemandExceedsSupply", query = "SELECT book.* FROM /Books book, /Inventory inventory WHERE book.title = 'How to crush it in the Book business like Amazon" AND inventory.isbn = book.isbn diff --git a/src/main/asciidoc/reference/data.adoc b/src/main/asciidoc/reference/data.adoc index e845c410..7fcc93dc 100644 --- a/src/main/asciidoc/reference/data.adoc +++ b/src/main/asciidoc/reference/data.adoc @@ -517,7 +517,8 @@ _Spring Data GemFire_ provides an implementation of the _Spring_ http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#cache[Cache Abstraction] to position Pivotal GemFire as a _caching provider_ in Spring's caching infrastructure. -To use Pivotal GemFire as a backing implementation, simply add `GemfireCacheManager` to your configuration: +To use Pivotal GemFire as a backing implementation, a "_caching provider_" _in Spring's Cache Abstraction_, +simply add `GemfireCacheManager` to your configuration: [source,xml] ---- @@ -543,7 +544,7 @@ To use Pivotal GemFire as a backing implementation, simply add `GemfireCacheMana ---- NOTE: The `cache-ref` attribute on the `CacheManager` bean definition is not necessary if the default cache bean name -is used (i.e. "gemfireCache"), that is, `` without an explicit ID. +is used (i.e. "gemfireCache"), i.e. `` without an explicit ID. When the `GemfireCacheManager` (Singleton) bean instance is declared and declarative caching is enabled (either in XML with `` or in JavaConfig with _Spring's_ `@EnableCaching` annotation), @@ -553,7 +554,7 @@ using GemFire Regions. These caches (i.e. Regions) must exist before the caching annotations that use them otherwise an error will occur. By way of example, suppose you have a Customer Service application with a `CustomerService` application component -that does caching... +that performs caching... [source,java] ---- @@ -588,7 +589,7 @@ XML: - + ... @@ -609,7 +610,9 @@ class ApplicationConfiguration { @Bean GemfireCacheManager cacheManager() { - return new GemfireCacheManager(gemfireCache()); + GemfireCacheManager cacheManager = GemfireCacheManager(); + cacheManager.setCache(gemfireCache()); + return cacheManager; } @Bean("Accounts") diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CachingDefinedRegionsConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CachingDefinedRegionsConfiguration.java index 3b6640b2..6062bc3d 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/CachingDefinedRegionsConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CachingDefinedRegionsConfiguration.java @@ -19,7 +19,6 @@ package org.springframework.data.gemfire.config.annotation; import static java.util.Arrays.stream; import static org.springframework.data.gemfire.util.ArrayUtils.asArray; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; -import static org.springframework.data.gemfire.util.CollectionUtils.asSet; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; @@ -36,13 +35,13 @@ import java.util.stream.Stream; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; @@ -53,16 +52,16 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.type.MethodMetadata; -import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; import org.springframework.data.gemfire.config.annotation.support.BeanDefinitionRegistryPostProcessorSupport; import org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean; import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -72,10 +71,18 @@ import org.springframework.util.StringUtils; * service classes and methods. * * @author John Blum + * @see java.lang.annotation.Annotation + * @see java.lang.reflect.AnnotatedElement * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionShortcut + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.beans.factory.annotation.AnnotatedBeanDefinition * @see org.springframework.beans.factory.config.BeanDefinition * @see org.springframework.beans.factory.config.BeanPostProcessor + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory + * @see org.springframework.beans.factory.support.AbstractBeanDefinition * @see org.springframework.beans.factory.support.BeanDefinitionBuilder * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor @@ -86,15 +93,17 @@ import org.springframework.util.StringUtils; * @see org.springframework.cache.annotation.Caching * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware * @see org.springframework.core.annotation.AnnotatedElementUtils * @see org.springframework.core.annotation.AnnotationUtils * @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport * @see org.springframework.data.gemfire.config.annotation.support.BeanDefinitionRegistryPostProcessorSupport * @see org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean * @since 2.0.0 */ @Configuration -public class CachingDefinedRegionsConfiguration { +public class CachingDefinedRegionsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { private static final Class[] CLASS_CACHE_ANNOTATION_TYPES; @@ -112,11 +121,155 @@ public class CachingDefinedRegionsConfiguration { } - private static final Set INFRASTRUCTURE_ROLES = - asSet(BeanDefinition.ROLE_INFRASTRUCTURE, BeanDefinition.ROLE_SUPPORT); + private ClientRegionShortcut clientRegionShortcut = ClientRegionShortcut.PROXY; - private static final String ORG_SPRINGFRAMEWORK_DATA_GEMFIRE_PACKAGE = "org.springframework.data.gemfire"; - private static final String ORG_SPRINGFRAMEWORK_PACKAGE = "org.springframework"; + private RegionShortcut serverRegionShortcut = RegionShortcut.PARTITION; + + private String poolName = ClientRegionFactoryBean.DEFAULT_POOL_NAME; + + /** + * Returns the {@link Annotation} {@link Class type} that configures and creates {@link Region Regions} + * for application service {@link Method Methods} that are annotated with Spring's Cache Abstraction Annotations. + * + * @return the {@link Annotation} {@link Class type} that configures and creates {@link Region Regions} + * for application service {@link Method Methods} that are annotated with Spring's Cache Abstraction Annotations. + * @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions + * @see java.lang.annotation.Annotation + * @see java.lang.Class + */ + @Override + protected Class getAnnotationType() { + return EnableCachingDefinedRegions.class; + } + + /** + * Configures the {@link ClientRegionShortcut} specifying the data management policy to use + * when creating a client {@link Region}. + * + * @param clientRegionShortcut {@link ClientRegionShortcut} specifying the data management policy + * to use when creating a client {@link Region}. + * @see org.apache.geode.cache.client.ClientRegionShortcut + */ + public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) { + this.clientRegionShortcut = clientRegionShortcut; + } + + /** + * Returns the configured {@link ClientRegionShortcut} specifying the data management policy to use + * when creating a client {@link Region}. + * + * @return an {@link Optional} {@link ClientRegionShortcut} specifying the data management policy to use + * when creating a client {@link Region}. + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see #setClientRegionShortcut(ClientRegionShortcut) + * @see java.util.Optional + */ + protected Optional getClientRegionShortcut() { + return Optional.ofNullable(this.clientRegionShortcut); + } + + /** + * Resolves the {@link ClientRegionShortcut} specifying the data management policy to use + * when creating a client {@link Region}; defaults to {@link ClientRegionShortcut#PROXY}. + * + * @return the resolved {@link ClientRegionShortcut} specifying the data management policy to use + * when creating a client {@link Region}; defaults to {@link ClientRegionShortcut#PROXY}. + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see #getClientRegionShortcut() + */ + protected ClientRegionShortcut resolveClientRegionShortcut() { + return getClientRegionShortcut().orElse(ClientRegionShortcut.PROXY); + } + + /** + * Configures the name of the dedicated {@link Pool} used by all caching-defined client {@link Region Regions} + * to send and receive data between the client and server. + * + * @param poolName {@link String} containing the name of the dedicated {@link Pool} for all + * caching-defined client {@link Region Regions}. + */ + public void setPoolName(String poolName) { + this.poolName = poolName; + } + + /** + * Returns the name of the dedicated {@link Pool} used by all caching-defined client {@link Region Regions} + * to send and receive data between the client and server. + * + * @return an {@link Optional} {@link String name} of the dedicated {@link Pool} used by all caching-defined + * client {@link Region Regions}. + * @see #setPoolName(String) + * @see java.util.Optional + */ + protected Optional getPoolName() { + return Optional.ofNullable(this.poolName).filter(StringUtils::hasText); + } + + /** + * Resolves the name of the dedicated {@link Pool} used by all caching-defined client {@link Region Regions} + * to send and receive data between the client and server; defaults to {@literal DEFAULT}. + * + * @return the {@link String name} of the dedicated {@link Pool} used by all caching-defined + * client {@link Region Regions}; defaults to {@literal DEFAULT}. + * @see #getPoolName() + */ + protected String resolvePoolName() { + return getPoolName().orElse(ClientRegionFactoryBean.DEFAULT_POOL_NAME); + } + + /** + * Configures the {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}. + * + * @param serverRegionShortcut {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}. + * @see org.apache.geode.cache.RegionShortcut + */ + public void setServerRegionShortcut(RegionShortcut serverRegionShortcut) { + this.serverRegionShortcut = serverRegionShortcut; + } + + /** + * Returns the configured {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}. + * + * @return an {@link Optional} {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}. + * @see #setServerRegionShortcut(RegionShortcut) + * @see org.apache.geode.cache.RegionShortcut + * @see java.util.Optional + */ + protected Optional getServerRegionShortcut() { + return Optional.ofNullable(this.serverRegionShortcut); + } + + /** + * Resolves the {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}; defaults to {@link RegionShortcut#PARTITION}. + * + * @return the resolved {@link RegionShortcut} specifying the data management policy to use + * when creating a server (peer) {@link Region}; defaults to {@link RegionShortcut#PARTITION}. + * @see org.apache.geode.cache.RegionShortcut + * @see #getServerRegionShortcut() + */ + protected RegionShortcut resolveServerRegionShortcut() { + return getServerRegionShortcut().orElse(RegionShortcut.PARTITION); + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + + if (isAnnotationPresent(importMetadata)) { + + AnnotationAttributes enableCachingDefinedRegionsAttributes = getAnnotationAttributes(importMetadata); + + setClientRegionShortcut(enableCachingDefinedRegionsAttributes.getEnum("clientRegionShortcut")); + + setPoolName(enableCachingDefinedRegionsAttributes.getString("poolName")); + + setServerRegionShortcut(enableCachingDefinedRegionsAttributes.getEnum("serverRegionShortcut")); + } + } @Bean @SuppressWarnings("all") @@ -162,35 +315,6 @@ public class CachingDefinedRegionsConfiguration { } } - private boolean isNotInfrastructureBean(Object bean) { - return isNotInfrastructureClass(bean.getClass().getName()); - } - - boolean isNotInfrastructureBean(BeanDefinition beanDefinition) { - return (isNotInfrastructureRole(beanDefinition) && isNotInfrastructureClass(beanDefinition)); - } - - boolean isNotInfrastructureClass(BeanDefinition beanDefinition) { - return resolveBeanClassName(beanDefinition).filter(this::isNotInfrastructureClass).isPresent(); - } - - boolean isNotInfrastructureClass(String className) { - return (className.startsWith(ORG_SPRINGFRAMEWORK_DATA_GEMFIRE_PACKAGE) - || !className.startsWith(ORG_SPRINGFRAMEWORK_PACKAGE)); - } - - boolean isNotInfrastructureRole(BeanDefinition beanDefinition) { - return !INFRASTRUCTURE_ROLES.contains(beanDefinition.getRole()); - } - - boolean isUserLevelMethod(Method method) { - - return Optional.ofNullable(method) - .filter(ClassUtils::isUserLevelMethod) - .filter(it -> !Object.class.equals(it.getDeclaringClass())) - .isPresent(); - } - @SuppressWarnings("unchecked") private Set collectCacheNames(Class type) { @@ -200,7 +324,6 @@ public class CachingDefinedRegionsConfiguration { cacheNames.addAll(collectCacheNames(type, CLASS_CACHE_ANNOTATION_TYPES)); stream(type.getMethods()).forEach(method -> { - if (isUserLevelMethod(method)) { cacheNames.addAll(collectCachingCacheNames(method)); cacheNames.addAll(collectCacheNames(method, METHOD_CACHE_ANNOTATION_TYPES)); @@ -224,8 +347,8 @@ public class CachingDefinedRegionsConfiguration { private Set collectCacheNames(Annotation annotation) { return Optional.ofNullable(annotation) - .map(AnnotationUtils::getAnnotationAttributes) - .map(annotationAttributes -> (String[]) annotationAttributes.get("cacheNames")) + .map(this::getAnnotationAttributes) + .map(annotationAttributes -> annotationAttributes.getStringArray("cacheNames")) .map(CollectionUtils::asSet) .orElse(Collections.emptySet()); } @@ -242,11 +365,11 @@ public class CachingDefinedRegionsConfiguration { .collect(Collectors.toSet())); cacheNames.addAll(stream(nullSafeArray(caching.evict(), CacheEvict.class)) - .flatMap(cacheable -> collectCacheNames(cacheable).stream()) + .flatMap(cacheEvict -> collectCacheNames(cacheEvict).stream()) .collect(Collectors.toSet())); cacheNames.addAll(stream(nullSafeArray(caching.put(), CachePut.class)) - .flatMap(cacheable -> collectCacheNames(cacheable).stream()) + .flatMap(cachePut -> collectCacheNames(cachePut).stream()) .collect(Collectors.toSet())); }); @@ -257,9 +380,6 @@ public class CachingDefinedRegionsConfiguration { private BeanDefinitionRegistry registerRegionBeanDefinitions(Set cacheNames, BeanDefinitionRegistry registry) { - boolean containsGemFirePoolBeanDefinition = - registry.containsBeanDefinition(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); - cacheNames.forEach(cacheName -> { if (!registry.containsBeanDefinition(cacheName)) { @@ -268,13 +388,10 @@ public class CachingDefinedRegionsConfiguration { BeanDefinitionBuilder.genericBeanDefinition(GemFireCacheTypeAwareRegionFactoryBean.class); builder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); - - if (!containsGemFirePoolBeanDefinition) { - builder.addPropertyValue("poolName", GemfireUtils.DEFAULT_POOL_NAME); - } - + builder.addPropertyValue("clientRegionShortcut", resolveClientRegionShortcut()); + builder.addPropertyValue("poolName", resolvePoolName()); builder.addPropertyValue("regionName", cacheName); - builder.addPropertyValue("serverRegionShortcut", RegionShortcut.PARTITION); + builder.addPropertyValue("serverRegionShortcut", resolveServerRegionShortcut()); registry.registerBeanDefinition(cacheName, builder.getBeanDefinition()); } @@ -285,8 +402,6 @@ public class CachingDefinedRegionsConfiguration { private ConfigurableBeanFactory registerRegionBeans(Set cacheNames, ConfigurableBeanFactory beanFactory) { - boolean containsGemFirePoolBean = beanFactory.containsBean(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); - cacheNames.forEach(cacheName -> { if (!beanFactory.containsBean(cacheName)) { @@ -295,14 +410,14 @@ public class CachingDefinedRegionsConfiguration { GemFireCacheTypeAwareRegionFactoryBean regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean<>(); - regionFactoryBean.setCache(beanFactory.getBean("gemfireCache", GemFireCache.class)); - - if (!containsGemFirePoolBean) { - regionFactoryBean.setPoolName(GemfireUtils.DEFAULT_POOL_NAME); - } + GemFireCache gemfireCache = + beanFactory.getBean(GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME, GemFireCache.class); + regionFactoryBean.setCache(gemfireCache); + regionFactoryBean.setClientRegionShortcut(resolveClientRegionShortcut()); + regionFactoryBean.setPoolName(resolvePoolName()); regionFactoryBean.setRegionName(cacheName); - regionFactoryBean.setServerRegionShortcut(RegionShortcut.PARTITION); + regionFactoryBean.setServerRegionShortcut(resolveServerRegionShortcut()); regionFactoryBean.afterPropertiesSet(); Optional.ofNullable(regionFactoryBean.getObject()) @@ -317,70 +432,4 @@ public class CachingDefinedRegionsConfiguration { return beanFactory; } - - /* (non-Javadoc) */ - A resolveAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - - return (annotatedElement instanceof Class - ? AnnotatedElementUtils.findMergedAnnotation(annotatedElement, annotationType) - : AnnotationUtils.findAnnotation(annotatedElement, annotationType)); - } - - /* (non-Javadoc) */ - Optional> resolveBeanClass(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) { - return resolveBeanClass(beanDefinition, resolveBeanClassLoader(registry)); - } - - /* (non-Javadoc) */ - private Optional> resolveBeanClass(BeanDefinition beanDefinition, ClassLoader classLoader) { - - Class beanClass = (beanDefinition instanceof AbstractBeanDefinition - ? safeResolveType(() -> ((AbstractBeanDefinition) beanDefinition).resolveBeanClass(classLoader)) : null); - - if (beanClass == null) { - beanClass = resolveBeanClassName(beanDefinition).map(beanClassName -> - safeResolveType(() -> ClassUtils.forName(beanClassName, classLoader))).orElse(null); - } - - return Optional.ofNullable(beanClass); - } - - /* (non-Javadoc) */ - ClassLoader resolveBeanClassLoader(BeanDefinitionRegistry registry) { - - return (registry instanceof ConfigurableBeanFactory - ? ((ConfigurableBeanFactory) registry).getBeanClassLoader() - : Thread.currentThread().getContextClassLoader()); - } - - /* (non-Javadoc) */ - Optional resolveBeanClassName(BeanDefinition beanDefinition) { - - Optional beanClassName = - Optional.ofNullable(beanDefinition.getBeanClassName()).filter(StringUtils::hasText); - - if (!beanClassName.isPresent()) { - beanClassName = Optional.of(beanDefinition) - .filter(it -> StringUtils.hasText(it.getFactoryMethodName())) - .filter(it -> it instanceof AnnotatedBeanDefinition) - .map(it -> ((AnnotatedBeanDefinition) it).getFactoryMethodMetadata()) - .map(MethodMetadata::getReturnTypeName); - } - - return beanClassName; - } - - /* (non-Javadoc) */ - Class safeResolveType(TypeResolver typeResolver) { - try { - return typeResolver.resolve(); - } - catch (ClassNotFoundException cause) { - return null; - } - } - - interface TypeResolver { - Class resolve() throws ClassNotFoundException; - } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegions.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegions.java index e833fccc..b41b863f 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegions.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegions.java @@ -24,18 +24,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.gemfire.cache.config.EnableGemfireCaching; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; /** * The {@link EnableCachingDefinedRegions} annotation marks a Spring {@link Configuration @Configuration} application * annotated class to enable the creation of GemFire/Geode {@link Region Regions} based on Spring's Cache Abstraction * Annotations applied to application service methods and types. * - * Additionally, this annotation also enable Spring's Cache Abstraction with SDG's {@link EnableGemfireCaching}, - * which declares Spring's {@link org.springframework.cache.annotation.EnableCaching} annotation as well as - * declares the SDG {@link org.springframework.data.gemfire.cache.GemfireCacheManager} bean definition. + * Additionally, this annotation enables Spring's Cache Abstraction with SDG's {@link EnableGemfireCaching} annotation, + * which declares Spring's {@link org.springframework.cache.annotation.EnableCaching} annotation as well as declares + * the SDG {@link org.springframework.data.gemfire.cache.GemfireCacheManager} bean definition. * * @author John Blum * @see java.lang.annotation.Documented @@ -43,9 +48,13 @@ import org.springframework.data.gemfire.cache.config.EnableGemfireCaching; * @see java.lang.annotation.Retention * @see java.lang.annotation.Target * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionShortcut + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.cache.client.Pool * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Import * @see org.springframework.data.gemfire.cache.config.EnableGemfireCaching + * @see org.springframework.data.gemfire.config.annotation.CachingDefinedRegionsConfiguration * @since 2.0.0 */ @Target(ElementType.TYPE) @@ -54,6 +63,37 @@ import org.springframework.data.gemfire.cache.config.EnableGemfireCaching; @Documented @EnableGemfireCaching @Import(CachingDefinedRegionsConfiguration.class) +@SuppressWarnings("unused") public @interface EnableCachingDefinedRegions { + /** + * When this annotation is applied to a cache client application, the {@literal clientRegionShortcut} attribute + * specifies the data management policy applied to client {@link Region Regions} where persistent entities are + * only annotated with the generic {@link org.springframework.data.gemfire.mapping.annotation.Region} + * mapping annotation, or the non-data policy specific mapping annotation. + * + * Defaults to {@link ClientRegionShortcut#PROXY}. + */ + ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY; + + /** + * When this annotation is applied to a cache client application, the {@literal poolName} attribute refers to + * the default name of the GemFire/Geode {@link Pool} assigned to client {@link Region Region(s)}. + * + * This value can be overridden by annotating entities with the {@link ClientRegion} annotation. + * + * Defaults to {@literal DEFAULT}. + */ + String poolName() default ClientRegionFactoryBean.DEFAULT_POOL_NAME; + + /** + * When this annotation is applied to a peer cache application, the {@literal serverRegionShortcut} attribute + * specifies the data management policy applied to server {@link Region Regions} where persistent entities are + * only annotated with the generic {@link org.springframework.data.gemfire.mapping.annotation.Region} + * mapping annotation, or the non-data policy specific mapping annotation. + * + * Defaults to {@link RegionShortcut#PARTITION}. + */ + RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION; + } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java index bf2e34fd..7393d452 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java @@ -17,14 +17,18 @@ package org.springframework.data.gemfire.config.annotation.support; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import org.apache.commons.logging.Log; @@ -33,6 +37,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; @@ -41,14 +47,17 @@ import org.springframework.context.EnvironmentAware; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.EnvironmentAccessor; import org.springframework.context.expression.MapAccessor; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -75,6 +84,11 @@ import org.springframework.util.StringUtils; public abstract class AbstractAnnotationConfigSupport implements BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware { + protected static final Set INFRASTRUCTURE_ROLES = + asSet(BeanDefinition.ROLE_INFRASTRUCTURE, BeanDefinition.ROLE_SUPPORT); + + protected static final String ORG_SPRINGFRAMEWORK_DATA_GEMFIRE_PACKAGE = "org.springframework.data.gemfire"; + protected static final String ORG_SPRINGFRAMEWORK_PACKAGE = "org.springframework"; protected static final String SPRING_DATA_GEMFIRE_PROPERTY_PREFIX = "spring.data.gemfire."; private BeanFactory beanFactory; @@ -87,17 +101,6 @@ public abstract class AbstractAnnotationConfigSupport private final Log log; - /** - * Determines whether the given {@link Object} has value. The {@link Object} is valuable - * if it is not {@literal null}. - * - * @param value {@link Object} to evaluate. - * @return a boolean value indicating whether the given {@link Object} has value. - */ - protected static boolean hasValue(Object value) { - return Optional.ofNullable(value).isPresent(); - } - /** * Determines whether the given {@link Number} has value. The {@link Number} is valuable * if it is not {@literal null} and is not equal to 0.0d. @@ -109,6 +112,17 @@ public abstract class AbstractAnnotationConfigSupport return Optional.ofNullable(value).filter(it -> it.doubleValue() != 0.0d).isPresent(); } + /** + * Determines whether the given {@link Object} has value. The {@link Object} is valuable + * if it is not {@literal null}. + * + * @param value {@link Object} to evaluate. + * @return a boolean value indicating whether the given {@link Object} has value. + */ + protected static boolean hasValue(Object value) { + return Optional.ofNullable(value).isPresent(); + } + /** * Determines whether the given {@link String} has value. The {@link String} is valuable * if it is not {@literal null} or empty. @@ -190,27 +204,71 @@ public abstract class AbstractAnnotationConfigSupport return LogFactory.getLog(getClass()); } - /* (non-Javadoc) */ + /** + * Determines whether the given {@link AnnotationMetadata type meta-data} for a particular {@link Class} + * is annotated with the declared {@link #getAnnotationTypeName()}. + * + * @param importingClassMetadata {@link AnnotationMetadata type meta-data} for a particular {@link Class}. + * @return a boolean indicating whether the particular {@link Class} is annotated with + * the declared {@link #getAnnotationTypeName()}. + * @see #isAnnotationPresent(AnnotationMetadata, String) + * @see #getAnnotationTypeName() + * @see org.springframework.core.type.AnnotationMetadata + */ protected boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata) { return isAnnotationPresent(importingClassMetadata, getAnnotationTypeName()); } - /* (non-Javadoc) */ + /** + * Determines whether the given {@link AnnotationMetadata type meta-data} for a particular {@link Class} + * is annotated with the given {@link Annotation} defined by {@link String name}. + * + * @param importingClassMetadata {@link AnnotationMetadata type meta-data} for a particular {@link Class}. + * @param annotationName {@link String name} of the {@link Annotation} of interests. + * @return a boolean indicating whether the particular {@link Class} is annotated with + * the given {@link Annotation} defined by {@link String name}. + * @see org.springframework.core.type.AnnotationMetadata + */ protected boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata, String annotationName) { return importingClassMetadata.hasAnnotation(annotationName); } - /* (non-Javadoc) */ + /** + * Returns the {@link AnnotationAttributes} for the given {@link Annotation}. + * + * @param annotation {@link Annotation} to get the {@link AnnotationAttributes} for. + * @return the {@link AnnotationAttributes} for the given {@link Annotation}. + * @see org.springframework.core.annotation.AnnotationAttributes + * @see java.lang.annotation.Annotation + */ protected AnnotationAttributes getAnnotationAttributes(Annotation annotation) { return AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(annotation)); } - /* (non-Javadoc) */ + /** + * Returns {@link AnnotationAttributes} for the declared {@link #getAnnotationTypeName()}. + * + * @param importingClassMetadata {@link AnnotationMetadata type meta-data} for a particular {@link Class}. + * @return {@link AnnotationAttributes} for the declared {@link #getAnnotationTypeName()}. + * @see org.springframework.core.annotation.AnnotationAttributes + * @see org.springframework.core.type.AnnotationMetadata + * @see #getAnnotationAttributes(AnnotationMetadata, String) + * @see #getAnnotationTypeName() + */ protected AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata) { return getAnnotationAttributes(importingClassMetadata, getAnnotationTypeName()); } - /* (non-Javadoc) */ + /** + * Returns {@link AnnotationAttributes} for the given {@link String named} {@link Annotation} from the given + * {@link AnnotationMetadata type meta-data}. + * + * @param importingClassMetadata {@link AnnotationMetadata type meta-data} for a particular {@link Class}. + * @param annotationName {@link String name} of the {@link Annotation} of interests. + * @return {@link AnnotationAttributes} for the given {@link String named} {@link Annotation}. + * @see org.springframework.core.annotation.AnnotationAttributes + * @see org.springframework.core.type.AnnotationMetadata + */ protected AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata, String annotationName) { @@ -250,6 +308,104 @@ public abstract class AbstractAnnotationConfigSupport return getAnnotationType().getSimpleName(); } + /** + * Null-safe method to determine whether the given {@link Object bean} is a Spring container provided + * infrastructure bean. + * + * @param bean {@link Object} to evaluate. + * @return {@literal true} iff the {@link Object bean} is not a Spring container infrastructure bean. + * @see #isNotInfrastructureClass(String) + */ + protected boolean isNotInfrastructureBean(Object bean) { + + return Optional.ofNullable(bean) + .map(Object::getClass) + .map(Class::getName) + .filter(this::isNotInfrastructureClass).isPresent(); + } + + /** + * Null-safe method to determine whether the bean defined in the given {@link BeanDefinition} + * is a Spring container provided infrastructure bean. + * + * @param beanDefinition {@link BeanDefinition} to evaluate. + * @return {@literal true} iff the bean defined in the given {@link BeanDefinition} is not a Spring container + * infrastructure bean. + * @see org.springframework.beans.factory.config.BeanDefinition + * @see #isNotInfrastructureClass(BeanDefinition) + * @see #isNotInfrastructureRole(BeanDefinition) + */ + protected boolean isNotInfrastructureBean(BeanDefinition beanDefinition) { + return (isNotInfrastructureRole(beanDefinition) && isNotInfrastructureClass(beanDefinition)); + } + + /** + * Null-safe method to determine whether the bean defined in the given {@link BeanDefinition} + * is a Spring container infrastructure bean based on the bean's {@link Class#getName() class type}. + * + * @param beanDefinition {@link BeanDefinition} of the bean to evaluate. + * @return {@literal true} iff the bean defined in the given {@link BeanDefinition} is not a Spring container + * infrastructure bean. + * @see org.springframework.beans.factory.config.BeanDefinition + * @see #resolveBeanClassName(BeanDefinition) + * @see #isNotInfrastructureClass(String) + */ + protected boolean isNotInfrastructureClass(BeanDefinition beanDefinition) { + return resolveBeanClassName(beanDefinition).filter(this::isNotInfrastructureClass).isPresent(); + } + + /** + * Determines whether the given {@link Class#getName() class type name} is considered a Spring container + * infrastructure type. + * + * The class type name is considered a Spring container infrastructure type if the package name begins with + * 'org.springframework', excluding 'org.springframework.data.gemfire'. + * + * @param className {@link String} containing the name of the class type to evaluate. + * @return {@literal true} iff the given {@link Class#getName() class type name} is not considered a + * Spring container infrastructure type. + */ + protected boolean isNotInfrastructureClass(String className) { + return (className.startsWith(ORG_SPRINGFRAMEWORK_DATA_GEMFIRE_PACKAGE) + || !className.startsWith(ORG_SPRINGFRAMEWORK_PACKAGE)); + } + + /** + * Null-safe method to determines whether the bean defined by the given {@link BeanDefinition} + * is a Spring container infrastructure bean based on the bean's role. + * + * @param beanDefinition {@link BeanDefinition} of the bean to evaluate. + * @return {@literal true} iff the bean defined in the given {@link BeanDefinition} is not a Spring container + * infrastructure bean. + * @see org.springframework.beans.factory.config.BeanDefinition + */ + protected boolean isNotInfrastructureRole(BeanDefinition beanDefinition) { + + return Optional.ofNullable(beanDefinition) + .map(BeanDefinition::getRole) + .filter(role -> !INFRASTRUCTURE_ROLES.contains(role)) + .isPresent(); + } + + /** + * Determines whether the given {@link Method} was declared and defined by the user. + * + * A {@link Method} is considered a user-level {@link Method} if the {@link Method} is not + * an {@link Object} class method, is a {@link Method#isBridge() Bridge Method} + * or is not {@link Method#isSynthetic()} nor a Groovy method. + * + * @param method {@link Method} to evaluate. + * @return a boolean value indicating whether the {@link Method} was declared/defined by the user. + * @see java.lang.reflect.Method + */ + protected boolean isUserLevelMethod(Method method) { + + return Optional.ofNullable(method) + .filter(ClassUtils::isUserLevelMethod) + .filter(it -> !Object.class.equals(it.getDeclaringClass())) + .isPresent(); + } + /** * {@inheritDoc} */ @@ -513,7 +669,7 @@ public abstract class AbstractAnnotationConfigSupport /* (non-Javadoc) */ protected String asArrayProperty(String propertyNamePrefix, int index, String propertyNameSuffix) { return String.format("%1$s[%2$d]%3$s", propertyNamePrefix, index, - Optional.ofNullable(propertyNamePrefix).filter(StringUtils::hasText).map("."::concat).orElse("")); + Optional.ofNullable(propertyNameSuffix).filter(StringUtils::hasText).map("."::concat).orElse("")); } /* (non-Javadoc) */ @@ -669,6 +825,109 @@ public abstract class AbstractAnnotationConfigSupport .orElseThrow(() -> newIllegalArgumentException("Property [%s] is required", propertyName)); } + /** + * Resolves the {@link Annotation} with the given {@link Class type} from the {@link AnnotatedElement}. + * + * @param {@link Class Subclass type} of the resolved {@link Annotation}. + * @param annotatedElement {@link AnnotatedElement} from which to resolve the {@link Annotation}. + * @param annotationType {@link Class type} of the {@link Annotation} to resolve from the {@link AnnotatedElement}. + * @return the resolved {@link Annotation}. + * @see java.lang.annotation.Annotation + * @see java.lang.reflect.AnnotatedElement + * @see java.lang.Class + */ + protected A resolveAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + + return (annotatedElement instanceof Class + ? AnnotatedElementUtils.findMergedAnnotation(annotatedElement, annotationType) + : AnnotationUtils.findAnnotation(annotatedElement, annotationType)); + } + + /** + * Resolves the {@link Class type} of the bean defined by the given {@link BeanDefinition}. + * + * @param beanDefinition {@link BeanDefinition} defining the bean from which the {@link Class type} is resolved. + * @param registry {@link BeanDefinitionRegistry} used to resolve the {@link ClassLoader} used to resolve + * the bean's {@link Class type}. + * @return an {@link Optional} {@link Class} specifying the resolved type of the bean. + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see #resolveBeanClassLoader(BeanDefinitionRegistry) + * @see #resolveBeanClass(BeanDefinition, ClassLoader) + */ + protected Optional> resolveBeanClass(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) { + return resolveBeanClass(beanDefinition, resolveBeanClassLoader(registry)); + } + + /** + * Resolves the {@link Class type} of the bean defined by the given {@link BeanDefinition}. + * + * @param beanDefinition {@link BeanDefinition} defining the bean from which the {@link Class type} is resolved. + * @param classLoader {@link ClassLoader} used to resolve the bean's {@link Class type}. + * @return an {@link Optional} resolved {@link Class type} of the bean. + * @see java.lang.ClassLoader + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass(ClassLoader) + * @see org.springframework.util.ClassUtils#forName(String, ClassLoader) + * @see #resolveBeanClassName(BeanDefinition) + */ + private Optional> resolveBeanClass(BeanDefinition beanDefinition, ClassLoader classLoader) { + + Class beanClass = (beanDefinition instanceof AbstractBeanDefinition + ? safeResolveType(() -> ((AbstractBeanDefinition) beanDefinition).resolveBeanClass(classLoader)) : null); + + if (beanClass == null) { + beanClass = resolveBeanClassName(beanDefinition).map(beanClassName -> + safeResolveType(() -> ClassUtils.forName(beanClassName, classLoader))).orElse(null); + } + + return Optional.ofNullable(beanClass); + } + + /** + * Attempts to resolve the {@link ClassLoader} used by the {@link BeanDefinitionRegistry} + * to load {@link Class} definitions of the beans defined in the registry. + * + * @param registry {@link BeanDefinitionRegistry} from which to resolve the {@link ClassLoader}. + * @return the resolved {@link ClassLoader} from the {@link BeanDefinitionRegistry} + * or the {@link Thread#currentThread() current Thread's} {@link Thread#getContextClassLoader() context ClassLoader}. + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader() + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see java.lang.Thread#currentThread() + * @see java.lang.Thread#getContextClassLoader() + */ + protected ClassLoader resolveBeanClassLoader(BeanDefinitionRegistry registry) { + + return Optional.ofNullable(registry) + .filter(it -> it instanceof ConfigurableBeanFactory) + .map(it -> ((ConfigurableBeanFactory) it).getBeanClassLoader()) + .orElseGet(() -> Thread.currentThread().getContextClassLoader()); + } + + /** + * Resolves the class type name of the bean defined by the given {@link BeanDefinition}. + * + * @param beanDefinition {@link BeanDefinition} defining the bean from which to resolve the class type name. + * @return an {@link Optional} {@link String} containing the resolved class type name of the bean defined + * by the given {@link BeanDefinition}. + * @see org.springframework.beans.factory.config.BeanDefinition#getBeanClassName() + */ + protected Optional resolveBeanClassName(BeanDefinition beanDefinition) { + + Optional beanClassName = + Optional.ofNullable(beanDefinition).map(BeanDefinition::getBeanClassName).filter(StringUtils::hasText); + + if (!beanClassName.isPresent()) { + beanClassName = Optional.ofNullable(beanDefinition) + .filter(it -> it instanceof AnnotatedBeanDefinition) + .filter(it -> StringUtils.hasText(it.getFactoryMethodName())) + .map(it -> ((AnnotatedBeanDefinition) it).getFactoryMethodMetadata()) + .map(MethodMetadata::getReturnTypeName); + } + + return beanClassName; + } + /** * Attempts to resolve the property with the given {@link String name} from the Spring {@link Environment} * as a {@link Boolean}. @@ -786,4 +1045,39 @@ public abstract class AbstractAnnotationConfigSupport targetType, defaultValue)) .orElse(defaultValue); } + + /** + * Safely resolves a {@link Class type} returned by the supplied {@link TypeResolver} where the {@link Class type} + * resolution might result in a {@link ClassNotFoundException}, or possibly, {@link NoClassDefFoundError}. + * + * @param {@link Class} of the type being resolved. + * @param typeResolver {@link TypeResolver} used to resolve a specific {@link Class type}. + * @return the resolved {@link Class type} or {@literal null} if the {@link Class type} returned by + * the {@link TypeResolver} could not be resolved. + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport.TypeResolver + * @see java.lang.Class + */ + protected Class safeResolveType(TypeResolver typeResolver) { + + try { + return typeResolver.resolve(); + } + catch (ClassNotFoundException cause) { + return null; + } + } + + /** + * {@link TypeResolver} is a {@link FunctionalInterface} defining a contract to encapsulated the logic + * to resolve a particular {@link Class type}. + * + * Implementations are free to decide on how a {@link Class type} gets resolved, such as + * with {@link Class#forName(String)} or perhaps using {@link ClassLoader#defineClass(String, byte[], int, int)}. + * + * @param {@link Class} of the type to resolve. + */ + @FunctionalInterface + protected interface TypeResolver { + Class resolve() throws ClassNotFoundException; + } } diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsIntegrationTests.java index 8e40a786..c6abb7e1 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsIntegrationTests.java @@ -67,12 +67,14 @@ public class EnableCachingDefinedRegionsIntegrationTests { @Test public void cacheRegionsExists() { + assertThat(gemfireCache.getRegion("/Example")).isNotNull(); assertThat(gemfireCache.getRegion("/Echo")).isNotNull(); } @Test public void echoServiceOperationsAreSuccessful() { + assertThat(echoService.isCacheMiss()).isFalse(); assertThat(echoService.echo("one")).isEqualTo("one"); assertThat(echoService.isCacheMiss()).isTrue(); @@ -97,7 +99,7 @@ public class EnableCachingDefinedRegionsIntegrationTests { @Bean TestService testService() { - return new DefaultTestService(); + return new CachingTestService(); } } @@ -121,7 +123,7 @@ public class EnableCachingDefinedRegionsIntegrationTests { Object testMethod(String key); } - static class DefaultTestService implements TestService { + static class CachingTestService implements TestService { @CachePut("Example") public Object testMethod(String key) { diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsUnitTests.java index 60c5a48f..48da903b 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegionsUnitTests.java @@ -25,33 +25,27 @@ 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 static org.springframework.data.gemfire.util.ArrayUtils.asArray; import static org.springframework.data.gemfire.util.CollectionUtils.asSet; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import java.util.Set; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; -import org.springframework.core.SpringVersion; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.type.MethodMetadata; -import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.test.support.MapBuilder; import org.springframework.stereotype.Service; @@ -73,7 +67,12 @@ import org.springframework.stereotype.Service; public class EnableCachingDefinedRegionsUnitTests { // Subject Under Test (SUT) - private CachingDefinedRegionsConfiguration configuration = new CachingDefinedRegionsConfiguration(); + private CachingDefinedRegionsConfiguration configuration; + + @Before + public void setup() { + this.configuration = new CachingDefinedRegionsConfiguration(); + } @SuppressWarnings("unchecked") private BeanDefinition mockBeanDefinition(Class beanClass) { @@ -112,6 +111,99 @@ public class EnableCachingDefinedRegionsUnitTests { return mockBeanDefinitionRegistry; } + @Test + public void setGetAndResolveClientRegionShortcut() { + + assertThat(this.configuration.getClientRegionShortcut().orElse(null)) + .isEqualTo(ClientRegionShortcut.PROXY); + assertThat(this.configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.PROXY); + + this.configuration.setClientRegionShortcut(ClientRegionShortcut.LOCAL); + + assertThat(this.configuration.getClientRegionShortcut().orElse(null)) + .isEqualTo(ClientRegionShortcut.LOCAL); + assertThat(this.configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL); + + this.configuration.setClientRegionShortcut(null); + + assertThat(this.configuration.getClientRegionShortcut().orElse(null)).isNull(); + assertThat(this.configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.PROXY); + + this.configuration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); + + assertThat(this.configuration.getClientRegionShortcut().orElse(null)) + .isEqualTo(ClientRegionShortcut.CACHING_PROXY); + assertThat(this.configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); + } + + @Test + public void setGetAndResolvePoolName() { + + assertThat(this.configuration.getPoolName().orElse(null)) + .isEqualTo(ClientRegionFactoryBean.DEFAULT_POOL_NAME); + assertThat(this.configuration.resolvePoolName()).isEqualTo(ClientRegionFactoryBean.DEFAULT_POOL_NAME); + + this.configuration.setPoolName(null); + + assertThat(this.configuration.getPoolName().orElse(null)).isNull(); + assertThat(this.configuration.resolvePoolName()).isEqualTo(ClientRegionFactoryBean.DEFAULT_POOL_NAME); + + this.configuration.setPoolName("TestPool"); + + assertThat(this.configuration.getPoolName().orElse(null)).isEqualTo("TestPool"); + assertThat(this.configuration.resolvePoolName()).isEqualTo("TestPool"); + } + + @Test + public void setGetAndResolveServerRegionShortcut() { + + assertThat(this.configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.PARTITION); + assertThat(this.configuration.resolveServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION); + + this.configuration.setServerRegionShortcut(RegionShortcut.LOCAL); + + assertThat(this.configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.LOCAL); + assertThat(this.configuration.resolveServerRegionShortcut()).isEqualTo(RegionShortcut.LOCAL); + + this.configuration.setServerRegionShortcut(null); + + assertThat(this.configuration.getServerRegionShortcut().orElse(null)).isNull(); + assertThat(this.configuration.resolveServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION); + + this.configuration.setServerRegionShortcut(RegionShortcut.REPLICATE); + + assertThat(this.configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.REPLICATE); + assertThat(this.configuration.resolveServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); + } + + @Test + public void setImportMetadataConfiguresClientRegionShortcutPoolNameAndServerRegionShortcut() { + + AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class); + + Map annotationAttributes = MapBuilder.newMapBuilder() + .put("clientRegionShortcut", ClientRegionShortcut.LOCAL_PERSISTENT) + .put("poolName", "SwimmingPool") + .put("serverRegionShortcut", RegionShortcut.PARTITION_PERSISTENT) + .build(); + + when(mockAnnotationMetadata.hasAnnotation(eq(EnableCachingDefinedRegions.class.getName()))).thenReturn(true); + when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableCachingDefinedRegions.class.getName()))) + .thenReturn(annotationAttributes); + + this.configuration.setImportMetadata(mockAnnotationMetadata); + + assertThat(this.configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL_PERSISTENT); + assertThat(this.configuration.resolvePoolName()).isEqualTo("SwimmingPool"); + assertThat(this.configuration.resolveServerRegionShortcut()).isEqualTo(RegionShortcut.PARTITION_PERSISTENT); + + verify(mockAnnotationMetadata, times(1)) + .hasAnnotation(eq(EnableCachingDefinedRegions.class.getName())); + + verify(mockAnnotationMetadata, times(1)) + .getAnnotationAttributes(eq(EnableCachingDefinedRegions.class.getName())); + } + @Test public void cacheableServiceOneRegistersRegionsOneAndTwo() { @@ -128,9 +220,6 @@ public class EnableCachingDefinedRegionsUnitTests { verify(mockBeanDefinitionRegistry, times(1)) .getBeanDefinition(eq("cacheableServiceOne")); - verify(mockBeanDefinitionRegistry, times(1)) - .containsBeanDefinition(eq(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)); - verify(mockBeanDefinitionRegistry, times(2)) .registerBeanDefinition(anyString(), any(BeanDefinition.class)); @@ -160,9 +249,6 @@ public class EnableCachingDefinedRegionsUnitTests { verify(mockBeanDefinitionRegistry, times(1)) .getBeanDefinition(eq("cacheableServiceTwo")); - verify(mockBeanDefinitionRegistry, times(1)) - .containsBeanDefinition(eq(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)); - verify(mockBeanDefinitionRegistry, times(registeredRegionBeanNames.size())) .registerBeanDefinition(anyString(), any(BeanDefinition.class)); @@ -195,9 +281,6 @@ public class EnableCachingDefinedRegionsUnitTests { verify(mockBeanDefinitionRegistry, times(1)) .getBeanDefinition(eq("cacheableServiceThree")); - verify(mockBeanDefinitionRegistry, times(1)) - .containsBeanDefinition(eq(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)); - verify(mockBeanDefinitionRegistry, times(registeredRegionBeanNames.size())) .registerBeanDefinition(anyString(), any(BeanDefinition.class)); @@ -230,9 +313,6 @@ public class EnableCachingDefinedRegionsUnitTests { verify(mockBeanDefinitionRegistry, times(1)) .getBeanDefinition(eq("cacheableServiceFour")); - verify(mockBeanDefinitionRegistry, times(1)) - .containsBeanDefinition(eq(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)); - verify(mockBeanDefinitionRegistry, never()) .registerBeanDefinition(eq("RegionTwentyFive"), any(BeanDefinition.class)); @@ -274,9 +354,6 @@ public class EnableCachingDefinedRegionsUnitTests { verify(mockBeanDefinitionRegistry, times(1)) .getBeanDefinition(eq("cacheableServiceThree")); - verify(mockBeanDefinitionRegistry, times(3)) - .containsBeanDefinition(eq(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)); - verify(mockBeanDefinitionRegistry, times(registeredRegionBeanNames.size())) .registerBeanDefinition(anyString(), any(BeanDefinition.class)); @@ -309,329 +386,6 @@ public class EnableCachingDefinedRegionsUnitTests { .containsExactly("RegionSixteen", "RegionSeventeen"); } - @Test - @SuppressWarnings("unchecked") - public void resolveBeanClassFromBeanClassName() throws ClassNotFoundException { - - AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class); - - BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - - when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))).thenReturn((Class) CacheableServiceOne.class); - - assertThat(this.configuration.resolveBeanClass(mockBeanDefinition, mockBeanDefinitionRegistry).orElse(null)) - .isEqualTo(CacheableServiceOne.class); - - verify(mockBeanDefinition, times(1)) - .resolveBeanClass(eq(Thread.currentThread().getContextClassLoader())); - - verifyNoMoreInteractions(mockBeanDefinition); - - verifyZeroInteractions(mockBeanDefinitionRegistry); - } - - @Test - @SuppressWarnings("unchecked") - public void resolveBeanClassFromFactoryMethodReturnType() throws ClassNotFoundException { - - AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); - - BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - - MethodMetadata mockMethodMetadata = mock(MethodMetadata.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn(" "); - when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); - when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(mockMethodMetadata); - when(mockMethodMetadata.getReturnTypeName()).thenReturn(CacheableServiceTwo.class.getName()); - - assertThat(this.configuration.resolveBeanClass(mockBeanDefinition, mockBeanDefinitionRegistry).orElse(null)) - .isEqualTo(CacheableServiceTwo.class); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); - verify(mockMethodMetadata, times(1)).getReturnTypeName(); - - verifyNoMoreInteractions(mockBeanDefinition); - verifyNoMoreInteractions(mockMethodMetadata); - - verifyZeroInteractions(mockBeanDefinitionRegistry); - } - - @Test - public void isNotInfrastructureBeanIsTrue() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(CacheableServiceOne.class); - - assertThat(this.configuration.isNotInfrastructureBean(mockBeanDefinition)).isTrue(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getRole(); - } - - @Test - public void isNotInfrastructureBeanWithInfrastructureClassIsFalse() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(SpringVersion.class); - - assertThat(this.configuration.isNotInfrastructureBean(mockBeanDefinition)).isFalse(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getRole(); - } - - @Test - public void isNotInfrastructureBeanWithInfrastructureRoleIsFalse() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(CacheableServiceOne.class); - - when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_INFRASTRUCTURE); - - assertThat(this.configuration.isNotInfrastructureBean(mockBeanDefinition)).isFalse(); - - verify(mockBeanDefinition, never()).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getRole(); - } - - @Test - public void isNotInfrastructureClassWithBeanDefinitionIsTrue() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(CacheableServiceOne.class); - - assertThat(this.configuration.isNotInfrastructureClass(mockBeanDefinition)).isTrue(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - } - - @Test - public void isNotInfrastructureClassWithBeanDefinitionIsFalse() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(SpringVersion.class); - - assertThat(this.configuration.isNotInfrastructureClass(mockBeanDefinition)).isFalse(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - } - - @Test - public void isNotInfrastructureClassIsTrue() { - assertThat(this.configuration.isNotInfrastructureClass(CacheableServiceOne.class.getName())).isTrue(); - assertThat(this.configuration.isNotInfrastructureClass("org.example.app.MyClass")).isTrue(); - } - - @Test - public void isNotInfrastructureClassIsFalse() { - assertThat(this.configuration.isNotInfrastructureClass("org.springframework.SomeType")).isFalse(); - assertThat(this.configuration.isNotInfrastructureClass("org.springframework.core.type.SomeType")) - .isFalse(); - } - - @Test - public void isNotInfrastructureRoleIsTrue() { - - BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); - - when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_APPLICATION).thenReturn(Integer.MAX_VALUE); - - assertThat(this.configuration.isNotInfrastructureRole(mockBeanDefinition)).isTrue(); - assertThat(this.configuration.isNotInfrastructureRole(mockBeanDefinition)).isTrue(); - - verify(mockBeanDefinition, times(2)).getRole(); - } - - @Test - public void isNotInfrastructureRoleIsFalse() { - - BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); - - when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_INFRASTRUCTURE) - .thenReturn(BeanDefinition.ROLE_SUPPORT); - - assertThat(this.configuration.isNotInfrastructureRole(mockBeanDefinition)).isFalse(); - assertThat(this.configuration.isNotInfrastructureRole(mockBeanDefinition)).isFalse(); - - verify(mockBeanDefinition, times(2)).getRole(); - } - - @Test - public void isUserLevelMethodWithNullMethodReturnsFalse() { - assertThat(this.configuration.isUserLevelMethod(null)).isFalse(); - } - - @Test - public void isUserLevelMethodWithObjectMethodReturnsFalse() throws NoSuchMethodException { - assertThat(this.configuration.isUserLevelMethod(Object.class.getMethod("equals", Object.class))) - .isFalse(); - } - - @Test - public void isUserLevelMethodWithUserMethodReturnsTrue() throws NoSuchMethodException { - assertThat(this.configuration.isUserLevelMethod(CacheableServiceOne.class.getMethod("cacheableMethodOne"))) - .isTrue(); - } - - @Test - public void resolveAnnotationFromClass() { - - Annotation cacheable = this.configuration.resolveAnnotation(CacheableServiceFour.class, Cacheable.class); - - assertThat(cacheable).isNotNull(); - assertThat(AnnotationUtils.getAnnotationAttributes(cacheable).get("cacheNames")) - .isEqualTo(asArray("RegionFifteen")); - } - - @Test - public void resolveAnnotationFromMethod() throws NoSuchMethodException { - - Method cacheableMethodFour = CacheableServiceFour.class.getMethod("cacheableMethodFour"); - - Annotation cachePut = this.configuration.resolveAnnotation(cacheableMethodFour, CachePut.class); - - assertThat(cachePut).isNotNull(); - assertThat(AnnotationUtils.getAnnotationAttributes(cachePut).get("cacheNames")) - .isEqualTo(asArray("RegionTwentyOne", "RegionTwentyTwo")); - } - - @Test - public void resolveAnnotationIsUnresolvable() throws NoSuchMethodException { - - Method cacheableMethodFive = CacheableServiceFour.class.getMethod("cacheableMethodFive"); - - assertThat(this.configuration.resolveAnnotation(cacheableMethodFive, Cacheable.class)).isNull(); - } - - @Test - public void resolveBeanClassIsUnresolvable() throws ClassNotFoundException { - - AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn("non.existing.bean.Class"); - when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))).thenThrow(new ClassNotFoundException("TEST")); - - assertThat(this.configuration.resolveBeanClass(mockBeanDefinition, null).orElse(null)).isNull(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)) - .resolveBeanClass(eq(Thread.currentThread().getContextClassLoader())); - - verifyNoMoreInteractions(mockBeanDefinition); - } - - @Test - public void resolveBeanClassLoaderReturnsRegistryClassLoader() { - - ClassLoader mockClassLoader = mock(ClassLoader.class); - - DefaultListableBeanFactory mockBeanDefinitionRegistry = mock(DefaultListableBeanFactory.class); - - when(mockBeanDefinitionRegistry.getBeanClassLoader()).thenReturn(mockClassLoader); - - assertThat(this.configuration.resolveBeanClassLoader(mockBeanDefinitionRegistry)).isEqualTo(mockClassLoader); - - verify(mockBeanDefinitionRegistry, times(1)).getBeanClassLoader(); - } - - @Test - public void resolveBeanClassLoaderReturnsThreadContextClassLoader() { - - BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - - assertThat(this.configuration.resolveBeanClassLoader(mockBeanDefinitionRegistry)) - .isEqualTo(Thread.currentThread().getContextClassLoader()); - - verifyZeroInteractions(mockBeanDefinitionRegistry); - } - - @Test - public void resolveBeanClassNameReturnsBeanDefinitionBeanClassName() { - - BeanDefinition mockBeanDefinition = mockBeanDefinition(CacheableServiceOne.class); - - assertThat(this.configuration.resolveBeanClassName(mockBeanDefinition).orElse(null)) - .isEqualTo(CacheableServiceOne.class.getName()); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verifyNoMoreInteractions(mockBeanDefinition); - } - - @Test - public void resolveBeanClassNameReturnsFactoryMethodReturnType() { - - AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); - - MethodMetadata mockMethodMetadata = mock(MethodMetadata.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn(null); - when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); - when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(mockMethodMetadata); - when(mockMethodMetadata.getReturnTypeName()).thenReturn(CacheableServiceTwo.class.getName()); - - assertThat(this.configuration.resolveBeanClassName(mockBeanDefinition).orElse(null)) - .isEqualTo(CacheableServiceTwo.class.getName()); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); - } - - @Test - public void resolveBeanClassNameWithNoFactoryMethodMetadataReturnsEmpty() { - - AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn(null); - when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); - when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(null); - - assertThat(this.configuration.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); - verifyNoMoreInteractions(mockBeanDefinition); - } - - @Test - public void resolveBeanClassNameWithNonAnnotatedBeanDefinitionReturnsEmpty() { - - BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn(null); - when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); - - assertThat(this.configuration.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodName(); - verifyNoMoreInteractions(mockBeanDefinition); - } - - @Test - public void resolveBeanClassNameWithNoFactoryMethodNameReturnsEmpty() { - - BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); - - when(mockBeanDefinition.getBeanClassName()).thenReturn(null); - when(mockBeanDefinition.getFactoryMethodName()).thenReturn(" "); - - assertThat(this.configuration.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); - - verify(mockBeanDefinition, times(1)).getBeanClassName(); - verify(mockBeanDefinition, times(1)).getFactoryMethodName(); - verifyNoMoreInteractions(mockBeanDefinition); - } - - @Test - public void safeResolveTypeReturnsType() { - assertThat(this.configuration.safeResolveType(() -> Object.class)).isEqualTo(Object.class); - } - - @Test - public void safeResolveTypeThrowingClassNotFoundExceptionReturnsNull() { - assertThat(this.configuration.safeResolveType(() -> { throw new ClassNotFoundException("TEST"); })).isNull(); - } - @Service @Cacheable(cacheNames = { "RegionOne", "RegionTwo" }) @SuppressWarnings("unused") diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java index a2a594eb..43cd4dd2 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupportTests.java @@ -20,14 +20,35 @@ 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.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.util.ArrayUtils.asArray; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.SpringVersion; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.MethodMetadata; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.mapping.annotation.Region; +import org.springframework.data.gemfire.test.model.Person; /** * Unit tests for {@link AbstractAnnotationConfigSupport}. @@ -35,21 +56,149 @@ import org.mockito.junit.MockitoJUnitRunner; * @author John Blum * @see org.junit.Test * @see org.junit.runner.RunWith - * @see org.mockito.Mock * @see org.mockito.Mockito * @see org.mockito.junit.MockitoJUnitRunner + * @see org.springframework.beans.factory.annotation.AnnotatedBeanDefinition + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.AbstractBeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport * @since 1.1.0 */ @RunWith(MockitoJUnitRunner.class) public class AbstractAnnotationConfigSupportTests { - @Mock private AbstractAnnotationConfigSupport support; + @Before + public void setup() { + this.support = spy(new TestAnnotationConfigSupport()); + } + + @SuppressWarnings("unchecked") + private BeanDefinition mockBeanDefinition(Class beanClass) { + + AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class, beanClass.getSimpleName()); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(beanClass.getName()); + when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_APPLICATION); + + return mockBeanDefinition; + } + + @Test + public void isNotInfrastructureBeanIsTrue() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(GemfireTemplate.class); + + assertThat(this.support.isNotInfrastructureBean(mockBeanDefinition)).isTrue(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getRole(); + } + + @Test + public void isNotInfrastructureBeanWithInfrastructureClassIsFalse() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(SpringVersion.class); + + assertThat(this.support.isNotInfrastructureBean(mockBeanDefinition)).isFalse(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getRole(); + } + + @Test + public void isNotInfrastructureBeanWithInfrastructureRoleIsFalse() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(Object.class); + + when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_INFRASTRUCTURE); + + assertThat(this.support.isNotInfrastructureBean(mockBeanDefinition)).isFalse(); + + verify(mockBeanDefinition, never()).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getRole(); + } + + @Test + public void isNotInfrastructureClassWithBeanDefinitionIsTrue() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(Object.class); + + assertThat(this.support.isNotInfrastructureClass(mockBeanDefinition)).isTrue(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + } + + @Test + public void isNotInfrastructureClassWithBeanDefinitionIsFalse() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(SpringVersion.class); + + assertThat(this.support.isNotInfrastructureClass(mockBeanDefinition)).isFalse(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + } + + @Test + public void isNotInfrastructureClassIsTrue() { + assertThat(this.support.isNotInfrastructureClass("org.example.app.model.MyClass")).isTrue(); + } + + @Test + public void isNotInfrastructureClassIsFalse() { + assertThat(this.support.isNotInfrastructureClass("org.springframework.SomeType")).isFalse(); + assertThat(this.support.isNotInfrastructureClass("org.springframework.core.type.SomeType")) + .isFalse(); + } + + @Test + public void isNotInfrastructureRoleIsTrue() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_APPLICATION).thenReturn(Integer.MAX_VALUE); + + assertThat(this.support.isNotInfrastructureRole(mockBeanDefinition)).isTrue(); + assertThat(this.support.isNotInfrastructureRole(mockBeanDefinition)).isTrue(); + + verify(mockBeanDefinition, times(2)).getRole(); + } + + @Test + public void isNotInfrastructureRoleIsFalse() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + when(mockBeanDefinition.getRole()).thenReturn(BeanDefinition.ROLE_INFRASTRUCTURE) + .thenReturn(BeanDefinition.ROLE_SUPPORT); + + assertThat(this.support.isNotInfrastructureRole(mockBeanDefinition)).isFalse(); + assertThat(this.support.isNotInfrastructureRole(mockBeanDefinition)).isFalse(); + + verify(mockBeanDefinition, times(2)).getRole(); + } + + @Test + public void isUserLevelMethodWithNullMethodReturnsFalse() { + assertThat(this.support.isUserLevelMethod(null)).isFalse(); + } + + @Test + public void isUserLevelMethodWithObjectMethodReturnsFalse() throws NoSuchMethodException { + assertThat(this.support.isUserLevelMethod(Object.class.getMethod("equals", Object.class))) + .isFalse(); + } + + @Test + public void isUserLevelMethodWithUserMethodReturnsTrue() throws NoSuchMethodException { + assertThat(this.support.isUserLevelMethod(Person.class.getMethod("getName"))).isTrue(); + } + @Test public void requirePropertyWithNonStringValueIsSuccessful() { - when(support.requireProperty(anyString(), any())).thenCallRealMethod(); + when(support.resolveProperty(anyString(), eq(Integer.class), eq(null))).thenReturn(1); assertThat(support.requireProperty("key", Integer.class)).isEqualTo(1); @@ -60,7 +209,7 @@ public class AbstractAnnotationConfigSupportTests { @Test public void requirePropertyWithStringValueIsSuccessful() { - when(support.requireProperty(anyString(), any())).thenCallRealMethod(); + when(support.resolveProperty(anyString(), eq(String.class), eq(null))).thenReturn("test"); assertThat(support.requireProperty("key", String.class)).isEqualTo("test"); @@ -71,13 +220,14 @@ public class AbstractAnnotationConfigSupportTests { @Test(expected = IllegalArgumentException.class) public void requirePropertyWithEmptyStringThrowsIllegalArgumentException() { - when(support.requireProperty(anyString(), any())).thenCallRealMethod(); + when(support.resolveProperty(anyString(), eq(String.class), eq(null))).thenReturn(" "); try { support.requireProperty("key", String.class); } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("Property [key] is required"); assertThat(expected).hasNoCause(); @@ -91,7 +241,7 @@ public class AbstractAnnotationConfigSupportTests { @Test(expected = IllegalArgumentException.class) public void requirePropertyWithNullValueThrowsIllegalArgumentException() { - when(support.requireProperty(anyString(), any())).thenCallRealMethod(); + when(support.resolveProperty(anyString(), any(), eq(null))).thenReturn(null); try { @@ -108,4 +258,288 @@ public class AbstractAnnotationConfigSupportTests { .resolveProperty(eq("key"), eq(Integer.class), eq(null)); } } + + @Test + public void resolveAnnotationFromClass() { + + Annotation region = this.support.resolveAnnotation(Person.class, Region.class); + + assertThat(region).isNotNull(); + assertThat(AnnotationUtils.getAnnotationAttributes(region).get("value")).isEqualTo("People"); + } + + @Test + public void resolveAnnotationFromMethod() throws NoSuchMethodException { + + Method cacheableMethod = TestService.class.getMethod("cacheableMethod"); + + Annotation cacheable = this.support.resolveAnnotation(cacheableMethod, Cacheable.class); + + assertThat(cacheable).isNotNull(); + assertThat(AnnotationUtils.getAnnotationAttributes(cacheable).get("cacheNames")) + .isEqualTo(asArray("cacheOne", "cacheTwo")); + } + + @Test + public void resolveAnnotationIsUnresolvable() throws NoSuchMethodException { + + Method cacheableMethodFive = TestService.class.getMethod("nonCacheableMethod"); + + assertThat(this.support.resolveAnnotation(cacheableMethodFive, Cacheable.class)).isNull(); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveBeanClassFromBeanDefinition() throws ClassNotFoundException { + + AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class); + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistryConfigurableBeanFactory.class); + + ClassLoader mockClassLoader = mock(ClassLoader.class); + + when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))).thenReturn((Class) Object.class); + when(((ConfigurableBeanFactory) mockBeanDefinitionRegistry).getBeanClassLoader()).thenReturn(mockClassLoader); + + assertThat(this.support.resolveBeanClass(mockBeanDefinition, mockBeanDefinitionRegistry).orElse(null)) + .isEqualTo(Object.class); + + verify(mockBeanDefinition, never()).getBeanClassName(); + verify(mockBeanDefinition, never()).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)).resolveBeanClass(eq(mockClassLoader)); + verify(((ConfigurableBeanFactory) mockBeanDefinitionRegistry), times(1)) + .getBeanClassLoader(); + } + + @Test + public void resolveBeanClassUsingRegistryClassLoaderAndBeanClassName() throws ClassNotFoundException { + + AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class); + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistryConfigurableBeanFactory.class); + + ClassLoader testClassLoader = AbstractAnnotationConfigSupport.class.getClassLoader(); + + when(mockBeanDefinition.getBeanClassName()).thenReturn("java.lang.Object"); + when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))).thenThrow(new ClassNotFoundException("test")); + when(((ConfigurableBeanFactory) mockBeanDefinitionRegistry).getBeanClassLoader()).thenReturn(testClassLoader); + + assertThat(this.support.resolveBeanClass(mockBeanDefinition, mockBeanDefinitionRegistry).orElse(null)) + .isEqualTo(Object.class); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, never()).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)).resolveBeanClass(eq(testClassLoader)); + verify((ConfigurableBeanFactory) mockBeanDefinitionRegistry, times(1)) + .getBeanClassLoader(); + } + + @Test + public void resolveBeanClassUsingThreadContextClassLoaderAndFactoryMethodReturnType() + throws ClassNotFoundException { + + AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistryConfigurableBeanFactory.class); + + MethodMetadata mockFactoryMethodMetadata = mock(MethodMetadata.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(null); + when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); + when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(mockFactoryMethodMetadata); + when(((ConfigurableBeanFactory) mockBeanDefinitionRegistry).getBeanClassLoader()).thenReturn(null); + when(mockFactoryMethodMetadata.getReturnTypeName()).thenReturn("java.lang.Object"); + + assertThat(this.support.resolveBeanClass(mockBeanDefinition, mockBeanDefinitionRegistry) + .orElse(null)).isEqualTo(Object.class); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); + verify((ConfigurableBeanFactory) mockBeanDefinitionRegistry, times(1)) + .getBeanClassLoader(); + verify(mockFactoryMethodMetadata, times(1)).getReturnTypeName(); + } + + @Test + public void resolveBeanClassIsUnresolvable() throws ClassNotFoundException { + + AbstractBeanDefinition mockBeanDefinition = mock(AbstractBeanDefinition.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn("non.existing.bean.Class"); + when(mockBeanDefinition.resolveBeanClass(any(ClassLoader.class))) + .thenThrow(new ClassNotFoundException("Class [non.existing.bean.Class] not found")); + + assertThat(this.support.resolveBeanClass(mockBeanDefinition, null).orElse(null)).isNull(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, never()).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)) + .resolveBeanClass(eq(Thread.currentThread().getContextClassLoader())); + + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void resolveBeanClassLoaderIsNullSafe() { + assertThat(this.support.resolveBeanClassLoader(null)) + .isEqualTo(Thread.currentThread().getContextClassLoader()); + } + + @Test + public void resolveBeanClassLoaderReturnsRegistryClassLoader() { + + ClassLoader mockClassLoader = mock(ClassLoader.class); + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistryConfigurableBeanFactory.class); + + when(((ConfigurableBeanFactory) mockBeanDefinitionRegistry).getBeanClassLoader()).thenReturn(mockClassLoader); + + assertThat(this.support.resolveBeanClassLoader(mockBeanDefinitionRegistry)).isEqualTo(mockClassLoader); + + verify((ConfigurableBeanFactory) mockBeanDefinitionRegistry, times(1)) + .getBeanClassLoader(); + } + + @Test + public void resolveBeanClassLoaderReturnsThreadContextClassLoaderWhenRegistryClassLoaderIsNull() { + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistryConfigurableBeanFactory.class); + + when(((ConfigurableBeanFactory) mockBeanDefinitionRegistry).getBeanClassLoader()).thenReturn(null); + + assertThat(this.support.resolveBeanClassLoader(mockBeanDefinitionRegistry)) + .isEqualTo(Thread.currentThread().getContextClassLoader()); + + verify((ConfigurableBeanFactory) mockBeanDefinitionRegistry, times(1)) + .getBeanClassLoader(); + } + + @Test + public void resolveBeanClassLoaderReturnsThreadContextClassLoaderWhenRegistryIsNotConfigurable() { + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); + + assertThat(this.support.resolveBeanClassLoader(mockBeanDefinitionRegistry)) + .isEqualTo(Thread.currentThread().getContextClassLoader()); + + verifyZeroInteractions(mockBeanDefinitionRegistry); + } + + @Test + public void resolveBeanClassNameIsNullSafe() { + assertThat(this.support.resolveBeanClassName(null).orElse(null)).isNull(); + } + + @Test + public void resolveBeanClassNameReturnsBeanClassName() { + + BeanDefinition mockBeanDefinition = mockBeanDefinition(Person.class); + + assertThat(this.support.resolveBeanClassName(mockBeanDefinition).orElse(null)) + .isEqualTo(Person.class.getName()); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void resolveBeanClassNameReturnsFactoryMethodReturnTypeName() { + + AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); + + MethodMetadata mockFactoryMethodMetadata = mock(MethodMetadata.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(null); + when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); + when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(mockFactoryMethodMetadata); + when(mockFactoryMethodMetadata.getReturnTypeName()).thenReturn(Person.class.getName()); + + assertThat(this.support.resolveBeanClassName(mockBeanDefinition).orElse(null)) + .isEqualTo(Person.class.getName()); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); + verify(mockFactoryMethodMetadata, times(1)).getReturnTypeName(); + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void resolveBeanClassNameWithNoFactoryMethodMetadataReturnsEmpty() { + + AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(" "); + when(mockBeanDefinition.getFactoryMethodName()).thenReturn("testFactoryMethod"); + when(mockBeanDefinition.getFactoryMethodMetadata()).thenReturn(null); + + assertThat(this.support.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodMetadata(); + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void resolveBeanClassNameWithNoFactoryMethodNameReturnsEmpty() { + + AnnotatedBeanDefinition mockBeanDefinition = mock(AnnotatedBeanDefinition.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(""); + when(mockBeanDefinition.getFactoryMethodName()).thenReturn(" "); + + assertThat(this.support.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getFactoryMethodName(); + verify(mockBeanDefinition, never()).getFactoryMethodMetadata(); + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void resolveBeanClassNameWithNonAnnotatedBeanDefinitionReturnsEmpty() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + when(mockBeanDefinition.getBeanClassName()).thenReturn(null); + + assertThat(this.support.resolveBeanClassName(mockBeanDefinition).orElse(null)).isNull(); + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, never()).getFactoryMethodName(); + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void safeResolveTypeReturnsType() { + assertThat(this.support.safeResolveType(() -> Object.class)).isEqualTo(Object.class); + } + + @Test + public void safeResolveTypeThrowingClassNotFoundExceptionReturnsNull() { + assertThat(this.support.safeResolveType(() -> { throw new ClassNotFoundException("TEST"); })).isNull(); + } + + interface BeanDefinitionRegistryConfigurableBeanFactory extends BeanDefinitionRegistry, ConfigurableBeanFactory { + } + + @SuppressWarnings("unused") + interface TestService { + + @Cacheable({ "cacheOne", "cacheTwo" }) + void cacheableMethod(); + + void nonCacheableMethod(); + + } + + class TestAnnotationConfigSupport extends AbstractAnnotationConfigSupport { + + @Override + protected Class getAnnotationType() { + throw new UnsupportedOperationException("Not Implemented"); + } + } }