From aca2edbce54ce8820ae8af291b140670628dba5c Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 8 Dec 2016 16:13:56 -0800 Subject: [PATCH] SGF-548 - Configure Regions with annotations. Related JIRA: SGF-250 - @EnableGemfireRegions for @Region. --- src/main/asciidoc/reference/repositories.adoc | 2 +- .../FixedPartitionAttributesFactoryBean.java | 145 +++-- .../PartitionAttributesFactoryBean.java | 78 ++- .../data/gemfire/RegionFactoryBean.java | 19 + .../data/gemfire/RegionLookupFactoryBean.java | 11 +- .../client/ClientRegionFactoryBean.java | 19 + .../ApacheShiroSecurityConfiguration.java | 2 +- .../EnableEntityDefinedRegions.java | 110 ++++ .../EntityDefinedRegionsConfiguration.java | 604 ++++++++++++++++++ ...emFireCacheTypeAwareRegionFactoryBean.java | 224 +++++++ .../GemFireComponentClassTypeScanner.java | 298 +++++++++ .../mapping/GemfirePersistentEntity.java | 110 +++- .../data/gemfire/mapping/Region.java | 44 -- .../mapping/annotation/ClientRegion.java | 108 ++++ .../mapping/annotation/LocalRegion.java | 111 ++++ .../mapping/annotation/PartitionRegion.java | 180 ++++++ .../gemfire/mapping/annotation/Region.java | 74 +++ .../mapping/annotation/ReplicateRegion.java | 121 ++++ ...mfireRepositoryConfigurationExtension.java | 4 +- .../support/GemfireRepositoryFactory.java | 4 +- .../support/ClientRegionShortcutWrapper.java | 35 +- .../support/RegionShortcutWrapper.java | 69 +- .../data/gemfire/util/ArrayUtils.java | 14 + .../data/gemfire/util/CollectionUtils.java | 58 +- ...ledClientCacheRegionsIntegrationTests.java | 2 +- .../PartitionAttributesFactoryBeanTest.java | 21 +- ...yDefinedRegionsConfigurationUnitTests.java | 522 +++++++++++++++ .../test/entities/ClientRegionEntity.java | 37 ++ .../CollocatedPartitionRegionEntity.java | 38 ++ .../test/entities/GenericRegionEntity.java | 38 ++ .../test/entities/LocalRegionEntity.java | 36 ++ .../annotation/test/entities/NonEntity.java | 28 + .../test/entities/PartitionRegionEntity.java | 44 ++ .../test/entities/ReplicateRegionEntity.java | 35 + .../mapping/GemfireMappingContextTest.java | 1 + .../GemfirePersistentEntityUnitTests.java | 3 +- .../cdi/SamplePersonRepository.java | 2 +- ...eRepositoryConfigurationExtensionTest.java | 2 +- .../gemfire/repository/sample/Account.java | 7 +- .../gemfire/repository/sample/Address.java | 4 +- .../gemfire/repository/sample/Algorithm.java | 4 +- .../repository/sample/CatRepository.java | 2 +- .../gemfire/repository/sample/Customer.java | 7 +- .../repository/sample/DogRepository.java | 2 +- .../gemfire/repository/sample/GuestUser.java | 4 +- .../gemfire/repository/sample/Person.java | 2 +- .../data/gemfire/repository/sample/Plant.java | 4 +- .../gemfire/repository/sample/Programmer.java | 4 +- .../repository/sample/RabbitRepository.java | 4 +- .../gemfire/repository/sample/RootUser.java | 4 +- .../data/gemfire/repository/sample/User.java | 4 +- .../GemfireRepositoryFactoryUnitTests.java | 2 +- ...undCachePutCacheEvictIntegrationTests.java | 2 +- .../data/gemfire/test/model/Person.java | 2 +- .../gemfire/util/ArrayUtilsUnitTests.java | 31 +- .../util/CollectionUtilsUnitTests.java | 98 ++- 56 files changed, 3183 insertions(+), 257 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireComponentClassTypeScanner.java delete mode 100644 src/main/java/org/springframework/data/gemfire/mapping/Region.java create mode 100644 src/main/java/org/springframework/data/gemfire/mapping/annotation/ClientRegion.java create mode 100644 src/main/java/org/springframework/data/gemfire/mapping/annotation/LocalRegion.java create mode 100644 src/main/java/org/springframework/data/gemfire/mapping/annotation/PartitionRegion.java create mode 100644 src/main/java/org/springframework/data/gemfire/mapping/annotation/Region.java create mode 100644 src/main/java/org/springframework/data/gemfire/mapping/annotation/ReplicateRegion.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegionsConfigurationUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ClientRegionEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/CollocatedPartitionRegionEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/GenericRegionEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/LocalRegionEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/NonEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ReplicateRegionEntity.java diff --git a/src/main/asciidoc/reference/repositories.adoc b/src/main/asciidoc/reference/repositories.adoc index 0daf77b0..8e40b0a5 100644 --- a/src/main/asciidoc/reference/repositories.adoc +++ b/src/main/asciidoc/reference/repositories.adoc @@ -196,7 +196,7 @@ As an example, suppose you have a `Customers` application domain type and corres package ...; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; ... @Region("Customers") diff --git a/src/main/java/org/springframework/data/gemfire/FixedPartitionAttributesFactoryBean.java b/src/main/java/org/springframework/data/gemfire/FixedPartitionAttributesFactoryBean.java index 7f392f91..0c3b2214 100644 --- a/src/main/java/org/springframework/data/gemfire/FixedPartitionAttributesFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/FixedPartitionAttributesFactoryBean.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire; import org.apache.geode.cache.FixedPartitionAttributes; @@ -21,15 +22,94 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** - * @author David Turanski + * Spring {@link FactoryBean} to create a instance of the {@link FixedPartitionAttributes}. * + * @author David Turanski + * @author John Blum + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.apache.geode.cache.FixedPartitionAttributes */ +@SuppressWarnings("unused") public class FixedPartitionAttributesFactoryBean implements FactoryBean, InitializingBean { + private Boolean primary; - private String partitionName; - private Integer numBuckets; + private FixedPartitionAttributes fixedPartitionAttributes; + private Integer numBuckets; + + private String partitionName; + + /** + * @inheritDoc + */ + @Override + @SuppressWarnings("all") + public void afterPropertiesSet() throws Exception { + Assert.hasText(this.partitionName, "partitionName must be specified"); + + if (this.primary == null && this.numBuckets == null){ + this.fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition(this.partitionName); + } + else if (this.primary == null && this.numBuckets != null){ + this.fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition( + this.partitionName, this.numBuckets); + } + else if (this.primary != null && this.numBuckets == null) { + this.fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition( + this.partitionName, this.primary); + } + else { + this.fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition( + this.partitionName, this.primary, this.numBuckets); + } + + } + + /** + * @inheritDoc + */ + @Override + public FixedPartitionAttributes getObject() throws Exception { + return this.fixedPartitionAttributes; + } + + /** + * @inheritDoc + */ + @Override + public Class getObjectType() { + return (this.fixedPartitionAttributes != null ? this.fixedPartitionAttributes.getClass() + : FixedPartitionAttributes.class); + } + + /** + * @inheritDoc + */ + @Override + public boolean isSingleton() { + return true; + } + + /** + * Set the number of buckets in the Partition Region. + * + * @param numBuckets integer value indicating the number of buckets in the Partition Region. + */ + public void setNumBuckets(Integer numBuckets) { + this.numBuckets = numBuckets; + } + + /** + * Set the name of the partition in the Partition Region. + * + * @param partitionName name of the partition. + */ + public void setPartitionName(String partitionName) { + this.partitionName = partitionName; + } + /** * Sets whether this particular PARTITION Region is the primary (i.e. not secondary). * @@ -38,63 +118,4 @@ public class FixedPartitionAttributesFactoryBean implements FactoryBean getObjectType() { - return FixedPartitionAttributes.class; - } - - /* (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#isSingleton() - */ - @Override - public boolean isSingleton() { - return true; - } - - /* (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.hasText(partitionName,"partitionName cannot be empty or null"); - - fixedPartitionAttributes = null; - if (primary == null && numBuckets == null){ - fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition(partitionName); - } else if (primary == null && numBuckets != null){ - fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition(partitionName, numBuckets); - } else if (primary != null && numBuckets == null) { - fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition(partitionName, primary); - } else { - fixedPartitionAttributes = FixedPartitionAttributes.createFixedPartition(partitionName,primary,numBuckets); - } - - } } diff --git a/src/main/java/org/springframework/data/gemfire/PartitionAttributesFactoryBean.java b/src/main/java/org/springframework/data/gemfire/PartitionAttributesFactoryBean.java index 04a9891d..6776cb74 100644 --- a/src/main/java/org/springframework/data/gemfire/PartitionAttributesFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/PartitionAttributesFactoryBean.java @@ -20,49 +20,84 @@ import java.util.List; import org.apache.geode.cache.FixedPartitionAttributes; import org.apache.geode.cache.PartitionAttributes; +import org.apache.geode.cache.PartitionAttributesFactory; import org.apache.geode.cache.PartitionResolver; import org.apache.geode.cache.partition.PartitionListener; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.gemfire.util.CollectionUtils; /** - * Spring-friendly bean for creating {@link PartitionAttributes}. Eliminates the need of using - * a XML 'factory-method' tag and allows the attributes properties to be set directly. + * Spring {@link FactoryBean} for creating {@link PartitionAttributes}. + * + * Eliminates the need to use a XML 'factory-method' tag and allows the attributes properties to be set directly. * * @author Costin Leau * @author David Turanski * @author John Blum + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.apache.geode.cache.FixedPartitionAttributes + * @see org.apache.geode.cache.PartitionAttributes + * @see org.apache.geode.cache.PartitionAttributesFactory + * @see org.apache.geode.cache.PartitionResolver + * @see org.apache.geode.cache.partition.PartitionListener */ @SuppressWarnings({ "rawtypes", "unchecked", "unused" }) -public class PartitionAttributesFactoryBean implements FactoryBean, InitializingBean { +public class PartitionAttributesFactoryBean implements FactoryBean>, InitializingBean { - private final org.apache.geode.cache.PartitionAttributesFactory partitionAttributesFactory = - new org.apache.geode.cache.PartitionAttributesFactory(); + private List partitionListeners; - private List listeners; + private PartitionAttributes partitionAttributes; + private final PartitionAttributesFactory partitionAttributesFactory = + new PartitionAttributesFactory(); + + /** + * @inheritDoc + */ + @Override + public void afterPropertiesSet() throws Exception { + for (PartitionListener listener : CollectionUtils.nullSafeList(partitionListeners)) { + partitionAttributesFactory.addPartitionListener(listener); + } + + this.partitionAttributes = partitionAttributesFactory.create(); + } + + /** + * @inheritDoc + */ @Override public PartitionAttributes getObject() throws Exception { - return partitionAttributesFactory.create(); + return this.partitionAttributes; } + /** + * @inheritDoc + */ @Override public Class getObjectType() { - return PartitionAttributes.class; + return (this.partitionAttributes != null ? this.partitionAttributes.getClass() : PartitionAttributes.class); } + /** + * @inheritDoc + */ @Override public boolean isSingleton() { return false; } - public void setColocatedWith(String colocatedRegionFullPath) { - partitionAttributesFactory.setColocatedWith(colocatedRegionFullPath); + public void setColocatedWith(String collocatedWith) { + partitionAttributesFactory.setColocatedWith(collocatedWith); } public void setFixedPartitionAttributes(List fixedPartitionAttributes) { - for (FixedPartitionAttributes fpa : fixedPartitionAttributes) { - partitionAttributesFactory.addFixedPartitionAttributes(fpa); + for (FixedPartitionAttributes fixedPartitionAttributesElement : + CollectionUtils.nullSafeList(fixedPartitionAttributes)) { + + partitionAttributesFactory.addFixedPartitionAttributes(fixedPartitionAttributesElement); } } @@ -70,12 +105,12 @@ public class PartitionAttributesFactoryBean implements FactoryBean partitionListeners) { + this.partitionListeners = partitionListeners; } - public void setPartitionListeners(List listeners) { - this.listeners = listeners; + public void setPartitionResolver(PartitionResolver resolver) { + partitionAttributesFactory.setPartitionResolver(resolver); } public void setRecoveryDelay(long recoveryDelay) { @@ -97,15 +132,4 @@ public class PartitionAttributesFactoryBean implements FactoryBean extends RegionLookupFactoryBean cacheWriter; + private Class keyConstraint; + private Class valueConstraint; + private DataPolicy dataPolicy; private EvictionAttributes evictionAttributes; @@ -151,10 +154,18 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean keyConstraint) { + this.keyConstraint = keyConstraint; + } + public void setPersistent(Boolean persistent) { this.persistent = persistent; } @@ -752,6 +767,10 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean valueConstraint) { + this.valueConstraint = valueConstraint; + } + /** * @inheritDoc */ diff --git a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java index fb554047..f731eb99 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java @@ -69,18 +69,17 @@ public abstract class RegionLookupFactoryBean implements FactoryBeangetSubregion(regionName) + : this.cache.getRegion(regionName)); } if (region != null) { log.info(String.format("Found Region [%1$s] in Cache [%2$s]", regionName, cache.getName())); } else { + log.info(String.format("Falling back to creating Region [%1$s] in Cache [%2$s]", + regionName, cache.getName())); + region = lookupRegion(cache, regionName); } } diff --git a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java index b46a2d80..49ffd5f6 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -85,6 +85,9 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean private CacheWriter cacheWriter; + private Class keyConstraint; + private Class valueConstraint; + private ClientRegionShortcut shortcut = null; private DataPolicy dataPolicy; @@ -125,6 +128,14 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean setEvictionAttributes(clientRegionFactory); setPoolName(clientRegionFactory); + if (keyConstraint != null) { + clientRegionFactory.setKeyConstraint(keyConstraint); + } + + if (valueConstraint != null) { + clientRegionFactory.setValueConstraint(valueConstraint); + } + return logCreateRegionEvent(create(clientRegionFactory, regionName)); } @@ -563,6 +574,10 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean return this.interests; } + public void setKeyConstraint(Class keyConstraint) { + this.keyConstraint = keyConstraint; + } + protected boolean isPersistentUnspecified() { return (persistent == null); } @@ -620,4 +635,8 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean public void setSnapshot(Resource snapshot) { this.snapshot = snapshot; } + + public void setValueConstraint(Class valueConstraint) { + this.valueConstraint = valueConstraint; + } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java index 0051bd0e..c74156eb 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java @@ -26,7 +26,7 @@ import java.util.Map; import org.apache.geode.cache.GemFireCache; import org.apache.geode.internal.security.SecurityService; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.mgt.*; +import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.springframework.beans.BeansException; diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java new file mode 100644 index 00000000..e2641e86 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.geode.cache.Region; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AliasFor; + +/** + * The {@link EnableEntityDefinedRegions} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated class to enable the creation of the GemFire/Geode {@link Region Regions} based on + * the application domain model object entities. + * + * @author John Blum + * @see org.springframework.context.annotation.ComponentScan + * @see org.springframework.context.annotation.Import + * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration + * @see org.apache.geode.cache.Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(EntityDefinedRegionsConfiguration.class) +@SuppressWarnings({ "unused" }) +public @interface EnableEntityDefinedRegions { + + /** + * Alias for {@link #basePackages()} attribute. + * + * @return a {@link String} array specifying the packages to search for application persistent entities. + * @see #basePackages() + */ + @AliasFor(attribute = "basePackages") + String[] value() default {}; + + /** + * Base packages to scan for {@link org.springframework.data.gemfire.mapping.annotation.Region @Region} annotated + * application persistent entities. {@link #value()} is an alias for this attribute. + * Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + * + * @return a {@link String} array specifying the packages to search for application persistent entities. + * @see #value() + */ + @AliasFor(attribute = "value") + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for + * {@link org.springframework.data.gemfire.mapping.annotation.Region @Region} annotated application persistent entities. + * The package of each class specified will be scanned. Consider creating a special no-op marker class or interface + * in each package that serves no other purpose than being referenced by this attribute. + * + * @return an array of {@link Class classes} used to determine the packages to scan + * for application persistent entities. + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components + * from everything in {@link #basePackages()} to everything in the base packages that matches the given filter + * or filters. + * + * @return an array {@link org.springframework.context.annotation.ComponentScan.Filter} of Filters used to + * specify application persistent entities to be included during the component scan. + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for component scanning. + * + * @return an array of {@link org.springframework.context.annotation.ComponentScan.Filter Filters} used to + * specify application persistent entities to be excluded during the component scan. + */ + ComponentScan.Filter[] excludeFilters() default {}; + + /** + * Determines whether the created {@link Region} will have strongly-typed key and value constraints + * based on the ID and {@link Class} type of application persistent entity. + * + * Defaults to {@literal false}. + */ + boolean strict() default false; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java new file mode 100644 index 00000000..10706da0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java @@ -0,0 +1,604 @@ +/* + * Copyright 2016 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.apache.geode.internal.lang.ObjectUtils.defaultIfNull; +import static org.springframework.data.gemfire.util.ArrayUtils.defaultIfEmpty; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.springframework.beans.BeanUtils; +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.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AspectJTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.gemfire.FixedPartitionAttributesFactoryBean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.PartitionAttributesFactoryBean; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.RegionAttributesFactoryBean; +import org.springframework.data.gemfire.RegionLookupFactoryBean; +import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; +import org.springframework.data.gemfire.ScopeType; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.GemFireComponentClassTypeScanner; +import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * The EntityDefinedRegionsConfiguration class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class EntityDefinedRegionsConfiguration + implements BeanClassLoaderAware, BeanFactoryAware, ImportBeanDefinitionRegistrar { + + protected static final Class DEFAULT_REGION_FACTORY_BEAN_CLASS = + GemFireCacheTypeAwareRegionFactoryBean.class; + + protected static final Map, Class> regionAnnotationToRegionFactoryBeanClass = + new HashMap<>(); + + static { + regionAnnotationToRegionFactoryBeanClass.put(ClientRegion.class, ClientRegionFactoryBean.class); + regionAnnotationToRegionFactoryBeanClass.put(LocalRegion.class, LocalRegionFactoryBean.class); + regionAnnotationToRegionFactoryBeanClass.put(PartitionRegion.class, PartitionedRegionFactoryBean.class); + regionAnnotationToRegionFactoryBeanClass.put(ReplicateRegion.class, ReplicatedRegionFactoryBean.class); + regionAnnotationToRegionFactoryBeanClass.put(org.springframework.data.gemfire.mapping.annotation.Region.class, + DEFAULT_REGION_FACTORY_BEAN_CLASS); + } + + private BeanFactory beanFactory; + + private ClassLoader beanClassLoader; + + private GemfireMappingContext mappingContext; + + /** + * Returns the {@link Annotation} {@link Class type} that configures and creates {@link Region Regions} + * for application persistent entities. + * + * @return the {@link Annotation} {@link Class type} that configures and creates {@link Region Regions} + * for application persistent entities. + * @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions + * @see java.lang.annotation.Annotation + * @see java.lang.Class + */ + protected Class getAnnotationType() { + return EnableEntityDefinedRegions.class; + } + + /** + * Returns the name of the {@link Annotation} type that configures and creates {@link Region Regions} + * for application persistent entities. + * + * @return the name of the {@link Annotation} type that configures and creates {@link Region Regions} + * for application persistent entities. + * @see java.lang.Class#getName() + * @see #getAnnotationType() + */ + protected String getAnnotationTypeName() { + return getAnnotationType().getName(); + } + + /** + * Returns the simple name of the {@link Annotation} type that configures and creates {@link Region Regions} + * for application persistent entities. + * + * @return the simple name of the {@link Annotation} type that configures and creates {@link Region Regions} + * for application persistent entities. + * @see java.lang.Class#getSimpleName() + * @see #getAnnotationType() + */ + @SuppressWarnings("unused") + protected String getAnnotationTypeSimpleName() { + return getAnnotationType().getSimpleName(); + } + + /** + * @inheritDoc + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + /* (non-Javadoc) */ + protected ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + /** + * @inheritDoc + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /* (non-Javadoc) */ + protected BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /* (non-Javadoc) */ + protected boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata) { + return isAnnotationPresent(importingClassMetadata, getAnnotationTypeName()); + } + + /* (non-Javadoc) */ + protected boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata, String annotationName) { + return importingClassMetadata.hasAnnotation(annotationName); + } + + /* (non-Javadoc) */ + protected AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata) { + return getAnnotationAttributes(importingClassMetadata, getAnnotationTypeName()); + } + + /* (non-Javadoc) */ + protected AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata, + String annotationName) { + + return AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(annotationName)); + } + + /* (non-Javadoc) */ + protected GemfirePersistentEntity getPersistentEntity(Class persistentEntityType) { + return resolveMappingContext().getPersistentEntity(persistentEntityType); + } + + /* (non-Javadoc) */ + protected GemfireMappingContext resolveMappingContext() { + if (this.mappingContext == null) { + try { + this.mappingContext = getBeanFactory().getBean(GemfireMappingContext.class); + } + catch (Throwable ignore) { + this.mappingContext = new GemfireMappingContext(); + } + } + + return this.mappingContext; + } + + /** + * @inheritDoc + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (isAnnotationPresent(importingClassMetadata)) { + AnnotationAttributes enableEntityDefinedRegionsAttributes = getAnnotationAttributes(importingClassMetadata); + + boolean strict = enableEntityDefinedRegionsAttributes.getBoolean("strict"); + + for (Class persistentEntityClass : newGemFireComponentClassTypeScanner( + importingClassMetadata, enableEntityDefinedRegionsAttributes).scan()) { + + GemfirePersistentEntity persistentEntity = getPersistentEntity(persistentEntityClass); + + registerRegionBeanDefinition(persistentEntity, strict, registry); + postProcess(persistentEntity); + } + } + } + + /* (non-Javadoc) */ + protected GemFireComponentClassTypeScanner newGemFireComponentClassTypeScanner( + AnnotationMetadata importingClassMetadata, AnnotationAttributes enableEntityDefinedRegionsAttributes) { + + Set resolvedBasePackages = resolveBasePackages(importingClassMetadata, + enableEntityDefinedRegionsAttributes); + + return GemFireComponentClassTypeScanner.from(resolvedBasePackages).with(resolveBeanClassLoader()) + .withExcludes(resolveExcludes(enableEntityDefinedRegionsAttributes)) + .withIncludes(resolveIncludes(enableEntityDefinedRegionsAttributes)) + .withIncludes(regionAnnotatedPersistentEntityTypeFilters()); + } + + /* (non-Javadoc) */ + protected Set resolveBasePackages(AnnotationMetadata importingClassMetaData, + AnnotationAttributes enableEntityDefinedRegionAttributes) { + + Set resolvedBasePackages = new HashSet<>(); + + Collections.addAll(resolvedBasePackages, ArrayUtils.nullSafeArray(defaultIfEmpty( + enableEntityDefinedRegionAttributes.getStringArray("basePackages"), + enableEntityDefinedRegionAttributes.getStringArray("value")), String.class)); + + for (Class type : ArrayUtils.nullSafeArray( + enableEntityDefinedRegionAttributes.getClassArray("basePackageClasses"), Class.class)) { + + resolvedBasePackages.add(type.getPackage().getName()); + } + + if (resolvedBasePackages.isEmpty()) { + resolvedBasePackages.add(ClassUtils.getPackageName(importingClassMetaData.getClassName())); + } + + return resolvedBasePackages; + } + + /* (non-Javadoc) */ + protected ClassLoader resolveBeanClassLoader() { + return (this.beanClassLoader != null ? this.beanClassLoader : Thread.currentThread().getContextClassLoader()); + } + + /* (non-Javadoc) */ + protected Iterable resolveExcludes(AnnotationAttributes enableEntityDefinedRegionsAttributes) { + return parseFilters(enableEntityDefinedRegionsAttributes.getAnnotationArray("excludeFilters")); + } + + /* (non-Javadoc) */ + protected Iterable resolveIncludes(AnnotationAttributes enableEntityDefinedRegionsAttributes) { + return parseFilters(enableEntityDefinedRegionsAttributes.getAnnotationArray("includeFilters")); + } + + /* (non-Javadoc) */ + private Iterable parseFilters(AnnotationAttributes[] componentScanFilterAttributes) { + Set typeFilters = new HashSet<>(); + + for (AnnotationAttributes filterAttributes : ArrayUtils.nullSafeArray( + componentScanFilterAttributes, AnnotationAttributes.class)) { + + CollectionUtils.addAll(typeFilters, typeFiltersFor(filterAttributes)); + } + + return typeFilters; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + private Iterable typeFiltersFor(AnnotationAttributes filterAttributes) { + Set typeFilters = new HashSet<>(); + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : ArrayUtils.nullSafeArray(filterAttributes.getClassArray("value"), Class.class)) { + switch (filterType) { + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, + String.format("@ComponentScan.Filter class [%s] must be an Annotation", filterClass)); + typeFilters.add(new AnnotationTypeFilter((Class) filterClass)); + break; + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, + String.format("@ComponentScan.Filter class [%s] must be a TypeFilter", filterClass)); + typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); + break; + default: + throw new IllegalArgumentException(String.format( + "Illegal filter type [%s] when 'value' or 'classes' are specified", filterType)); + } + + for (String pattern : nullSafeGetPatterns(filterAttributes)) { + switch (filterType) { + case ASPECTJ: + typeFilters.add(new AspectJTypeFilter(pattern, resolveBeanClassLoader())); + break; + case REGEX: + typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(pattern))); + break; + default: + throw new IllegalArgumentException(String.format( + "Illegal filter type [%s] when 'patterns' are specified", filterType)); + } + } + } + + return typeFilters; + } + + /** + * Safely reads the {@code pattern} attribute from the given {@link AnnotationAttributes} + * and returns an empty array if the attribute is not present. + * + * @param filterAttributes {@link AnnotationAttributes} from which to extract the {@code pattern} attribute value. + * @return a {@link String} array. + */ + private String[] nullSafeGetPatterns(AnnotationAttributes filterAttributes) { + try { + return ArrayUtils.nullSafeArray(filterAttributes.getStringArray("pattern"), String.class); + } + catch (IllegalArgumentException ignore) { + return new String[0]; + } + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected Iterable regionAnnotatedPersistentEntityTypeFilters() { + Set regionAnnotatedPersistentEntityTypeFilters = new HashSet<>(); + + for (Class annotationType : + org.springframework.data.gemfire.mapping.annotation.Region.REGION_ANNOTATION_TYPES) { + + regionAnnotatedPersistentEntityTypeFilters.add(new AnnotationTypeFilter(annotationType)); + } + + return regionAnnotatedPersistentEntityTypeFilters; + } + + /* (non-Javadoc) */ + protected void registerRegionBeanDefinition(GemfirePersistentEntity persistentEntity, boolean strict, + BeanDefinitionRegistry registry) { + + BeanDefinitionBuilder regionFactoryBeanBuilder = + BeanDefinitionBuilder.genericBeanDefinition(resolveRegionFactoryBeanClass(persistentEntity)) + .addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME) + .addPropertyValue("close", false); + + setRegionAttributes(persistentEntity, regionFactoryBeanBuilder, strict); + + registry.registerBeanDefinition(persistentEntity.getRegionName(), + regionFactoryBeanBuilder.getBeanDefinition()); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected Class resolveRegionFactoryBeanClass( + GemfirePersistentEntity persistentEntity) { + + return defaultIfNull(regionAnnotationToRegionFactoryBeanClass.get(persistentEntity.getRegionAnnotationType()), + DEFAULT_REGION_FACTORY_BEAN_CLASS); + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder setRegionAttributes(GemfirePersistentEntity persistentEntity, + BeanDefinitionBuilder regionFactoryBeanBuilder, boolean strict) { + + Annotation regionAnnotation = persistentEntity.getRegionAnnotation(); + + if (regionAnnotation != null) { + AnnotationAttributes regionAnnotationAttributes = + AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(regionAnnotation)); + + if (strict) { + regionFactoryBeanBuilder.addPropertyValue("keyConstraint", resolveIdType(persistentEntity)); + regionFactoryBeanBuilder.addPropertyValue("valueConstraint", resolveDomainType(persistentEntity)); + } + + if (regionAnnotationAttributes.containsKey("diskStoreName")) { + String diskStoreName = regionAnnotationAttributes.getString("diskStoreName"); + + setPropertyValueIfNotDefault(regionFactoryBeanBuilder, "diskStoreName", diskStoreName, ""); + + if (StringUtils.hasText(diskStoreName)) { + regionFactoryBeanBuilder.addDependsOn(diskStoreName); + } + } + + if (regionAnnotationAttributes.containsKey("persistent")) { + setPropertyValueIfNotDefault(regionFactoryBeanBuilder, "persistent", + regionAnnotationAttributes.getBoolean("persistent"), false); + } + + BeanDefinitionBuilder regionAttributesFactoryBeanBuilder = + resolveRegionAttributesFactoryBeanBuilder(regionAnnotation, regionFactoryBeanBuilder); + + if (regionAnnotationAttributes.containsKey("diskSynchronous")) { + setPropertyValueIfNotDefault(regionAttributesFactoryBeanBuilder, "diskSynchronous", + regionAnnotationAttributes.getBoolean("diskSynchronous"), true); + } + + if (regionAnnotationAttributes.containsKey("ignoreJta")) { + setPropertyValueIfNotDefault(regionAttributesFactoryBeanBuilder, "ignoreJTA", + regionAnnotationAttributes.getBoolean("ignoreJta"), false); + } + + setClientRegionAttributes(regionAnnotationAttributes, regionFactoryBeanBuilder); + + setPartitionRegionAttributes(regionAnnotationAttributes, regionFactoryBeanBuilder, + regionAttributesFactoryBeanBuilder); + + setReplicateRegionAttributes(regionAnnotationAttributes, regionFactoryBeanBuilder); + + } + + return regionFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + protected Class resolveDomainType(GemfirePersistentEntity persistentEntity) { + return defaultIfNull(persistentEntity.getType(), Object.class); + } + + /* (non-Javadoc) */ + protected Class resolveIdType(GemfirePersistentEntity persistentEntity) { + return (persistentEntity.hasIdProperty() ? + defaultIfNull(persistentEntity.getIdProperty().getActualType(), Object.class) : Object.class); + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder resolveRegionAttributesFactoryBeanBuilder(Annotation regionAnnotation, + BeanDefinitionBuilder regionFactoryBeanBuilder) { + + BeanDefinitionBuilder regionAttributesFactoryBeanBuilder = regionFactoryBeanBuilder; + + if (!ClientRegion.class.isAssignableFrom(regionAnnotation.annotationType())) { + regionAttributesFactoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition( + RegionAttributesFactoryBean.class); + + regionFactoryBeanBuilder.addPropertyValue("attributes", + regionAttributesFactoryBeanBuilder.getBeanDefinition()); + } + + return regionAttributesFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder setClientRegionAttributes(AnnotationAttributes regionAnnotationAttributes, + BeanDefinitionBuilder regionFactoryBeanBuilder) { + + if (regionAnnotationAttributes.containsKey("poolName")) { + setPropertyValueIfNotDefault(regionFactoryBeanBuilder, "poolName", + regionAnnotationAttributes.getString("poolName"), null); + } + + if (regionAnnotationAttributes.containsKey("shortcut")) { + setPropertyValueIfNotDefault(regionFactoryBeanBuilder, "shortcut", + regionAnnotationAttributes.getEnum("shortcut"), ClientRegionShortcut.PROXY); + } + + return regionFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder setPartitionRegionAttributes(AnnotationAttributes regionAnnotationAttributes, + BeanDefinitionBuilder regionFactoryBeanBuilder, BeanDefinitionBuilder regionAttributesFactoryBeanBuilder) { + + if (regionAnnotationAttributes.containsKey("redundantCopies")) { + BeanDefinitionBuilder partitionAttributesFactoryBeanBuilder = + BeanDefinitionBuilder.genericBeanDefinition(PartitionAttributesFactoryBean.class); + + String collocatedWith = regionAnnotationAttributes.getString("collocatedWith"); + + setPropertyValueIfNotDefault(partitionAttributesFactoryBeanBuilder, "colocatedWith", collocatedWith, ""); + + if (StringUtils.hasText(collocatedWith)) { + regionFactoryBeanBuilder.addDependsOn(collocatedWith); + } + + setPropertyReferenceIfSet(partitionAttributesFactoryBeanBuilder, "partitionResolver", + regionAnnotationAttributes.getString("partitionResolverName")); + + setPropertyValueIfNotDefault(partitionAttributesFactoryBeanBuilder, "redundantCopies", + regionAnnotationAttributes.getNumber("redundantCopies"), 0); + + setFixedPartitionRegionAttributes(regionAnnotationAttributes, partitionAttributesFactoryBeanBuilder); + + regionAttributesFactoryBeanBuilder.addPropertyValue("partitionAttributes", + partitionAttributesFactoryBeanBuilder.getBeanDefinition()); + } + + return regionAttributesFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder setFixedPartitionRegionAttributes(AnnotationAttributes regionAnnotationAttributes, + BeanDefinitionBuilder partitionAttributesFactoryBeanBuilder) { + + PartitionRegion.FixedPartition[] fixedPartitions = ArrayUtils.nullSafeArray( + regionAnnotationAttributes.getAnnotationArray("fixedPartitions", PartitionRegion.FixedPartition.class), + PartitionRegion.FixedPartition.class); + + if (!ObjectUtils.isEmpty(fixedPartitions)) { + ManagedList fixedPartitionAttributesFactoryBeans = + new ManagedList<>(fixedPartitions.length); + + for (PartitionRegion.FixedPartition fixedPartition : fixedPartitions) { + BeanDefinitionBuilder fixedPartitionAttributesFactoryBeanBuilder = + BeanDefinitionBuilder.genericBeanDefinition(FixedPartitionAttributesFactoryBean.class); + + fixedPartitionAttributesFactoryBeanBuilder.addPropertyValue("partitionName", fixedPartition.name()); + + setPropertyValueIfNotDefault(fixedPartitionAttributesFactoryBeanBuilder, "primary", + fixedPartition.primary(), false); + + setPropertyValueIfNotDefault(fixedPartitionAttributesFactoryBeanBuilder, "numBuckets", + fixedPartition.numBuckets(), 1); + + fixedPartitionAttributesFactoryBeans.add( + fixedPartitionAttributesFactoryBeanBuilder.getBeanDefinition()); + } + + partitionAttributesFactoryBeanBuilder.addPropertyValue("fixedPartitionAttributes", + fixedPartitionAttributesFactoryBeans); + } + + return partitionAttributesFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + protected BeanDefinitionBuilder setReplicateRegionAttributes(AnnotationAttributes regionAnnotationAttributes, + BeanDefinitionBuilder regionFactoryBeanBuilder) { + + if (regionAnnotationAttributes.containsKey("scope")) { + setPropertyValueIfNotDefault(regionFactoryBeanBuilder, "scope", + regionAnnotationAttributes.getEnum("scope").getScope(), ScopeType.DISTRIBUTED_NO_ACK); + } + + return regionFactoryBeanBuilder; + } + + /* (non-Javadoc) */ + private BeanDefinitionBuilder setPropertyReferenceIfSet(BeanDefinitionBuilder beanDefinitionBuilder, + String propertyName, String beanName) { + + return (StringUtils.hasText(beanName) ? beanDefinitionBuilder.addPropertyReference(propertyName, beanName) + : beanDefinitionBuilder); + } + + /* (non-Javadoc) */ + private BeanDefinitionBuilder setPropertyValueIfNotDefault(BeanDefinitionBuilder beanDefinitionBuilder, + String propertyName, T value, T defaultValue) { + + return (value != null && !value.equals(defaultValue) ? + beanDefinitionBuilder.addPropertyValue(propertyName, value) : beanDefinitionBuilder); + } + + /** + * Performs addition post processing on the {@link GemfirePersistentEntity} to offer additional feature support + * (e.g. dynamic Index creation). + * + * @param persistentEntity {@link GemfirePersistentEntity} to process. + * @return the given {@link GemfirePersistentEntity}. + * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity + */ + protected GemfirePersistentEntity postProcess(GemfirePersistentEntity persistentEntity) { + return persistentEntity; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java new file mode 100644 index 00000000..1e0ee6a7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java @@ -0,0 +1,224 @@ +/* + * Copyright 2016 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.support; + +import static org.springframework.data.gemfire.util.SpringUtils.defaultIfEmpty; +import static org.springframework.data.gemfire.util.SpringUtils.defaultIfNull; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.data.gemfire.GenericRegionFactoryBean; +import org.springframework.data.gemfire.RegionLookupFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.util.Assert; + +/** + * The {@link GemFireCacheTypeAwareRegionFactoryBean} class is a smart Spring {@link FactoryBean} that knows how to + * create a client or server {@link Region} depending on whether the {@link GemFireCache} is + * a {@link org.apache.geode.cache.client.ClientCache} or a peer {@link org.apache.geode.cache.Cache}. + * + * @author John Blum + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.data.gemfire.RegionLookupFactoryBean + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @since 1.9.0 + */ +@SuppressWarnings("unused") +public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBean + implements BeanFactoryAware { + + private GemFireCache gemfireCache; + + private BeanFactory beanFactory; + + private Boolean close = false; + + private Class keyConstraint; + private Class valueConstraint; + + private ClientRegionShortcut clientRegionShortcut = ClientRegionShortcut.PROXY; + + private DataPolicy dataPolicy = DataPolicy.DEFAULT; + + private RegionAttributes regionAttributes; + + private RegionShortcut serverRegionShortcut; + + private String poolName; + private String regionName; + + /** + * @inheritDoc + */ + @Override + public Region lookupRegion(GemFireCache gemfireCache, String regionName) throws Exception { + return (GemfireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache, regionName) + : newServerRegion(gemfireCache, regionName)); + } + + /** + * Constructs a new client {@link Region} using the {@link ClientRegionFactoryBean}. + * + * @param gemfireCache reference to the {@link GemFireCache} used to create/initialize the factory + * used to create the client {@link Region}. + * @param regionName name given to the client {@link Region}. + * @return a new instance of a client {@link Region} with the given {@code regionName}. + * @throws Exception if the client {@link Region} could not be created. + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + */ + protected Region newClientRegion(GemFireCache gemfireCache, String regionName) throws Exception { + ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean<>(); + + clientRegion.setAttributes(getRegionAttributes()); + clientRegion.setBeanFactory(getBeanFactory()); + clientRegion.setCache(gemfireCache); + clientRegion.setClose(isClose()); + clientRegion.setKeyConstraint(getKeyConstraint()); + clientRegion.setPoolName(getPoolName()); + clientRegion.setRegionName(regionName); + clientRegion.setShortcut(getClientRegionShortcut()); + clientRegion.setValueConstraint(getValueConstraint()); + clientRegion.afterPropertiesSet(); + + return clientRegion.getObject(); + } + + /** + * Constructs a new server {@link Region} using the {@link GenericRegionFactoryBean}. + * + * @param gemfireCache reference to the {@link GemFireCache} used to create/initialize the factory + * used to create the server {@link Region}. + * @param regionName name given to the server {@link Region}. + * @return a new instance of a server {@link Region} with the given {@code regionName}. + * @throws Exception if the server {@link Region} could not be created. + * @see org.springframework.data.gemfire.GenericRegionFactoryBean + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + */ + protected Region newServerRegion(GemFireCache gemfireCache, String regionName) throws Exception { + GenericRegionFactoryBean serverRegion = new GenericRegionFactoryBean<>(); + + serverRegion.setAttributes(getRegionAttributes()); + serverRegion.setCache(gemfireCache); + serverRegion.setClose(isClose()); + serverRegion.setDataPolicy(getDataPolicy()); + serverRegion.setKeyConstraint(getKeyConstraint()); + serverRegion.setRegionName(regionName); + serverRegion.setShortcut(getServerRegionShortcut()); + serverRegion.setValueConstraint(getValueConstraint()); + serverRegion.afterPropertiesSet(); + + return serverRegion.getObject(); + } + + public void setAttributes(RegionAttributes regionAttributes) { + this.regionAttributes = regionAttributes; + } + + protected RegionAttributes getRegionAttributes() { + return this.regionAttributes; + } + + /** + * @inheritDoc + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + protected BeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); + return this.beanFactory; + } + + public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) { + this.clientRegionShortcut = clientRegionShortcut; + } + + protected ClientRegionShortcut getClientRegionShortcut() { + return defaultIfNull(this.clientRegionShortcut, ClientRegionShortcut.PROXY); + } + + public void setClose(Boolean close) { + this.close = close; + } + + protected Boolean getClose() { + return this.close; + } + + protected boolean isClose() { + return Boolean.TRUE.equals(getClose()); + } + + public void setDataPolicy(DataPolicy dataPolicy) { + this.dataPolicy = dataPolicy; + } + + protected DataPolicy getDataPolicy() { + return defaultIfNull(this.dataPolicy, DataPolicy.DEFAULT); + } + + public void setKeyConstraint(Class keyConstraint) { + this.keyConstraint = keyConstraint; + } + + protected Class getKeyConstraint() { + return this.keyConstraint; + } + + public void setPoolName(String poolName) { + this.poolName = poolName; + } + + protected String getPoolName() { + return defaultIfEmpty(this.poolName, GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + } + + public void setServerRegionShortcut(RegionShortcut shortcut) { + this.serverRegionShortcut = shortcut; + } + + protected RegionShortcut getServerRegionShortcut() { + return this.serverRegionShortcut; + } + + public void setValueConstraint(Class valueConstraint) { + this.valueConstraint = valueConstraint; + } + + protected Class getValueConstraint() { + return this.valueConstraint; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireComponentClassTypeScanner.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireComponentClassTypeScanner.java new file mode 100644 index 00000000..91753d0c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireComponentClassTypeScanner.java @@ -0,0 +1,298 @@ +/* + * Copyright 2016 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.support; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * The {@link GemFireComponentClassTypeScanner} class is a classpath component scanner used to search + * for GemFire components based on {@link Class} type. + * + * @author John Blum + * @see java.lang.Iterable + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider + * @see org.springframework.core.env.Environment + * @since 1.9.0 + */ +@SuppressWarnings("unused") +public class GemFireComponentClassTypeScanner implements Iterable { + + /** + * Factory method to construct an instance of the {@link GemFireComponentClassTypeScanner} initialized with + * the given array of base packages to scan. + * + * @param basePackages array of base packages to scan for GemFire components. + * @throws IllegalArgumentException if the array of base packages is {@literal null} or empty. + * @return an initialized instance of {@link GemFireComponentClassTypeScanner}. + * @see #GemFireComponentClassTypeScanner(Set) + */ + public static GemFireComponentClassTypeScanner from(String... basePackages) { + return new GemFireComponentClassTypeScanner(CollectionUtils.asSet( + ArrayUtils.nullSafeArray(basePackages, String.class))); + } + + /** + * Factory method to construct an instance of the {@link GemFireComponentClassTypeScanner} initialized with + * the given {@link Iterable} of base packages to scan. + * + * @param basePackages {@link Iterable} of base packages to scan for GemFire components. + * @throws IllegalArgumentException if the {@link Iterable} of base packages is {@literal null} or empty. + * @return an initialized instance of {@link GemFireComponentClassTypeScanner}. + * @see #GemFireComponentClassTypeScanner(Set) + */ + public static GemFireComponentClassTypeScanner from(Iterable basePackages) { + Set basePackageSet = new HashSet(); + + for (String basePackage : CollectionUtils.nullSafeIterable(basePackages)) { + basePackageSet.add(basePackage); + } + + return new GemFireComponentClassTypeScanner(basePackageSet); + } + + private ClassLoader entityClassLoader; + + private ConfigurableApplicationContext applicationContext; + + private Set excludes = new HashSet(); + private Set includes = new HashSet(); + + protected final Log log = LogFactory.getLog(getClass()); + + private final Set basePackages; + + /** + * Constructs an instance of the {@link GemFireComponentClassTypeScanner} initialized with + * the given {@link Set} of base packages to scan. + * + * @param basePackages {@link Set} of base packages to scan for GemFire component clases. + * @throws IllegalArgumentException if the {@link Set} is {@literal null} or empty. + * @see java.util.Set + */ + protected GemFireComponentClassTypeScanner(Set basePackages) { + Assert.notEmpty(basePackages, "Base packages must be specified"); + this.basePackages = basePackages; + } + + /** + * Returns a reference to the Spring {@link org.springframework.context.ApplicationContext}. + * + * @return a reference to the Spring {@link org.springframework.context.ApplicationContext}. + * @see org.springframework.context.ConfigurableApplicationContext + */ + protected ConfigurableApplicationContext getApplicationContext() { + return this.applicationContext; + } + + /** + * Returns an unmodifiable {@link Set} of base packages to scan for GemFire components. + * + * @return an unmodifiable {@link Set} of base packages to scan for GemFire components. + */ + protected Set getBasePackages() { + return Collections.unmodifiableSet(this.basePackages); + } + + /** + * Returns a reference to the {@link ClassLoader} to find and load GemFire application persistent entity classes. + * + * @return a {@link ClassLoader} to find and load GemFire application persistent entity classes. + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader() + * @see java.lang.Thread#getContextClassLoader() + * @see java.lang.ClassLoader + * @see #getApplicationContext() + */ + protected ClassLoader getEntityClassLoader() { + ConfigurableApplicationContext applicationContext = getApplicationContext(); + + return (this.entityClassLoader != null ? this.entityClassLoader + : (applicationContext != null ? applicationContext.getBeanFactory().getBeanClassLoader() + : Thread.currentThread().getContextClassLoader())); + } + + /** + * Returns a reference to the Spring {@link Environment} in which the Spring GemFire application is running. + * + * @return a reference to the Spring {@link Environment}. + * @see org.springframework.context.ApplicationContext#getEnvironment() + * @see org.springframework.core.env.Environment + * @see org.springframework.core.env.StandardEnvironment + * @see #getApplicationContext() + */ + protected Environment getEnvironment() { + ConfigurableApplicationContext applicationContext = getApplicationContext(); + return (applicationContext != null ? applicationContext.getEnvironment() : new StandardEnvironment()); + } + + /** + * Returns a collection of {@link TypeFilter TypeFilters} used to exclude types found + * during the classpath component scan. + * + * @return a collection of {@link TypeFilter} objects + * @see org.springframework.core.type.filter.TypeFilter + */ + protected Iterable getExcludes() { + return this.excludes; + } + + /** + * Returns a collection of {@link TypeFilter TypeFilters} used to include (match) types found + * during the classpath component scan. + * + * @return a collection of {@link TypeFilter} objects + * @see org.springframework.core.type.filter.TypeFilter + */ + protected Iterable getIncludes() { + return this.includes; + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return getBasePackages().iterator(); + } + + /** + * Scans the {@link Set} of base packages searching for GemFire application components accepted by the filters + * of this scanner. + * + * @return a {@link Set} of GemFire application component {@link Class} types. + * @see #newClassPathScanningCandidateComponentProvider(boolean) + * @see java.util.Set + */ + public Set> scan() { + Set> componentClasses = new HashSet>(); + + ClassLoader entityClassLoader = getEntityClassLoader(); + + ClassPathScanningCandidateComponentProvider componentProvider = + newClassPathScanningCandidateComponentProvider(); + + for (String packageName : this) { + for (BeanDefinition beanDefinition : componentProvider.findCandidateComponents(packageName)) { + try { + componentClasses.add(ClassUtils.forName(beanDefinition.getBeanClassName(), entityClassLoader)); + } + catch (ClassNotFoundException ignore) { + log.warn(String.format("Class not found for component type [%s]", + beanDefinition.getBeanClassName())); + } + } + } + + return componentClasses; + } + + /** + * Constructs a new instance of the {@link ClassPathScanningCandidateComponentProvider} initialized with + * no default filters. + * + * @return a new instance of the {@link ClassPathScanningCandidateComponentProvider}. + * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider + * @see #newClassPathScanningCandidateComponentProvider(boolean) + */ + protected ClassPathScanningCandidateComponentProvider newClassPathScanningCandidateComponentProvider() { + return newClassPathScanningCandidateComponentProvider(false); + } + + /** + * Constructs a new instance of the {@link ClassPathScanningCandidateComponentProvider} initialized with + * the {@code useDefaultFilters} boolean value to indicate whether to use default values or not. Additionally, + * the exclude/include filters are also set. + * + * @param useDefaultFilters boolean value to indicate whether to use default filters. + * @return a new instance of the {@link ClassPathScanningCandidateComponentProvider}. + * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider + * @see #newClassPathScanningCandidateComponentProvider(boolean) + */ + protected ClassPathScanningCandidateComponentProvider newClassPathScanningCandidateComponentProvider( + boolean useDefaultFilters) { + + ClassPathScanningCandidateComponentProvider componentProvider = + new ClassPathScanningCandidateComponentProvider(useDefaultFilters, getEnvironment()); + + for (TypeFilter exclude : excludes) { + componentProvider.addExcludeFilter(exclude); + } + + for (TypeFilter include : includes) { + componentProvider.addIncludeFilter(include); + } + + return componentProvider; + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner with(ClassLoader entityClassLoader) { + this.entityClassLoader = entityClassLoader; + return this; + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner with(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + return this; + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner withExcludes(TypeFilter... excludes) { + return withExcludes(CollectionUtils.asSet(ArrayUtils.nullSafeArray(excludes, TypeFilter.class))); + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner withExcludes(Iterable excludes) { + for (TypeFilter exclude : CollectionUtils.nullSafeIterable(excludes)) { + this.excludes.add(exclude); + } + + return this; + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner withIncludes(TypeFilter... includes) { + return withIncludes(CollectionUtils.asSet(ArrayUtils.nullSafeArray(includes, TypeFilter.class))); + } + + /* (non-Javadoc) */ + public GemFireComponentClassTypeScanner withIncludes(Iterable includes) { + for (TypeFilter include : CollectionUtils.nullSafeIterable(includes)) { + this.includes.add(include); + } + + return this; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java index 2012cac1..ac881f4e 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java @@ -13,43 +13,117 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.mapping; +import static org.springframework.data.gemfire.util.SpringUtils.defaultIfEmpty; + +import java.lang.annotation.Annotation; + +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; +import org.springframework.data.gemfire.mapping.annotation.Region; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; -import org.springframework.util.StringUtils; /** - * {@link PersistentEntity} implementation adding custom Gemfire related metadata, such as the region the entity is - * mapped to etc. - * + * {@link PersistentEntity} implementation adding custom GemFire persistent entity related metadata, such as the + * {@link org.apache.geode.cache.Region} to which the entity is mapped, etc. + * * @author Oliver Gierke + * @author John Blum + * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty + * @see org.springframework.data.mapping.model.BasicPersistentEntity */ +@SuppressWarnings("unused") public class GemfirePersistentEntity extends BasicPersistentEntity { + private final Annotation regionAnnotation; + private final String regionName; - /** - * Creates a new {@link GemfirePersistentEntity} for the given {@link TypeInformation}. - * - * @param information must not be {@literal null}. - */ - public GemfirePersistentEntity(TypeInformation information) { + /* (non-Javadoc) */ + protected static Annotation resolveRegionAnnotation(Class persistentEntityType) { - super(information); + for (Class regionAnnotationType : Region.REGION_ANNOTATION_TYPES) { + Annotation regionAnnotation = AnnotatedElementUtils.getMergedAnnotation( + persistentEntityType, regionAnnotationType); - Class rawType = information.getType(); - Region region = rawType.getAnnotation(Region.class); - String fallbackName = rawType.getSimpleName(); + if (regionAnnotation != null) { + return regionAnnotation; + } + } - this.regionName = region == null || !StringUtils.hasText(region.value()) ? fallbackName : region.value(); + return null; + } + + /* (non-Javadoc) */ + protected static String resolveRegionName(Class persistentEntityType, Annotation regionAnnotation) { + + String regionName = (regionAnnotation != null ? AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(regionAnnotation)).getString("value") : null); + + return defaultIfEmpty(regionName, persistentEntityType.getSimpleName()); } /** - * Returns the name of the region the entity shall be stored in. - * - * @return the name of the region the entity shall be stored in. + * Creates a new {@link GemfirePersistentEntity} for the given {@link TypeInformation}. + * + * @param information must not be {@literal null}. + */ + public GemfirePersistentEntity(TypeInformation information) { + super(information); + + Class rawType = information.getType(); + + this.regionAnnotation = resolveRegionAnnotation(rawType); + this.regionName = resolveRegionName(rawType, this.regionAnnotation); + } + + /** + * Returns the {@link Region} annotation used to annotate this {@link PersistentEntity} or {@literal null} + * if this {@link PersistentEntity} was not annotated with a {@link Region} annotation. + * + * @param concrete {@link Class} type of the Region {@link Annotation}. + * @return the {@link Region} annotation used to annotate this {@link PersistentEntity} or {@literal null} + * if this {@link PersistentEntity} was not annotated with a {@link Region} annotation. + * @see ClientRegion + * @see LocalRegion + * @see PartitionRegion + * @see ReplicateRegion + * @see Region + * @see java.lang.annotation.Annotation + */ + @SuppressWarnings("unchecked") + public T getRegionAnnotation() { + return (T) this.regionAnnotation; + } + + /** + * Returns the {@link Class} type of the Region {@link Annotation} or {@literal null} + * if this {@link PersistentEntity} was not annotated with a Region {@link Annotation}. + * + * @return the {@link Class} type of the Region {@link Annotation} or {@literal null} + * if this {@link PersistentEntity} was not annotated with a Region {@link Annotation}. + * @see java.lang.annotation.Annotation#annotationType() + * @see #getRegionAnnotation() + */ + public Class getRegionAnnotationType() { + Annotation regionAnnotation = getRegionAnnotation(); + return (regionAnnotation != null ? regionAnnotation.annotationType() : null); + } + + /** + * Returns the name of the {@link Region} in which this {@link PersistentEntity} will be stored. + * + * @return the name of the {@link Region} in which this {@link PersistentEntity} will be stored. + * @see org.apache.geode.cache.Region#getName() */ public String getRegionName() { return this.regionName; diff --git a/src/main/java/org/springframework/data/gemfire/mapping/Region.java b/src/main/java/org/springframework/data/gemfire/mapping/Region.java deleted file mode 100644 index fd40881a..00000000 --- a/src/main/java/org/springframework/data/gemfire/mapping/Region.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012 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.mapping; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to define the region an entity will be stored in. - * - * @author Oliver Gierke - */ -@Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -public @interface Region { - - /** - * The name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} the entity - * shall be stored in (e.g. "Users", or "/Local/Admin/Users"). - * - * @return the name or qualified path of the Region the entity shall be persisted in. - */ - String value() default ""; - -} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/ClientRegion.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/ClientRegion.java new file mode 100644 index 00000000..fd9672a9 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/ClientRegion.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 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.mapping.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; +import org.springframework.core.annotation.AliasFor; +import org.springframework.data.gemfire.config.xml.GemfireConstants; + +/** + * {@link Annotation} defining the Client {@link Region} in which the application persistent entity will be stored. + * + * @author John Blum + * @see Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Region +@SuppressWarnings("unused") +public @interface ClientRegion { + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "name") + String name() default ""; + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "value") + String value() default ""; + + /** + * Name of the {@link org.apache.geode.cache.DiskStore} in which this persistent entity's data is overflowed + * and/or persisted. + * + * Maybe the name of a Spring bean defined in the Spring context. + * + * Defaults to unset. + */ + String diskStoreName() default ""; + + /** + * Determines whether disk-based operations (used in overflow and persistent) are synchronous or asynchronous. + * + * Defaults to {@literal synchronous}. + */ + boolean diskSynchronous() default true; + + /** + * Name of the GemFire/Geode {@link Pool} used by this persistent entity's {@link org.apache.geode.cache.Region} + * data access operations sent to the corresponding {@link org.apache.geode.cache.Region} + * on the GemFire/Geode Server. + * + * Defaults to {@literal gemfirePool}. + */ + String poolName() default GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME; + + /** + * {@link ClientRegionShortcut} used by this persistent entity's client {@link org.apache.geode.cache.Region} + * to define the {@link org.apache.geode.cache.DataPolicy}. + * + * Defaults to {@link ClientRegionShortcut#PROXY}. + * + * @see org.apache.geode.cache.client.ClientRegionShortcut + */ + ClientRegionShortcut shortcut() default ClientRegionShortcut.PROXY; + +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/LocalRegion.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/LocalRegion.java new file mode 100644 index 00000000..512f31b0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/LocalRegion.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 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.mapping.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * {@link Annotation} defining the Local {@link Region} in which the application persistent entity will be stored. + * + * @author John Blum + * @see Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Region +@SuppressWarnings("unused") +public @interface LocalRegion { + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "name") + String name() default ""; + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "value") + String value() default ""; + + /** + * Name of the {@link org.apache.geode.cache.DiskStore} in which this persistent entity's data is overflowed + * and/or persisted. + * + * Maybe the name of a Spring bean defined in the Spring context. + * + * Defaults to unset. + */ + String diskStoreName() default ""; + + /** + * Determines whether disk-based operations (used in overflow and persistent) are synchronous or asynchronous. + * + * Defaults to {@literal synchronous}. + */ + boolean diskSynchronous() default true; + + /** + * Determines whether this {@link org.apache.geode.cache.Region Region's} data access operations participates in + * any existing, Global JTA transaction in progress. + * + * Defaults to {@literal false} (will NOT ignore JTA). + */ + boolean ignoreJta() default false; + + /** + * Determines whether this persistent entity's {@link org.apache.geode.cache.Region} is persistent, + * storing data to disk. + * + * Note, this setting independent of whether or not the {@link org.apache.geode.cache.Region} associated + * with this persistent entity overflows data to disk during eviction due to entry/heap/memory constraints. + * + * A {@link org.apache.geode.cache.Region} can also be persistent without an explicit + * {@link org.apache.geode.cache.DiskStore} defined; in that case, GemFire/Geode writes to the "DEFAULT" + * {@link org.apache.geode.cache.DiskStore}. + * + * Defaults to {@literal false}. + * + * @see org.apache.geode.cache.DataPolicy + */ + boolean persistent() default false; + +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/PartitionRegion.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/PartitionRegion.java new file mode 100644 index 00000000..744b0401 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/PartitionRegion.java @@ -0,0 +1,180 @@ +/* + * Copyright 2016 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.mapping.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * {@link Annotation} defining the Partition {@link Region} in which the application persistent entity will be stored. + * + * @author John Blum + * @see Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Region +@SuppressWarnings("unused") +public @interface PartitionRegion { + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "name") + String name() default ""; + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "value") + String value() default ""; + + /** + * Sets the name of the {@link org.apache.geode.cache.Region} to which this persistent entity's + * {@link org.apache.geode.cache.Region} will be collocated. + * + * Collocation is used in data access, querying operations where the user wishes to combine data + * from multiple {@link org.apache.geode.cache.Region Regions} into a single result set returned + * from an OQL statement. + * + * Defaults to unset. + */ + String collocatedWith() default ""; + + /** + * Name of the {@link org.apache.geode.cache.DiskStore} in which this persistent entity's data is overflowed + * and/or persisted. + * + * Maybe the name of a Spring bean defined in the Spring context. + * + * Defaults to unset. + */ + String diskStoreName() default ""; + + /** + * Determines whether disk-based operations (used in overflow and persistent) are synchronous or asynchronous. + * + * Defaults to {@literal synchronous}. + */ + boolean diskSynchronous() default true; + + /** + * Defines an array of fixed partitions in a {@link org.apache.geode.cache.DataPolicy#PARTITION} + * {@link org.apache.geode.cache.Region} + * + * Default is unset. + * + * @see org.apache.geode.cache.FixedPartitionAttributes + */ + FixedPartition[] fixedPartitions() default {}; + + /** + * Determines whether this {@link org.apache.geode.cache.Region Region's} data access operations participates in + * any existing, Global JTA transaction in progress. + * + * Defaults to {@literal false} (will NOT ignore JTA). + */ + boolean ignoreJta() default false; + + /** + * Name of the {@link org.apache.geode.cache.PartitionResolver} used to customize the partitioning strategy + * in this persistent entity's {@link org.apache.geode.cache.DataPolicy#PARTITION} + * {@link org.apache.geode.cache.Region}. + * + * This setting may also be the name of a Spring bean defined in the Spring context. + * + * Defaults to unset, thus using the default GemFire/Geode partitioning strategy. + */ + String partitionResolverName() default ""; + + /** + * Determines whether this persistent entity's {@link org.apache.geode.cache.Region} is persistent, + * storing data to disk. + * + * Note, this setting independent of whether or not the {@link org.apache.geode.cache.Region} associated + * with this persistent entity overflows data to disk during eviction due to entry/heap/memory constraints. + * + * A {@link org.apache.geode.cache.Region} can also be persistent without an explicit + * {@link org.apache.geode.cache.DiskStore} defined; in that case, GemFire/Geode writes to the "DEFAULT" + * {@link org.apache.geode.cache.DiskStore}. + * + * Defaults to {@literal false}. + * + * @see org.apache.geode.cache.DataPolicy + */ + boolean persistent() default false; + + /** + * Defines the number of redundant copies of this persistent entity's data. + * + * Defaults to {@literal 0}. + */ + int redundantCopies() default 0; + + /** + * {@link FixedPartition} defined fixed partition meta-data within + * a {@link org.apache.geode.cache.DataPolicy#PARTITION} {@link org.apache.geode.cache.Region}. + * + * @see org.apache.geode.cache.FixedPartitionAttributes + */ + @interface FixedPartition { + + /** + * Name of the fixed partition. + */ + String name(); + + /** + * Set whether this partition is the primary partition in + * the {@link org.apache.geode.cache.DataPolicy#PARTITION} {@link org.apache.geode.cache.Region}. + * + * Defaults to {@literal false}. + */ + boolean primary() default false; + + /** + * Defines the number of bucket in this partition. + * + * Defaults to {@literal 1}. + */ + int numBuckets() default 1; + + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/Region.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Region.java new file mode 100644 index 00000000..e682214e --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Region.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 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.mapping.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.List; + +import org.springframework.core.annotation.AliasFor; + +/** + * {@link Annotation} defining the {@link Region} in which the application persistent entity will be stored. + * + * @author Oliver Gierke + * @author John Blum + * @see org.apache.geode.cache.Region + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@SuppressWarnings("unused") +public @interface Region { + + @SuppressWarnings("unchecked") + List> REGION_ANNOTATION_TYPES = Arrays.asList( + ClientRegion.class, LocalRegion.class, PartitionRegion.class, ReplicateRegion.class, Region.class); + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(attribute = "value") + String name() default ""; + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(attribute = "name") + String value() default ""; + +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/ReplicateRegion.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/ReplicateRegion.java new file mode 100644 index 00000000..656258a1 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/ReplicateRegion.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016 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.mapping.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.data.gemfire.ScopeType; + +/** + * {@link Annotation} defining the Replicate {@link Region} in which the application persistent entity will be stored. + * + * @author John Blum + * @see Region + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Region +@SuppressWarnings("unused") +public @interface ReplicateRegion { + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "name") + String name() default ""; + + /** + * Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region} + * in which the application persistent entity will be stored (e.g. "Users", or "/Local/Admin/Users"). + * + * Defaults to simple name of the application persistent entity defined by {@link java.lang.Class#getSimpleName()}. + * + * @return the name or fully-qualified path of the {@link Region} in which the application persistent entity + * will be stored. + */ + @AliasFor(annotation = Region.class, attribute = "value") + String value() default ""; + + /** + * Name of the {@link org.apache.geode.cache.DiskStore} in which this persistent entity's data is overflowed + * and/or persisted. + * + * Maybe the name of a Spring bean defined in the Spring context. + * + * Defaults to unset. + */ + String diskStoreName() default ""; + + /** + * Determines whether disk-based operations (used in overflow and persistent) are synchronous or asynchronous. + * + * Defaults to {@literal synchronous}. + */ + boolean diskSynchronous() default true; + + /** + * Determines whether this {@link org.apache.geode.cache.Region Region's} data access operations participates in + * any existing, Global JTA transaction in progress. + * + * Defaults to {@literal false} (will NOT ignore JTA). + */ + boolean ignoreJta() default false; + + /** + * Determines whether this persistent entity's {@link org.apache.geode.cache.Region} is persistent, + * storing data to disk. + * + * Note, this setting independent of whether or not the {@link org.apache.geode.cache.Region} associated + * with this persistent entity overflows data to disk during eviction due to entry/heap/memory constraints. + * + * A {@link org.apache.geode.cache.Region} can also be persistent without an explicit + * {@link org.apache.geode.cache.DiskStore} defined; in that case, GemFire/Geode writes to the "DEFAULT" + * {@link org.apache.geode.cache.DiskStore}. + * + * Defaults to {@literal false}. + * + * @see org.apache.geode.cache.DataPolicy + */ + boolean persistent() default false; + + /** + * Defines the {@link org.apache.geode.cache.Scope} used by this persistent entity's + * {@link org.apache.geode.cache.DataPolicy#REPLICATE} {@link org.apache.geode.cache.Region} to + * acknowledge messages sent between peers. + * + * Defaults to {@link ScopeType#DISTRIBUTED_NO_ACK} + */ + ScopeType scope() default ScopeType.DISTRIBUTED_NO_ACK; + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtension.java index 1583c2a8..2aee4b3f 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtension.java @@ -24,7 +24,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.data.gemfire.mapping.GemfireMappingContext; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; @@ -57,7 +57,7 @@ public class GemfireRepositoryConfigurationExtension extends RepositoryConfigura */ @Override protected Collection> getIdentifyingAnnotations() { - return Collections.>singleton(Region.class); + return Region.REGION_ANNOTATION_TYPES; } /* diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java index 6e855e3a..39cf340b 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java @@ -126,8 +126,8 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { } String getRepositoryRegionName(Class repositoryInterface) { - return (repositoryInterface.isAnnotationPresent(org.springframework.data.gemfire.mapping.Region.class) ? - repositoryInterface.getAnnotation(org.springframework.data.gemfire.mapping.Region.class).value() : null); + return (repositoryInterface.isAnnotationPresent(org.springframework.data.gemfire.mapping.annotation.Region.class) ? + repositoryInterface.getAnnotation(org.springframework.data.gemfire.mapping.annotation.Region.class).value() : null); } /* diff --git a/src/main/java/org/springframework/data/gemfire/support/ClientRegionShortcutWrapper.java b/src/main/java/org/springframework/data/gemfire/support/ClientRegionShortcutWrapper.java index 56ba884c..d7eebff2 100644 --- a/src/main/java/org/springframework/data/gemfire/support/ClientRegionShortcutWrapper.java +++ b/src/main/java/org/springframework/data/gemfire/support/ClientRegionShortcutWrapper.java @@ -16,6 +16,7 @@ package org.springframework.data.gemfire.support; +import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.client.ClientRegionShortcut; import org.springframework.util.ObjectUtils; @@ -29,24 +30,27 @@ import org.springframework.util.ObjectUtils; */ @SuppressWarnings("unused") public enum ClientRegionShortcutWrapper { - CACHING_PROXY(ClientRegionShortcut.CACHING_PROXY), - CACHING_PROXY_HEAP_LRU(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU), - CACHING_PROXY_OVERFLOW(ClientRegionShortcut.CACHING_PROXY_OVERFLOW), - LOCAL(ClientRegionShortcut.LOCAL), - LOCAL_HEAP_LRU(ClientRegionShortcut.LOCAL_HEAP_LRU), - LOCAL_OVERFLOW(ClientRegionShortcut.LOCAL_OVERFLOW), - LOCAL_PERSISTENT(ClientRegionShortcut.LOCAL_PERSISTENT), - LOCAL_PERSISTENT_OVERFLOW(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW), - PROXY(ClientRegionShortcut.PROXY), - UNSPECIFIED(null); + CACHING_PROXY(ClientRegionShortcut.CACHING_PROXY, DataPolicy.NORMAL), + CACHING_PROXY_HEAP_LRU(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU, DataPolicy.NORMAL), + CACHING_PROXY_OVERFLOW(ClientRegionShortcut.CACHING_PROXY_OVERFLOW, DataPolicy.NORMAL), + LOCAL(ClientRegionShortcut.LOCAL, DataPolicy.NORMAL), + LOCAL_HEAP_LRU(ClientRegionShortcut.LOCAL_HEAP_LRU, DataPolicy.NORMAL), + LOCAL_OVERFLOW(ClientRegionShortcut.LOCAL_OVERFLOW, DataPolicy.NORMAL), + LOCAL_PERSISTENT(ClientRegionShortcut.LOCAL_PERSISTENT, DataPolicy.PERSISTENT_REPLICATE), + LOCAL_PERSISTENT_OVERFLOW(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW, DataPolicy.PERSISTENT_REPLICATE), + PROXY(ClientRegionShortcut.PROXY, DataPolicy.EMPTY), + UNSPECIFIED(null, null); private final ClientRegionShortcut clientRegionShortcut; - ClientRegionShortcutWrapper(final ClientRegionShortcut clientRegionShortcut) { + private final DataPolicy dataPolicy; + + ClientRegionShortcutWrapper(ClientRegionShortcut clientRegionShortcut, DataPolicy dataPolicy) { this.clientRegionShortcut = clientRegionShortcut; + this.dataPolicy = dataPolicy; } - public static ClientRegionShortcutWrapper valueOf(final ClientRegionShortcut clientRegionShortcut) { + public static ClientRegionShortcutWrapper valueOf(ClientRegionShortcut clientRegionShortcut) { for (ClientRegionShortcutWrapper wrapper : values()) { if (ObjectUtils.nullSafeEquals(wrapper.getClientRegionShortcut(), clientRegionShortcut)) { return wrapper; @@ -57,7 +61,11 @@ public enum ClientRegionShortcutWrapper { } public ClientRegionShortcut getClientRegionShortcut() { - return clientRegionShortcut; + return this.clientRegionShortcut; + } + + public DataPolicy getDataPolicy() { + return this.dataPolicy; } public boolean isCaching() { @@ -87,5 +95,4 @@ public enum ClientRegionShortcutWrapper { public boolean isProxy() { return name().contains("PROXY"); } - } diff --git a/src/main/java/org/springframework/data/gemfire/support/RegionShortcutWrapper.java b/src/main/java/org/springframework/data/gemfire/support/RegionShortcutWrapper.java index 522466c5..ab49ee78 100644 --- a/src/main/java/org/springframework/data/gemfire/support/RegionShortcutWrapper.java +++ b/src/main/java/org/springframework/data/gemfire/support/RegionShortcutWrapper.java @@ -16,6 +16,7 @@ package org.springframework.data.gemfire.support; +import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.RegionShortcut; import org.springframework.util.ObjectUtils; @@ -29,38 +30,41 @@ import org.springframework.util.ObjectUtils; */ @SuppressWarnings("unused") public enum RegionShortcutWrapper { - LOCAL(RegionShortcut.LOCAL), - LOCAL_HEAP_LRU(RegionShortcut.LOCAL_HEAP_LRU), - LOCAL_OVERFLOW(RegionShortcut.LOCAL_OVERFLOW), - LOCAL_PERSISTENT(RegionShortcut.LOCAL_PERSISTENT), - LOCAL_PERSISTENT_OVERFLOW(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW), - PARTITION(RegionShortcut.PARTITION), - PARTITION_HEAP_LRU(RegionShortcut.PARTITION_HEAP_LRU), - PARTITION_OVERFLOW(RegionShortcut.PARTITION_OVERFLOW), - PARTITION_PERSISTENT(RegionShortcut.PARTITION_PERSISTENT), - PARTITION_PERSISTENT_OVERFLOW(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW), - PARTITION_PROXY(RegionShortcut.PARTITION_PROXY), - PARTITION_PROXY_REDUNDANT(RegionShortcut.PARTITION_PROXY_REDUNDANT), - PARTITION_REDUNDANT(RegionShortcut.PARTITION_REDUNDANT), - PARTITION_REDUNDANT_HEAP_LRU(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU), - PARTITION_REDUNDANT_OVERFLOW(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW), - PARTITION_REDUNDANT_PERSISTENT(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT), - PARTITION_REDUNDANT_PERSISTENT_OVERFLOW(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW), - REPLICATE(RegionShortcut.REPLICATE), - REPLICATE_HEAP_LRU(RegionShortcut.REPLICATE_HEAP_LRU), - REPLICATE_OVERFLOW(RegionShortcut.REPLICATE_OVERFLOW), - REPLICATE_PERSISTENT(RegionShortcut.REPLICATE_PERSISTENT), - REPLICATE_PERSISTENT_OVERFLOW(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW), - REPLICATE_PROXY(RegionShortcut.REPLICATE_PROXY), - UNSPECIFIED(null); + LOCAL(RegionShortcut.LOCAL, DataPolicy.NORMAL), + LOCAL_HEAP_LRU(RegionShortcut.LOCAL_HEAP_LRU, DataPolicy.NORMAL), + LOCAL_OVERFLOW(RegionShortcut.LOCAL_OVERFLOW, DataPolicy.NORMAL), + LOCAL_PERSISTENT(RegionShortcut.LOCAL_PERSISTENT, DataPolicy.NORMAL), + LOCAL_PERSISTENT_OVERFLOW(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW, DataPolicy.PERSISTENT_REPLICATE), + PARTITION(RegionShortcut.PARTITION, DataPolicy.PARTITION), + PARTITION_HEAP_LRU(RegionShortcut.PARTITION_HEAP_LRU, DataPolicy.PARTITION), + PARTITION_OVERFLOW(RegionShortcut.PARTITION_OVERFLOW, DataPolicy.PARTITION), + PARTITION_PERSISTENT(RegionShortcut.PARTITION_PERSISTENT, DataPolicy.PERSISTENT_PARTITION), + PARTITION_PERSISTENT_OVERFLOW(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW, DataPolicy.PERSISTENT_PARTITION), + PARTITION_PROXY(RegionShortcut.PARTITION_PROXY, DataPolicy.PARTITION), + PARTITION_PROXY_REDUNDANT(RegionShortcut.PARTITION_PROXY_REDUNDANT, DataPolicy.PARTITION), + PARTITION_REDUNDANT(RegionShortcut.PARTITION_REDUNDANT, DataPolicy.PARTITION), + PARTITION_REDUNDANT_HEAP_LRU(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU, DataPolicy.PARTITION), + PARTITION_REDUNDANT_OVERFLOW(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW, DataPolicy.PARTITION), + PARTITION_REDUNDANT_PERSISTENT(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT, DataPolicy.PERSISTENT_PARTITION), + PARTITION_REDUNDANT_PERSISTENT_OVERFLOW(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW, DataPolicy.PERSISTENT_PARTITION), + REPLICATE(RegionShortcut.REPLICATE, DataPolicy.REPLICATE), + REPLICATE_HEAP_LRU(RegionShortcut.REPLICATE_HEAP_LRU, DataPolicy.REPLICATE), + REPLICATE_OVERFLOW(RegionShortcut.REPLICATE_OVERFLOW, DataPolicy.REPLICATE), + REPLICATE_PERSISTENT(RegionShortcut.REPLICATE_PERSISTENT, DataPolicy.PERSISTENT_REPLICATE), + REPLICATE_PERSISTENT_OVERFLOW(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW, DataPolicy.PERSISTENT_REPLICATE), + REPLICATE_PROXY(RegionShortcut.REPLICATE_PROXY, DataPolicy.EMPTY), + UNSPECIFIED(null, null); + + private final DataPolicy dataPolicy; private final RegionShortcut regionShortcut; - RegionShortcutWrapper(final RegionShortcut regionShortcut) { + RegionShortcutWrapper(RegionShortcut regionShortcut, DataPolicy dataPolicy) { this.regionShortcut = regionShortcut; + this.dataPolicy = dataPolicy; } - public static RegionShortcutWrapper valueOf(final RegionShortcut regionShortcut) { + public static RegionShortcutWrapper valueOf(RegionShortcut regionShortcut) { for (RegionShortcutWrapper wrapper : values()) { if (ObjectUtils.nullSafeEquals(wrapper.getRegionShortcut(), regionShortcut)) { return wrapper; @@ -70,6 +74,14 @@ public enum RegionShortcutWrapper { return RegionShortcutWrapper.UNSPECIFIED; } + public DataPolicy getDataPolicy() { + return this.dataPolicy; + } + + public RegionShortcut getRegionShortcut() { + return this.regionShortcut; + } + public boolean isHeapLru() { return name().contains("HEAP_LRU"); } @@ -105,9 +117,4 @@ public enum RegionShortcutWrapper { public boolean isReplicate() { return name().contains("REPLICATE"); } - - public RegionShortcut getRegionShortcut() { - return regionShortcut; - } - } diff --git a/src/main/java/org/springframework/data/gemfire/util/ArrayUtils.java b/src/main/java/org/springframework/data/gemfire/util/ArrayUtils.java index 5df4c519..cdd27d18 100644 --- a/src/main/java/org/springframework/data/gemfire/util/ArrayUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/ArrayUtils.java @@ -16,6 +16,8 @@ package org.springframework.data.gemfire.util; import java.lang.reflect.Array; import java.util.Arrays; +import org.springframework.util.ObjectUtils; + /** * The ArrayUtils class is a utility class for working with Object arrays. * @@ -37,6 +39,18 @@ public abstract class ArrayUtils { return elements; } + /** + * Returns the given {@code array} if not {@literal null} or empty, otherwise returns the {@code defaultArray}. + * + * @param {@link Class} type of the array elements. + * @param array array to evaluate. + * @param defaultArray array to return if the given {@code array} is {@literal null} or empty. + * @return the given {@code array} if not {@literal null} or empty otherwise return the {@code defaultArray}. + */ + public static T[] defaultIfEmpty(T[] array, T[] defaultArray) { + return (!ObjectUtils.isEmpty(array) ? array : defaultArray); + } + /** * Null-safe method to return the first element in the array or {@literal null} * if the array is {@literal null} or empty. diff --git a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java index 8bb552c9..65d59a2f 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java @@ -47,6 +47,28 @@ import org.springframework.util.Assert; @SuppressWarnings("unused") public abstract class CollectionUtils extends org.springframework.util.CollectionUtils { + /** + * Adds all elements from the given {@link Iterable} to the {@link Collection}. + * + * @param {@link Class} type of the elements in the {@link Collection} and {@link Iterable}. + * @param concrete {@link Class} type of the {@link Collection}. + * @param collection {@link Collection} in which to add the elements from the {@link Iterable}. + * @param iterable {@link Iterable} containing the elements to add to the {@link Collection}. + * @return the given {@link Collection}. + * @throws IllegalArgumentException if {@link Collection} is {@literal null}. + * @see java.lang.Iterable + * @see java.util.Collection + */ + public static > T addAll(T collection, Iterable iterable) { + Assert.notNull(collection, "Collection must not be null"); + + for (E element : nullSafeIterable(iterable)) { + collection.add(element); + } + + return collection; + } + /** * Returns an unmodifiable {@link Set} containing the elements from the given object array. * @@ -61,6 +83,32 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio return Collections.unmodifiableSet(set); } + /** + * Returns the given {@link Iterable} if not {@literal null} or empty, otherwise returns the {@code defaultIterable}. + * + * @param concrete {@link Class} type of the {@link Iterable}. + * @param {@link Class} type of the elements in the {@link Iterable Iterables}. + * @param iterable {@link Iterable} to evaluate. + * @param defaultIterable {@link Iterable} to return if the given {@code iterable} is {@literal null} or empty. + * @return {@code iterable} if not {@literal null} or empty otherwise return {@code defaultIterable}. + * @see java.lang.Iterable + */ + public static > T defaultIfEmpty(T iterable, T defaultIterable) { + return (iterable != null && iterable.iterator().hasNext() ? iterable : defaultIterable); + } + + /** + * Returns an empty {@link Iterable} object. + * + * @param {@link Class} type of the elements in the {@link Iterable}. + * @return an empty {@link Iterable}. + * @see java.lang.Iterable + * @see #nullSafeIterable(Iterable) + */ + public static Iterable emptyIterable() { + return nullSafeIterable(null); + } + /** * Adapts the given Enumeration as an Iterable object for use within a for each loop. * @@ -124,15 +172,18 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio return (iterable != null ? iterable : new Iterable() { @Override public Iterator iterator() { return new Iterator() { - @Override public boolean hasNext() { + @Override + public boolean hasNext() { return false; } - @Override public T next() { + @Override + public T next() { throw new NoSuchElementException("No more elements"); } - @Override public void remove() { + @Override + public void remove() { throw new UnsupportedOperationException("Operation not supported"); } }; @@ -165,6 +216,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.Collections#emptyMap() * @see java.util.Map */ + @SuppressWarnings("all") public static Map nullSafeMap(Map map) { return (map != null ? map : Collections.emptyMap()); } diff --git a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java index e3f3085e..aa8de62b 100644 --- a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java @@ -45,7 +45,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.gemfire.client.ClientCacheFactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.client.PoolFactoryBean; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.process.ProcessExecutor; import org.springframework.data.gemfire.process.ProcessWrapper; import org.springframework.data.gemfire.server.CacheServerFactoryBean; diff --git a/src/test/java/org/springframework/data/gemfire/PartitionAttributesFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/PartitionAttributesFactoryBeanTest.java index 9349d6ee..d92aac08 100644 --- a/src/test/java/org/springframework/data/gemfire/PartitionAttributesFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/PartitionAttributesFactoryBeanTest.java @@ -26,8 +26,7 @@ import org.apache.geode.cache.PartitionResolver; import org.junit.Test; /** - * The PartitionAttributesFactoryBeanTest class is test suite of test cases testing the contract and functionality of - * the PartitionAttributesFactoryBean class. + * Unit tests for {@link PartitionAttributesFactoryBean}. * * @author John Blum * @see org.junit.Test @@ -52,11 +51,12 @@ public class PartitionAttributesFactoryBeanTest { partitionAttributesFactoryBean.setColocatedWith("mockColocatedRegion"); partitionAttributesFactoryBean.setLocalMaxMemory(1024); partitionAttributesFactoryBean.setPartitionResolver(createMockPartitionResolver("mockPartitionResolver")); - partitionAttributesFactoryBean.setRecoveryDelay(1000l); + partitionAttributesFactoryBean.setRecoveryDelay(1000L); partitionAttributesFactoryBean.setRedundantCopies(1); - partitionAttributesFactoryBean.setStartupRecoveryDelay(60000l); - partitionAttributesFactoryBean.setTotalMaxMemory(8192l); + partitionAttributesFactoryBean.setStartupRecoveryDelay(60000L); + partitionAttributesFactoryBean.setTotalMaxMemory(8192L); partitionAttributesFactoryBean.setTotalNumBuckets(42); + partitionAttributesFactoryBean.afterPropertiesSet(); PartitionAttributes partitionAttributes = partitionAttributesFactoryBean.getObject(); @@ -65,10 +65,10 @@ public class PartitionAttributesFactoryBeanTest { assertEquals(1024, partitionAttributes.getLocalMaxMemory()); assertNotNull(partitionAttributes.getPartitionResolver()); assertEquals("mockPartitionResolver", partitionAttributes.getPartitionResolver().getName()); - assertEquals(1000l, partitionAttributes.getRecoveryDelay()); + assertEquals(1000L, partitionAttributes.getRecoveryDelay()); assertEquals(1, partitionAttributes.getRedundantCopies()); - assertEquals(60000l, partitionAttributes.getStartupRecoveryDelay()); - assertEquals(8192l, partitionAttributes.getTotalMaxMemory()); + assertEquals(60000L, partitionAttributes.getStartupRecoveryDelay()); + assertEquals(8192L, partitionAttributes.getTotalMaxMemory()); assertEquals(42, partitionAttributes.getTotalNumBuckets()); } @@ -76,14 +76,13 @@ public class PartitionAttributesFactoryBeanTest { public void testValidationOnRedundantCopiesWhenExceedsBound() throws Exception { PartitionAttributesFactoryBean partitionAttributesFactoryBean = new PartitionAttributesFactoryBean(); partitionAttributesFactoryBean.setRedundantCopies(4); - partitionAttributesFactoryBean.getObject(); + partitionAttributesFactoryBean.afterPropertiesSet(); } @Test(expected = IllegalStateException.class) public void testValidationOnRedundantCopiesWhenPrecedesBound() throws Exception { PartitionAttributesFactoryBean partitionAttributesFactoryBean = new PartitionAttributesFactoryBean(); partitionAttributesFactoryBean.setRedundantCopies(-1); - partitionAttributesFactoryBean.getObject(); + partitionAttributesFactoryBean.afterPropertiesSet(); } - } diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegionsConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegionsConfigurationUnitTests.java new file mode 100644 index 00000000..64e5b98c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegionsConfigurationUnitTests.java @@ -0,0 +1,522 @@ +/* + * Copyright 2016 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.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.FixedPartitionAttributes; +import org.apache.geode.cache.PartitionAttributes; +import org.apache.geode.cache.PartitionResolver; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.Scope; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientRegionFactory; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.After; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.gemfire.config.annotation.test.entities.ClientRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.CollocatedPartitionRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.GenericRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.NonEntity; +import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; +import org.springframework.data.gemfire.support.ClientRegionShortcutWrapper; +import org.springframework.data.gemfire.util.CollectionUtils; + +/** + * Unit tests for the {@link EnableEntityDefinedRegions} annotation and {@link EntityDefinedRegionsConfiguration} class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions + * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration + * @since 1.9.0 + */ +public class EnableEntityDefinedRegionsConfigurationUnitTests { + + private static final AtomicInteger MOCK_ID = new AtomicInteger(0); + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + if (applicationContext != null) { + applicationContext.close(); + } + } + + /* (non-Javadoc) */ + protected void assertRegion(Region region, String name) { + assertRegion(region, name, null, null); + } + + /* (non-Javadoc) */ + protected void assertRegion(Region region, String name, + Class keyConstraint, Class valueConstraint) { + + assertRegion(region, name, String.format("%1$s%2$s", Region.SEPARATOR, name), keyConstraint, valueConstraint); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unused") + protected void assertRegion(Region region, String name, String fullPath) { + assertRegion(region, name, fullPath, null, null); + } + + /* (non-Javadoc) */ + protected void assertRegion(Region region, String name, String fullPath, + Class keyConstraint, Class valueConstraint) { + + assertThat(region).isNotNull(); + assertThat(region.getName()).isEqualTo(name); + assertThat(region.getFullPath()).isEqualTo(fullPath); + assertThat(region.getAttributes()).isNotNull(); + assertThat(region.getAttributes().getKeyConstraint()).isEqualTo(keyConstraint); + assertThat(region.getAttributes().getValueConstraint()).isEqualTo(valueConstraint); + } + + /* (non-Javadoc) */ + protected void assertRegionAttributes(RegionAttributes regionAttributes, DataPolicy dataPolicy, + String diskStoreName, Boolean diskSynchronous, Boolean ignoreJta, String poolName, Scope scope) { + + assertThat(regionAttributes).isNotNull(); + assertThat(regionAttributes.getDataPolicy()).isEqualTo(dataPolicy); + assertThat(regionAttributes.getDiskStoreName()).isEqualTo(diskStoreName); + assertThat(regionAttributes.isDiskSynchronous()).isEqualTo(diskSynchronous); + assertThat(regionAttributes.getIgnoreJTA()).isEqualTo(ignoreJta); + assertThat(regionAttributes.getPoolName()).isEqualToIgnoringCase(poolName); + assertThat(regionAttributes.getScope()).isEqualTo(scope); + } + + /* (non-Javadoc) */ + protected void assertPartitionAttributes(PartitionAttributes partitionAttributes, + String collocatedWith, PartitionResolver partitionResolver, Integer redundantCopies) { + + assertThat(partitionAttributes).isNotNull(); + assertThat(partitionAttributes.getColocatedWith()).isEqualTo(collocatedWith); + assertThat(partitionAttributes.getPartitionResolver()).isEqualTo(partitionResolver); + assertThat(partitionAttributes.getRedundantCopies()).isEqualTo(redundantCopies); + } + + /* (non-Javadoc) */ + protected void assertFixedPartitionAttributes(FixedPartitionAttributes fixedPartitionAttributes, + String partitionName, boolean primary, int numBuckets) { + + assertThat(fixedPartitionAttributes).isNotNull(); + assertThat(fixedPartitionAttributes.getPartitionName()).isEqualTo(partitionName); + assertThat(fixedPartitionAttributes.isPrimary()).isEqualTo(primary); + assertThat(fixedPartitionAttributes.getNumBuckets()).isEqualTo(numBuckets); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected FixedPartitionAttributes findFixedPartitionAttributes(PartitionAttributes partitionAttributes, + String partitionName) { + + assertThat(partitionAttributes).isNotNull(); + + List fixedPartitionAttributes = + CollectionUtils.nullSafeList(partitionAttributes.getFixedPartitionAttributes()); + + for (FixedPartitionAttributes attributes : fixedPartitionAttributes) { + if (attributes.getPartitionName().equals(partitionName)) { + return attributes; + } + } + + return null; + } + + /* (non-Javadoc) */ + protected ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); + applicationContext.registerShutdownHook(); + return applicationContext; + } + + @Test + @SuppressWarnings("unchecked") + public void entityClientRegionsDefined() { + applicationContext = newApplicationContext(ClientPersistentEntitiesConfiguration.class); + + Region sessions = applicationContext.getBean("Sessions", Region.class); + + assertRegion(sessions, "Sessions", String.class, ClientRegionEntity.class); + assertRegionAttributes(sessions.getAttributes(), DataPolicy.NORMAL, null, true, false, + GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, null); + + Region genericRegionEntity = + applicationContext.getBean("GenericRegionEntity", Region.class); + + assertRegion(genericRegionEntity, "GenericRegionEntity", Long.class, GenericRegionEntity.class); + assertRegionAttributes(genericRegionEntity.getAttributes(), DataPolicy.EMPTY, null, true, false, + GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, null); + + assertThat(applicationContext.containsBean("CollocatedPartitionRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("ContactEvents")).isFalse(); + assertThat(applicationContext.containsBean("NonEntity")).isFalse(); + assertThat(applicationContext.containsBean("Accounts")).isFalse(); + assertThat(applicationContext.containsBean("Customers")).isFalse(); + assertThat(applicationContext.containsBean("LocalRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse(); + } + + @Test + @SuppressWarnings("unchecked") + public void entityPeerPartitionRegionsDefined() { + applicationContext = newApplicationContext(PeerPartitionRegionPersistentEntitiesConfiguration.class); + + Region customers = applicationContext.getBean("Customers", Region.class); + + assertRegion(customers, "Customers"); + assertRegionAttributes(customers.getAttributes(), DataPolicy.PERSISTENT_PARTITION, null, true, false, null, + Scope.DISTRIBUTED_NO_ACK); + assertPartitionAttributes(customers.getAttributes().getPartitionAttributes(), null, null, 1); + assertFixedPartitionAttributes(findFixedPartitionAttributes( + customers.getAttributes().getPartitionAttributes(), "one"), "one", true, 16); + assertFixedPartitionAttributes(findFixedPartitionAttributes( + customers.getAttributes().getPartitionAttributes(), "two"), "two", false, 21); + + Region contactEvents = applicationContext.getBean("ContactEvents", Region.class); + + assertRegion(contactEvents, "ContactEvents"); + assertRegionAttributes(contactEvents.getAttributes(), DataPolicy.PERSISTENT_PARTITION, "mockDiskStore", + false, true, null, Scope.DISTRIBUTED_NO_ACK); + assertPartitionAttributes(contactEvents.getAttributes().getPartitionAttributes(), "Customers", + applicationContext.getBean("mockPartitionResolver", PartitionResolver.class), 2); + + assertThat(applicationContext.getBean("mockDiskStore")).isInstanceOf(DiskStore.class); + assertThat(applicationContext.containsBean("ClientRegion")).isFalse(); + assertThat(applicationContext.containsBean("NonEntity")).isFalse(); + assertThat(applicationContext.containsBean("Accounts")).isFalse(); + assertThat(applicationContext.containsBean("LocalRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("Sessions")).isFalse(); + } + + @Test + @SuppressWarnings("unchecked") + public void entityServerRegionsDefined() { + applicationContext = newApplicationContext(AllServerPersistentEntitiesConfiguration.class); + + Region accounts = applicationContext.getBean("Accounts", Region.class); + + assertRegion(accounts, "Accounts"); + assertRegionAttributes(accounts.getAttributes(), DataPolicy.REPLICATE, null, true, false, null, + Scope.DISTRIBUTED_ACK); + + Region customers = applicationContext.getBean("Customers", Region.class); + + assertRegion(customers, "Customers"); + assertRegionAttributes(customers.getAttributes(), DataPolicy.PERSISTENT_PARTITION, null, true, false, null, + Scope.DISTRIBUTED_NO_ACK); + assertPartitionAttributes(customers.getAttributes().getPartitionAttributes(), null, null, 1); + + Region localRegionEntity = applicationContext.getBean("LocalRegionEntity", Region.class); + + assertRegion(localRegionEntity, "LocalRegionEntity"); + assertRegionAttributes(localRegionEntity.getAttributes(), DataPolicy.NORMAL, + null, true, false, null, Scope.LOCAL); + + Region genericRegionEntity = applicationContext.getBean("GenericRegionEntity", Region.class); + + assertRegion(genericRegionEntity, "GenericRegionEntity"); + assertRegionAttributes(genericRegionEntity.getAttributes(), DataPolicy.NORMAL, null, true, false, null, + Scope.DISTRIBUTED_NO_ACK); + + assertThat(applicationContext.containsBean("CollocatedPartitionRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("ContactEvents")).isFalse(); + assertThat(applicationContext.containsBean("NonEntity")).isFalse(); + assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse(); + assertThat(applicationContext.containsBean("Sessions")).isFalse(); + } + + /* (non-Javadoc) */ + protected static String mockName(String baseMockName) { + return String.format("%s%d", baseMockName, MOCK_ID.incrementAndGet()); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected static Cache mockCache() { + Cache mockCache = mock(Cache.class); + + Answer> createRegionFactory = invocation -> { + RegionAttributes defaultRegionAttributes = + mockRegionAttributes(null, null, true, false, null, null, null, Scope.DISTRIBUTED_NO_ACK, null); + + RegionAttributes regionAttributes = (invocation.getArguments().length == 1 + ? invocation.getArgumentAt(0, RegionAttributes.class) : defaultRegionAttributes); + + return mockRegionFactory(regionAttributes); + }; + + when(mockCache.createRegionFactory()).thenAnswer(createRegionFactory); + when(mockCache.createRegionFactory(any(RegionAttributes.class))).thenAnswer(createRegionFactory); + + return mockCache; + } + + protected static ClientCache mockClientCache() { + ClientCache mockClientCache = mock(ClientCache.class, mockName("ClientCache")); + + Answer> createClientRegionFactory = + invocation -> mockClientRegionFactory(invocation.getArgumentAt(0, ClientRegionShortcut.class)); + + when(mockClientCache.createClientRegionFactory(any(ClientRegionShortcut.class))) + .thenAnswer(createClientRegionFactory); + + return mockClientCache; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected static ClientRegionFactory mockClientRegionFactory(ClientRegionShortcut shortcut) { + ClientRegionFactory mockClientRegionFactory = + mock(ClientRegionFactory.class, mockName("MockClientRegionFactory")); + + AtomicReference diskStoreName = new AtomicReference<>(); + AtomicReference diskSynchronous = new AtomicReference<>(true); + AtomicReference keyConstraint = new AtomicReference<>(null); + AtomicReference poolName = new AtomicReference<>(); + AtomicReference valueConstraint = new AtomicReference<>(null); + + when(mockClientRegionFactory.setDiskStoreName(anyString())).thenAnswer( + newSetter(String.class, diskStoreName, mockClientRegionFactory)); + + when(mockClientRegionFactory.setDiskSynchronous(anyBoolean())).thenAnswer( + newSetter(Boolean.TYPE, diskSynchronous, mockClientRegionFactory)); + + when(mockClientRegionFactory.setKeyConstraint(any(Class.class))).thenAnswer( + newSetter(Class.class, keyConstraint, mockClientRegionFactory)); + + when(mockClientRegionFactory.setPoolName(anyString())).thenAnswer( + newSetter(String.class, poolName, mockClientRegionFactory)); + + when(mockClientRegionFactory.setValueConstraint(any(Class.class))).thenAnswer( + newSetter(Class.class, valueConstraint, mockClientRegionFactory)); + + final RegionAttributes mockRegionAttributes = + mock(RegionAttributes.class, mockName("MockClientRegionAttributes")); + + when(mockRegionAttributes.getDataPolicy()).thenReturn( + ClientRegionShortcutWrapper.valueOf(shortcut).getDataPolicy()); + when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName)); + when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous)); + when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint)); + when(mockRegionAttributes.getPoolName()).thenAnswer(newGetter(poolName)); + when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint)); + + when(mockClientRegionFactory.create(anyString())).thenAnswer(new Answer>() { + @Override + public Region answer(InvocationOnMock invocation) throws Throwable { + return mockRegion(invocation.getArgumentAt(0, String.class), mockRegionAttributes); + } + }); + + return mockClientRegionFactory; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected static RegionAttributes mockRegionAttributes(DataPolicy dataPolicy, + String diskStoreName, boolean diskSynchronous, boolean ignoreJta, Class keyConstraint, + PartitionAttributes partitionAttributes, String poolName, Scope scope, Class valueConstraint) { + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class, mockName("MockRegionAttributes")); + + when(mockRegionAttributes.getDataPolicy()).thenReturn(dataPolicy); + when(mockRegionAttributes.getDiskStoreName()).thenReturn(diskStoreName); + when(mockRegionAttributes.isDiskSynchronous()).thenReturn(diskSynchronous); + when(mockRegionAttributes.getIgnoreJTA()).thenReturn(ignoreJta); + when(mockRegionAttributes.getKeyConstraint()).thenReturn(keyConstraint); + when(mockRegionAttributes.getPartitionAttributes()).thenReturn(partitionAttributes); + when(mockRegionAttributes.getPoolName()).thenReturn(poolName); + when(mockRegionAttributes.getScope()).thenReturn(scope); + when(mockRegionAttributes.getValueConstraint()).thenReturn(valueConstraint); + + return mockRegionAttributes; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected static RegionFactory mockRegionFactory(RegionAttributes regionAttributes) { + RegionFactory mockRegionFactory = mock(RegionFactory.class, mockName("MockRegionFactory")); + + AtomicReference dataPolicy = new AtomicReference<>(regionAttributes.getDataPolicy()); + AtomicReference diskStoreName = new AtomicReference<>(regionAttributes.getDiskStoreName()); + AtomicReference diskSynchronous = new AtomicReference<>(regionAttributes.isDiskSynchronous()); + AtomicReference ignoreJta = new AtomicReference<>(regionAttributes.getIgnoreJTA()); + AtomicReference keyConstraint = new AtomicReference<>(null); + AtomicReference partitionAttributes = new AtomicReference<>( + regionAttributes.getPartitionAttributes()); + AtomicReference scope = new AtomicReference<>(regionAttributes.getScope()); + AtomicReference valueConstraint = new AtomicReference<>(null); + + when(mockRegionFactory.setDataPolicy(any(DataPolicy.class))).thenAnswer( + newSetter(DataPolicy.class, dataPolicy, mockRegionFactory)); + + when(mockRegionFactory.setDiskStoreName(anyString())).thenAnswer( + newSetter(String.class, diskStoreName, mockRegionFactory)); + + when(mockRegionFactory.setDiskSynchronous(anyBoolean())).thenAnswer( + newSetter(Boolean.TYPE, diskSynchronous, mockRegionFactory)); + + when(mockRegionFactory.setIgnoreJTA(anyBoolean())).thenAnswer( + newSetter(Boolean.TYPE, ignoreJta, mockRegionFactory)); + + when(mockRegionFactory.setKeyConstraint(any(Class.class))).thenAnswer( + newSetter(Class.class, keyConstraint, mockRegionFactory)); + + when(mockRegionFactory.setPartitionAttributes(any(PartitionAttributes.class))).thenAnswer( + newSetter(PartitionAttributes.class, partitionAttributes, mockRegionFactory)); + + when(mockRegionFactory.setScope(any(Scope.class))).thenAnswer( + newSetter(Scope.class, scope, mockRegionFactory)); + + when(mockRegionFactory.setValueConstraint(any(Class.class))).thenAnswer( + newSetter(Class.class, valueConstraint, mockRegionFactory)); + + final RegionAttributes mockRegionAttributes = + mock(RegionAttributes.class, mockName("MockRegionAttributes")); + + when(mockRegionAttributes.getDataPolicy()).thenAnswer(newGetter(dataPolicy)); + when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName)); + when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous)); + when(mockRegionAttributes.getIgnoreJTA()).thenAnswer(newGetter(ignoreJta)); + when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint)); + when(mockRegionAttributes.getPartitionAttributes()).thenAnswer(newGetter(partitionAttributes)); + when(mockRegionAttributes.getScope()).thenAnswer(newGetter(scope)); + when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint)); + + when(mockRegionFactory.create(anyString())).thenAnswer(new Answer>() { + @Override + public Region answer(InvocationOnMock invocation) throws Throwable { + return mockRegion(invocation.getArgumentAt(0, String.class), mockRegionAttributes); + } + }); + + return mockRegionFactory; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected static Region mockRegion(String name, RegionAttributes regionAttributes) { + Region mockRegion = mock(Region.class, mockName(name)); + + when(mockRegion.getName()).thenReturn(name); + when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name)); + when(mockRegion.getAttributes()).thenReturn(regionAttributes); + + return mockRegion; + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicReference returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newSetter(Class parameterType, AtomicReference argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgumentAt(0, parameterType)); + return returnValue; + }; + } + + @Configuration + @SuppressWarnings("unused") + static abstract class ClientCacheConfiguration { + + @Bean + ClientCache gemfireCache() { + return mockClientCache(); + } + } + + @Configuration + @SuppressWarnings("unused") + static abstract class ServerCacheConfiguration { + + @Bean + Cache gemfireCache() { + return mockCache(); + } + } + + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = ClientRegion.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = CollocatedPartitionRegionEntity.class) }) + @SuppressWarnings("all") + static class AllServerPersistentEntitiesConfiguration extends ServerCacheConfiguration { + } + + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, strict = true, + excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, + classes = { LocalRegion.class, PartitionRegion.class, ReplicateRegion.class })) + @SuppressWarnings("all") + static class ClientPersistentEntitiesConfiguration extends ClientCacheConfiguration { + } + + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, + classes = { ClientRegion.class, LocalRegion.class, ReplicateRegion.class })) + @SuppressWarnings("all") + static class PeerPartitionRegionPersistentEntitiesConfiguration extends ServerCacheConfiguration { + + @Bean @Lazy + DiskStore mockDiskStore() { + return mock(DiskStore.class, mockName("MockDiskStore")); + } + + @Bean @Lazy + PartitionResolver mockPartitionResolver() { + return mock(PartitionResolver.class, mockName("MockPartitionResolver")); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ClientRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ClientRegionEntity.java new file mode 100644 index 00000000..a83681a4 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ClientRegionEntity.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 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.test.entities; + +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; + +/** + * {@link ClientRegionEntity} persistent entity stored in the "Users" {@link org.apache.geode.cache.DataPolicy#NORMAL}, + * client {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@ClientRegion(name = "Sessions", shortcut = ClientRegionShortcut.CACHING_PROXY) +public class ClientRegionEntity { + + @Id + private String id; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/CollocatedPartitionRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/CollocatedPartitionRegionEntity.java new file mode 100644 index 00000000..7a378889 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/CollocatedPartitionRegionEntity.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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.test.entities; + +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; + +/** + * {@link CollocatedPartitionRegionEntity} persistent entity stored in the "ContactEvents" + * {@link org.apache.geode.cache.DataPolicy#PERSISTENT_PARTITION} {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@PartitionRegion(value = "ContactEvents", collocatedWith = "Customers", diskStoreName = "mockDiskStore", + diskSynchronous = false, ignoreJta = true, partitionResolverName = "mockPartitionResolver", + persistent = true, redundantCopies = 2) +public class CollocatedPartitionRegionEntity { + + private String email; + + private String phoneNumber; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/GenericRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/GenericRegionEntity.java new file mode 100644 index 00000000..5d5f7118 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/GenericRegionEntity.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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.test.entities; + +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.annotation.Region; + +/** + * {@link GenericRegionEntity} persistent entity stored in the "GenericRegionEntity" + * {@link org.apache.geode.cache.DataPolicy#NORMAL}, {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@Region +public class GenericRegionEntity { + + @Id + private Long id; + + private String name; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/LocalRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/LocalRegionEntity.java new file mode 100644 index 00000000..43d0780c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/LocalRegionEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 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.test.entities; + +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; + +/** + * {@link LocalRegionEntity} persistent entity stored in the "LocalRegionEntity" {@link org.apache.geode.cache.DataPolicy#NORMAL}, + * {@link org.apache.geode.cache.Scope#LOCAL} {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@LocalRegion +public class LocalRegionEntity { + + @Id + private String id; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/NonEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/NonEntity.java new file mode 100644 index 00000000..4dd14c78 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/NonEntity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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.test.entities; + +/** + * A non-persistent entity class. + * + * @author John Blum + * @since 1.9.0 + */ +public class NonEntity { + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java new file mode 100644 index 00000000..6e939b4f --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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.test.entities; + +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; + +/** + * {@link PartitionRegionEntity} persistent entity stored in the "Customers" + * {@link org.apache.geode.cache.DataPolicy#PERSISTENT_PARTITION} {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@PartitionRegion(name = "Customers", persistent = true, redundantCopies = 1, + fixedPartitions = { + @PartitionRegion.FixedPartition(name = "one", primary = true, numBuckets = 16), + @PartitionRegion.FixedPartition(name = "two", numBuckets = 21) + } +) +public class PartitionRegionEntity { + + @Id + private Long id; + + private String firstName; + private String lastName; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ReplicateRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ReplicateRegionEntity.java new file mode 100644 index 00000000..979b62c5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/ReplicateRegionEntity.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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.test.entities; + +import org.springframework.data.gemfire.ScopeType; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; + +/** + * {@link ReplicateRegionEntity} persistent entity stored in the "Accounts" + * {@link org.apache.geode.cache.DataPolicy#REPLICATE} {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @since 1.9.0 + */ +@ReplicateRegion(name = "Accounts", scope = ScopeType.DISTRIBUTED_ACK) +public class ReplicateRegionEntity { + + private String number; + +} diff --git a/src/test/java/org/springframework/data/gemfire/mapping/GemfireMappingContextTest.java b/src/test/java/org/springframework/data/gemfire/mapping/GemfireMappingContextTest.java index 86f0fa4c..73436ce3 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/GemfireMappingContextTest.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/GemfireMappingContextTest.java @@ -28,6 +28,7 @@ import java.math.BigInteger; import org.junit.Test; import org.springframework.data.gemfire.TestUtils; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.mapping.model.GemfireSimpleTypeHolder; import org.springframework.data.mapping.PersistentEntity; diff --git a/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java index 849360bd..124e9d30 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java @@ -24,12 +24,13 @@ import java.math.BigDecimal; import java.math.BigInteger; import org.junit.Test; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.util.ClassTypeInformation; /** * Unit tests for {@link GemfirePersistentEntity}. - * + * * @author Oliver Gierke * @author John Blum */ diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java index bc12dae1..31834cdb 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java @@ -17,7 +17,7 @@ package org.springframework.data.gemfire.repository.cdi; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.sample.Person; diff --git a/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtensionTest.java b/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtensionTest.java index 245126c9..ab22845d 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtensionTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryConfigurationExtensionTest.java @@ -42,7 +42,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Account.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Account.java index beafdb46..18ffc7a9 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Account.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Account.java @@ -17,17 +17,18 @@ package org.springframework.data.gemfire.repository.sample; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; import org.springframework.util.Assert; /** * The Account class is an abstract data type (ADT) for modeling customer accounts. * * @author John Blum - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @since 1.0.0 */ -@Region("Accounts") +@ReplicateRegion("Accounts") @SuppressWarnings("unused") public class Account { diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java index 60016e0f..366fbefc 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java @@ -16,10 +16,10 @@ package org.springframework.data.gemfire.repository.sample; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; /** - * + * * @author Oliver Gierke */ @Region("address") diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Algorithm.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Algorithm.java index 9726698c..2c933ceb 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Algorithm.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Algorithm.java @@ -17,14 +17,14 @@ package org.springframework.data.gemfire.repository.sample; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; /** * The Algorithm interface define abstract data type modeling a computer algorithm. * * @author John Blum * @see org.springframework.data.annotation.Id - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @since 1.4.0 */ @Region("Algorithms") diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/CatRepository.java b/src/test/java/org/springframework/data/gemfire/repository/sample/CatRepository.java index 2238a2d8..41548cb3 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/CatRepository.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/CatRepository.java @@ -16,7 +16,7 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.Query; diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Customer.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Customer.java index 5532e5c0..6940a41e 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Customer.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Customer.java @@ -17,7 +17,8 @@ package org.springframework.data.gemfire.repository.sample; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; import org.springframework.util.ObjectUtils; /** @@ -25,11 +26,11 @@ import org.springframework.util.ObjectUtils; * * @author John Blum * @see org.springframework.data.annotation.Id - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @since 1.0.0 */ +@ReplicateRegion("Customers") @SuppressWarnings("unused") -@Region("Customers") public class Customer { @Id diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/DogRepository.java b/src/test/java/org/springframework/data/gemfire/repository/sample/DogRepository.java index 25721c19..b2fa3249 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/DogRepository.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/DogRepository.java @@ -16,7 +16,7 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.Query; diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/GuestUser.java b/src/test/java/org/springframework/data/gemfire/repository/sample/GuestUser.java index 4fbe1c7e..fb0ac5ee 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/GuestUser.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/GuestUser.java @@ -16,13 +16,13 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; /** * The GuestUser class represents an authorized restricted user of a service or computer system, etc. * * @author John Blum - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @see org.springframework.data.gemfire.repository.sample.User * @since 1.4.0 */ diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java index cbc3fc65..a57d56ca 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.util.ObjectUtils; /** diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Plant.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Plant.java index 48e26ab5..7011ed0a 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Plant.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Plant.java @@ -17,7 +17,7 @@ package org.springframework.data.gemfire.repository.sample; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.util.ObjectUtils; /** @@ -25,7 +25,7 @@ import org.springframework.util.ObjectUtils; * * @author John Blum * @see org.springframework.data.annotation.Id - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @since 1.4.0 */ @Region("Plants") diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Programmer.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Programmer.java index 8e669da2..5f0ba3b4 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/Programmer.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Programmer.java @@ -16,14 +16,14 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.util.StringUtils; /** * The Programmer class is a User representing/modeling a software engineer/developer. * * @author John J. Blum - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @see org.springframework.data.gemfire.repository.sample.User * @since 1.4.0 */ diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/RabbitRepository.java b/src/test/java/org/springframework/data/gemfire/repository/sample/RabbitRepository.java index 36d1b7e1..f5f4b759 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/RabbitRepository.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/RabbitRepository.java @@ -16,7 +16,7 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.Query; @@ -25,7 +25,7 @@ import org.springframework.data.gemfire.repository.Query; * from/to an underlying data store (GemFire). * * @author John Blum - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @see org.springframework.data.gemfire.repository.GemfireRepository * @see org.springframework.data.gemfire.repository.Query * @since 1.4.0 diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/RootUser.java b/src/test/java/org/springframework/data/gemfire/repository/sample/RootUser.java index b6407e6a..47a1005f 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/RootUser.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/RootUser.java @@ -16,13 +16,13 @@ package org.springframework.data.gemfire.repository.sample; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; /** * The RootUser class represents an authorized administrative user of a service or computer system, etc. * * @author John Blum - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @see org.springframework.data.gemfire.repository.sample.User * @since 1.4.0 */ diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/User.java b/src/test/java/org/springframework/data/gemfire/repository/sample/User.java index fc6aecc4..915032a8 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/User.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/User.java @@ -19,7 +19,7 @@ package org.springframework.data.gemfire.repository.sample; import java.util.Calendar; import org.springframework.data.annotation.Id; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -29,7 +29,7 @@ import org.springframework.util.ObjectUtils; * @author John Blum * @see java.lang.Comparable * @see org.springframework.data.annotation.Id - * @see org.springframework.data.gemfire.mapping.Region + * @see Region * @since 1.4.0 */ @Region("Users") diff --git a/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java index fd2259c8..7d45258f 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java @@ -314,7 +314,7 @@ public class GemfireRepositoryFactoryUnitTests { interface SampleCustomGemfireRepository extends GemfireRepository, SampleCustomRepository { } - @org.springframework.data.gemfire.mapping.Region("People") + @org.springframework.data.gemfire.mapping.annotation.Region("People") interface PersonRepository extends GemfireRepository { } } diff --git a/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java index 68ecdfd6..eee81457 100644 --- a/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java @@ -43,7 +43,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.gemfire.CacheFactoryBean; import org.springframework.data.gemfire.LocalRegionFactoryBean; import org.springframework.data.gemfire.mapping.GemfireMappingContext; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; import org.springframework.data.gemfire.test.support.IdentifierSequence; import org.springframework.data.repository.CrudRepository; diff --git a/src/test/java/org/springframework/data/gemfire/test/model/Person.java b/src/test/java/org/springframework/data/gemfire/test/model/Person.java index 0c838a37..48e25d7d 100644 --- a/src/test/java/org/springframework/data/gemfire/test/model/Person.java +++ b/src/test/java/org/springframework/data/gemfire/test/model/Person.java @@ -24,7 +24,7 @@ import java.util.Date; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.mapping.annotation.Region; import org.springframework.data.gemfire.test.support.IdentifierSequence; import org.springframework.data.gemfire.util.SpringUtils; import org.springframework.util.Assert; diff --git a/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java index 7f57bdb4..4bd48347 100644 --- a/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java @@ -33,7 +33,7 @@ import org.junit.Test; public class ArrayUtilsUnitTests { @Test - public void asArrayRetursEmptyArray() { + public void asArrayReturnsEmptyArray() { Object[] array = ArrayUtils.asArray(); assertThat(array).isNotNull(); @@ -58,6 +58,34 @@ public class ArrayUtilsUnitTests { assertThat(array).isEqualTo(new Object[] { 1 }); } + @Test + public void defaultIfEmptyWithNonNullNonEmptyArrayReturnsArray() { + Object[] array = { "test" }; + Object[] defaultArray = { "tested" }; + + assertThat(ArrayUtils.defaultIfEmpty(array, defaultArray)).isSameAs(array); + } + + @Test + public void defaultIfEmptyWithEmptyArrayReturnsDefaultArray() { + Object[] array = {}; + Object[] defaultArray = { "tested" }; + + assertThat(ArrayUtils.defaultIfEmpty(array, defaultArray)).isSameAs(defaultArray); + } + + @Test + public void defaultIfEmptyWithNullArrayReturnsDefaultArray() { + Object[] defaultArray = { "tested" }; + + assertThat(ArrayUtils.defaultIfEmpty(null, defaultArray)).isSameAs(defaultArray); + } + + @Test + public void defaultIfEmptyWithNullArrayAndNullDefaultArrayReturnsNull() { + assertThat(ArrayUtils.defaultIfEmpty(null, null)).isNull(); + } + @Test public void getFirstWithNonNullArray() { assertThat(ArrayUtils.getFirst(ArrayUtils.asArray(1, 2, 3))).isEqualTo(1); @@ -187,6 +215,7 @@ public class ArrayUtilsUnitTests { } @Test + @SuppressWarnings("unchecked") public void sortIsSuccessful() { Comparable[] array = new Comparable[] { 2, 3, 1 }; Comparable[] sortedArray = ArrayUtils.sort(array); diff --git a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java index 7e95a9eb..f54d6c4b 100644 --- a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java @@ -27,7 +27,9 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -59,6 +61,61 @@ public class CollectionUtilsUnitTests { @Rule public ExpectedException exception = ExpectedException.none(); + @Test + public void addAllIterableElementsToList() { + List target = new ArrayList(Arrays.asList(1, 2, 3)); + Set source = new HashSet(Arrays.asList(1, 2, 3)); + + target = CollectionUtils.addAll(target, source); + + assertThat(target).isNotNull(); + assertThat(target.size()).isEqualTo(6); + assertThat(target).isEqualTo(Arrays.asList(1, 2, 3, 1, 2, 3)); + } + + @Test + public void addAllIterableElementsToSet() { + Set target = new HashSet(Arrays.asList(1, 2, 3)); + Set source = new HashSet(Arrays.asList(1, 2, 3, 4, 5)); + + target = CollectionUtils.addAll(target, source); + + assertThat(target).isNotNull(); + assertThat(target.size()).isEqualTo(5); + assertThat(target).contains(1, 2, 3, 4, 5); + } + + @Test + public void addIterableToNullCollection() { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("Collection must not be null"); + + CollectionUtils.addAll(null, Collections.emptySet()); + } + + @Test + public void addEmptyIterableToCollection() { + Collection target = new ArrayList(Arrays.asList(1, 2, 3)); + + target = CollectionUtils.addAll(target, Collections.emptyList()); + + assertThat(target).isNotNull(); + assertThat(target.size()).isEqualTo(3); + assertThat(target).contains(1, 2, 3); + } + + @Test + public void addNullIterableToCollection() { + Collection target = new ArrayList(Arrays.asList(1, 2, 3)); + + target = CollectionUtils.addAll(target, null); + + assertThat(target).isNotNull(); + assertThat(target.size()).isEqualTo(3); + assertThat(target).contains(1, 2, 3); + } + @Test public void asSetContainsAllArrayElements() { Object[] elements = { "a", "b", "c" }; @@ -67,7 +124,7 @@ public class CollectionUtilsUnitTests { assertThat(set).isNotNull(); assertThat(set.size()).isEqualTo(elements.length); - assertThat(set).containsAll(Arrays.asList(elements)); + assertThat(set.containsAll(Arrays.asList(elements))).isTrue(); } @Test @@ -78,7 +135,7 @@ public class CollectionUtilsUnitTests { assertThat(set).isNotNull(); assertThat(set.size()).isEqualTo(2); - assertThat(set).containsAll(Arrays.asList(elements)); + assertThat(set.containsAll(Arrays.asList(elements))).isTrue(); } @Test(expected = UnsupportedOperationException.class) @@ -99,6 +156,43 @@ public class CollectionUtilsUnitTests { } } + @Test + public void defaultIfEmptyWithNonNullNonEmptyIterableReturnsIterable() { + Iterable iterable = Collections.singleton(1); + Iterable defaultIterable = Collections.singleton(2); + + assertThat(CollectionUtils.defaultIfEmpty(iterable, defaultIterable)).isSameAs(iterable); + } + + @Test + public void defaultIfEmptyWithEmptyIterableReturnsDefault() { + Iterable iterable = Collections.emptySet(); + Iterable defaultIterable = Collections.singleton(2); + + assertThat(CollectionUtils.defaultIfEmpty(iterable, defaultIterable)).isSameAs(defaultIterable); + } + + @Test + public void defaultIfEmptyWithNullIterableReturnsDefault() { + Iterable defaultIterable = Collections.singleton(2); + + assertThat(CollectionUtils.defaultIfEmpty(null, defaultIterable)).isSameAs(defaultIterable); + } + + @Test + public void defaultIfEmptyWithNullIterableAndNullDefaultReturnsNull() { + assertThat(CollectionUtils.defaultIfEmpty((Iterable) null, null)).isNull(); + } + + @Test + public void emptyIterableReturnsEmptyIterable() { + Iterable iterable = CollectionUtils.emptyIterable(); + + assertThat(iterable).isNotNull(); + assertThat(iterable.iterator()).isNotNull(); + assertThat(iterable.iterator().hasNext()).isFalse(); + } + @Test @SuppressWarnings("unchecked") public void iterableEnumeration() {