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 index e2641e86..9a798336 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java @@ -36,9 +36,12 @@ import org.springframework.core.annotation.AliasFor; * * @author John Blum * @see org.springframework.context.annotation.ComponentScan + * @see org.springframework.context.annotation.ComponentScan.Filter * @see org.springframework.context.annotation.Import * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.data.gemfire.config.annotation.EnableIndexes * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration * @see org.apache.geode.cache.Region * @since 1.9.0 */ @@ -46,7 +49,7 @@ import org.springframework.core.annotation.AliasFor; @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented -@Import(EntityDefinedRegionsConfiguration.class) +@Import(IndexConfiguration.class) @SuppressWarnings({ "unused" }) public @interface EnableEntityDefinedRegions { diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexes.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexes.java new file mode 100644 index 00000000..21f53c06 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexes.java @@ -0,0 +1,55 @@ +/* + * 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.query.Index; + +/** + * The {@link EnableIndexes} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated application class to enable the creation of GemFire/Geode Indexes based on application persistent entity + * field/property annotations, such as the {@link @Id} and @Indexed annotations. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @see org.apache.geode.cache.query.Index + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@SuppressWarnings({ "unused" }) +public @interface EnableIndexes { + + /** + * Determines whether all GemFire/Geode {@link Index Indexes} will be defined before created. + * If set to {@literal true}, then all {@link Index Indexes} are defined first and the created + * in a single, bulk operation, thereby improving index creation efficiency. + * + * Defaults to false. + */ + boolean define() 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 index 10706da0..6fb7736a 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java @@ -75,10 +75,32 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** - * The EntityDefinedRegionsConfiguration class... + * The {@link EntityDefinedRegionsConfiguration} class is Spring {@link ImportBeanDefinitionRegistrar} used in + * the {@link EnableEntityDefinedRegions} annotation to dynamically create GemFire/Geode {@link Region Regions} + * based on the application persistent entity classes. * * @author John Blum - * @since 1.0.0 + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar + * @see org.springframework.data.gemfire.LocalRegionFactoryBean + * @see org.springframework.data.gemfire.PartitionedRegionFactoryBean + * @see org.springframework.data.gemfire.ReplicatedRegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.support.GemFireComponentClassTypeScanner + * @see org.springframework.data.gemfire.mapping.annotation.ClientRegion + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity + * @see org.springframework.data.gemfire.mapping.annotation.LocalRegion + * @see org.springframework.data.gemfire.mapping.annotation.PartitionRegion + * @see org.springframework.data.gemfire.mapping.annotation.ReplicateRegion + * @see org.springframework.data.gemfire.mapping.annotation.Region + * @since 1.9.0 */ @SuppressWarnings("unused") public class EntityDefinedRegionsConfiguration @@ -155,6 +177,7 @@ public class EntityDefinedRegionsConfiguration } /* (non-Javadoc) */ + @SuppressWarnings("unused") protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; } @@ -229,7 +252,7 @@ public class EntityDefinedRegionsConfiguration GemfirePersistentEntity persistentEntity = getPersistentEntity(persistentEntityClass); registerRegionBeanDefinition(persistentEntity, strict, registry); - postProcess(persistentEntity); + postProcess(importingClassMetadata, registry, persistentEntity); } } } @@ -594,11 +617,17 @@ public class EntityDefinedRegionsConfiguration * Performs addition post processing on the {@link GemfirePersistentEntity} to offer additional feature support * (e.g. dynamic Index creation). * + * @param importingClassMetadata {@link AnnotationMetadata} for the importing application class. + * @param registry {@link BeanDefinitionRegistry} used to register Spring bean definitions. * @param persistentEntity {@link GemfirePersistentEntity} to process. * @return the given {@link GemfirePersistentEntity}. + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity */ - protected GemfirePersistentEntity postProcess(GemfirePersistentEntity persistentEntity) { + protected GemfirePersistentEntity postProcess(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry, GemfirePersistentEntity persistentEntity) { + return persistentEntity; } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java new file mode 100644 index 00000000..0f385297 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java @@ -0,0 +1,252 @@ +/* + * 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.Annotation; + +import org.apache.geode.cache.Region; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.IndexFactoryBean; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.xml.GemfireConstants; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; +import org.springframework.data.gemfire.mapping.annotation.Indexed; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.util.StringUtils; + +/** + * The {@link IndexConfiguration} class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} + * and extension of {@link EntityDefinedRegionsConfiguration} used in the {@link EnableIndexes} annotation + * to dynamically create GemFire/Geode {@link org.apache.geode.cache.Region} Indexes based on + * {@link GemfirePersistentEntity} {@link GemfirePersistentProperty} annotations. + * + * @author John Blum + * @see java.lang.annotation.Annotation + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.data.annotation.Id + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.IndexType + * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration + * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity + * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty + * @see Indexed + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.Index + * @since 1.9.0 + */ +public class IndexConfiguration extends EntityDefinedRegionsConfiguration { + + /** + * Returns the {@link Annotation} {@link Class type} that configures and creates {@link Region} Indexes + * from application persistent entity properties. + * + * @return the {@link Annotation} {@link Class type} that configures and creates {@link Region Region} Indexes + * from application persistent entity properties. + * @see org.springframework.data.gemfire.config.annotation.EnableIndexes + * @see java.lang.annotation.Annotation + * @see java.lang.Class + */ + protected Class getEnableIndexesAnnotationType() { + return EnableIndexes.class; + } + + /** + * Returns the name of the {@link Annotation} {@link Class type} that configures and creates {@link Region} Indexes + * from application persistent entity properties. + * + * @return the name of the {@link Annotation} {@link Class type} that configures and creates {@link Region Region} + * Indexes from application persistent entity properties. + * @see #getEnableIndexesAnnotationType() + * @see java.lang.Class#getName() + */ + protected String getEnableIndexesAnnotationTypeName() { + return getEnableIndexesAnnotationType().getName(); + } + + /** + * Returns the simple name of the {@link Annotation} {@link Class type} that configures and creates {@link Region} + * Indexes from application persistent entity properties. + * + * @return the simple name of the {@link Annotation} {@link Class type} that configures and creates + * {@link Region Region} Indexes from application persistent entity properties. + * @see #getEnableIndexesAnnotationType() + * @see java.lang.Class#getSimpleName() + */ + @SuppressWarnings("unused") + protected String getEnableIndexesAnnotationTypeSimpleName() { + return getEnableIndexesAnnotationType().getSimpleName(); + } + + /** + * @inheritDoc + */ + @Override + protected GemfirePersistentEntity postProcess(AnnotationMetadata importingClassMetadata, + final BeanDefinitionRegistry registry, GemfirePersistentEntity persistentEntity) { + + final GemfirePersistentEntity localPersistentEntity = + super.postProcess(importingClassMetadata, registry, persistentEntity); + + if (isAnnotationPresent(importingClassMetadata, getEnableIndexesAnnotationTypeName())) { + final AnnotationAttributes enableIndexesAttributes = + getAnnotationAttributes(importingClassMetadata, getEnableIndexesAnnotationTypeName()); + + localPersistentEntity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(GemfirePersistentProperty persistentProperty) { + Id idAnnotation = persistentProperty.findAnnotation(Id.class); + + if (idAnnotation != null) { + registerIndexBeanDefinition(enableIndexesAttributes, localPersistentEntity, persistentProperty, + IndexType.KEY, idAnnotation, registry); + } + + Indexed indexedAnnotation = persistentProperty.findAnnotation(Indexed.class); + + if (indexedAnnotation != null) { + registerIndexBeanDefinition(enableIndexesAttributes, localPersistentEntity, persistentProperty, + indexedAnnotation.type(), indexedAnnotation, registry); + } + } + }); + } + + return persistentEntity; + } + + /** + * Registers an Index of the given {@link IndexType} for the {@link GemfirePersistentProperty} + * on the {@link GemfirePersistentEntity} using the {@link Annotation} meta-data to define the Index. + * + * @param enableIndexesAttributes {@link AnnotationAttributes} containing meta-data + * for the {@link EnableIndexes} annotation. + * @param persistentEntity {@link GemfirePersistentEntity} containing the {@link GemfirePersistentProperty} + * to be indexed. + * @param persistentProperty {@link GemfirePersistentProperty} for which the Index will be created. + * @param indexType {@link IndexType} enum specifying the Index type (e.g. KEY, HASH, etc). + * @param indexAnnotation Index {@link Annotation}. + * @param registry {@link BeanDefinitionRegistry} used to register the Index bean definition. + * @see java.lang.annotation.Annotation + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.data.gemfire.IndexType + * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity + * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty + */ + @SuppressWarnings("unused") + protected void registerIndexBeanDefinition(AnnotationAttributes enableIndexesAttributes, + GemfirePersistentEntity persistentEntity, GemfirePersistentProperty persistentProperty, + IndexType indexType, Annotation indexAnnotation, BeanDefinitionRegistry registry) { + + if (indexAnnotation != null) { + AnnotationAttributes indexAnnotationAttributes = AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(indexAnnotation)); + + BeanDefinitionBuilder indexFactoryBeanBuilder = + BeanDefinitionBuilder.genericBeanDefinition(IndexFactoryBean.class); + + String indexName = resolveName(persistentEntity, persistentProperty, + indexAnnotationAttributes, indexType); + + indexFactoryBeanBuilder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + + indexFactoryBeanBuilder.addPropertyValue("define", resolveDefine(enableIndexesAttributes)); + + indexFactoryBeanBuilder.addPropertyValue("expression", + resolveExpression(persistentEntity, persistentProperty, indexAnnotationAttributes)); + + indexFactoryBeanBuilder.addPropertyValue("from", + resolveFrom(persistentEntity, persistentProperty, indexAnnotationAttributes)); + + indexFactoryBeanBuilder.addPropertyValue("name", indexName); + + indexFactoryBeanBuilder.addPropertyValue("type", + resolveType(persistentEntity, persistentProperty, indexAnnotationAttributes, indexType).toString()); + + registry.registerBeanDefinition(indexName, indexFactoryBeanBuilder.getBeanDefinition()); + } + } + + /* (non-Javadoc) */ + private boolean resolveDefine(AnnotationAttributes enableIndexesAnnotationAttributes) { + return (enableIndexesAnnotationAttributes.containsKey("define") + && enableIndexesAnnotationAttributes.getBoolean("define")); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unused") + private String resolveExpression(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes) { + + String expression = (indexAnnotationAttributes.containsKey("expression") + ? indexAnnotationAttributes.getString("expression") : null); + + return (StringUtils.hasText(expression) ? expression : persistentProperty.getName()); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unused") + private String resolveFrom(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes) { + + String from = (indexAnnotationAttributes.containsKey("from") + ? indexAnnotationAttributes.getString("from") : null); + + return (StringUtils.hasText(from) ? from : persistentEntity.getRegionName()); + } + + /* (non-Javadoc) */ + private String resolveName(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes, + IndexType indexType) { + + String indexName = (indexAnnotationAttributes.containsKey("name") + ? indexAnnotationAttributes.getString("name") : null); + + return (StringUtils.hasText(indexName) ? indexName + : generateIndexName(persistentEntity, persistentProperty, indexType)); + } + + /* (non-Javadoc) */ + private String generateIndexName(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, IndexType indexType) { + + return String.format("%1$s%2$s%3$sIdx", persistentEntity.getRegionName(), + StringUtils.capitalize(persistentProperty.getName()), + StringUtils.capitalize(indexType.name().toLowerCase())); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unused") + private IndexType resolveType(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes, + IndexType indexType) { + + IndexType resolvedIndexType = (indexAnnotationAttributes.containsKey("type") + ? indexAnnotationAttributes.getEnum("type") : null); + + return (resolvedIndexType != null ? resolvedIndexType : indexType); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java new file mode 100644 index 00000000..043c7b21 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java @@ -0,0 +1,80 @@ +/* + * 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.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.query.Index; +import org.springframework.core.annotation.AliasFor; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; + +/** + * The {@link Indexed} annotation is used to index a {@link GemfirePersistentEntity} {@link GemfirePersistentProperty}, + * which creates a GemFire/Geode {@link Index} on a GemFire/Geode {@link org.apache.geode.cache.Region}. + * + * @author John Blum + * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.data.gemfire.IndexType + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.Index + * @since 1.9.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@SuppressWarnings({ "unused" }) +public @interface Indexed { + + /** + * Name of the Index. + */ + @AliasFor(attribute = "name") + String value() default ""; + + /** + * Name of the Index. + */ + @AliasFor(attribute = "value") + String name() default ""; + + /** + * Expression to index. + */ + String expression() default ""; + + /** + * The GemFire/Geode {@link org.apache.geode.cache.Region} on which the Index is created. + */ + String from() default ""; + + /** + * Type of Index to create. + * + * Defaults to {@link IndexType#HASH}. + */ + IndexType type() default IndexType.HASH; + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexesConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexesConfigurationUnitTests.java new file mode 100644 index 00000000..83cb0a7b --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexesConfigurationUnitTests.java @@ -0,0 +1,189 @@ +/* + * 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.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.query.QueryService; +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.data.gemfire.IndexType; +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.LocalRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.NonEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.ReplicateRegionEntity; + +/** + * Unit tests for the {@link EnableIndexes} and {@link IndexConfiguration} class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.annotation.EnableIndexes + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @since 1.9.0 + */ +public class EnableIndexesConfigurationUnitTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + if (applicationContext != null) { + applicationContext.close(); + } + } + + /* (non-Javadoc) */ + protected void assertIndex(Index index, String name, String expression, String from, IndexType indexType) { + assertThat(index).isNotNull(); + assertThat(index.getName()).isEqualTo(name); + assertThat(index.getIndexedExpression()).isEqualTo(expression); + assertThat(index.getFromClause()).isEqualTo(from); + assertThat(index.getType()).isEqualTo(indexType.getGemfireIndexType()); + } + + /* (non-Javadoc) */ + protected ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); + applicationContext.registerShutdownHook(); + return applicationContext; + } + + @Test + public void pesistentEntityIndexesCreatedSuccessfully() { + applicationContext = newApplicationContext(IndexedPersistentEntityConfiguration.class); + + Index customersIdIdx = applicationContext.getBean("CustomersIdKeyIdx", Index.class); + + assertIndex(customersIdIdx, "CustomersIdKeyIdx", "id", "Customers", IndexType.KEY); + + Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class); + + assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx", "first_name", "/LoyalCustomers", + IndexType.FUNCTIONAL); + + Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class); + + assertIndex(lastNameIdx, "LastNameIdx", "lastName", "Customers", IndexType.HASH); + } + + @Configuration + @SuppressWarnings("unused") + static class CacheConfiguration { + + @Bean + @SuppressWarnings("unchecked") + Cache gemfireCache() throws Exception { + Cache mockCache = mock(Cache.class); + QueryService mockQueryService = mock(QueryService.class); + RegionFactory mockRegionFactory = mock(RegionFactory.class); + + when(mockCache.getQueryService()).thenReturn(mockQueryService); + when(mockCache.createRegionFactory()).thenReturn(mockRegionFactory); + when(mockCache.createRegionFactory(any(RegionAttributes.class))).thenReturn(mockRegionFactory); + when(mockCache.createRegionFactory(any(RegionShortcut.class))).thenReturn(mockRegionFactory); + when(mockCache.createRegionFactory(anyString())).thenReturn(mockRegionFactory); + + when(mockQueryService.createHashIndex(anyString(), anyString(), anyString())) + .thenAnswer(new HashIndexAnswer()); + + when(mockQueryService.createIndex(anyString(), anyString(), anyString())) + .thenAnswer(new FunctionalIndexAnswer()); + + when(mockQueryService.createKeyIndex(anyString(), anyString(), anyString())) + .thenAnswer(new KeyIndexAnswer()); + + return mockCache; + } + } + + static abstract class AbstractIndexAnswer implements Answer { + + @Override + public Index answer(InvocationOnMock invocation) throws Throwable { + String name = invocation.getArgumentAt(0, String.class); + String expression = invocation.getArgumentAt(1, String.class); + String from = invocation.getArgumentAt(2, String.class); + + Index mockIndex = mock(Index.class, name); + + when(mockIndex.getName()).thenReturn(name); + when(mockIndex.getIndexedExpression()).thenReturn(expression); + when(mockIndex.getFromClause()).thenReturn(from); + when(mockIndex.getType()).thenReturn(getType().getGemfireIndexType()); + + return mockIndex; + } + + abstract IndexType getType(); + + } + + static class FunctionalIndexAnswer extends AbstractIndexAnswer { + + @Override + IndexType getType() { + return IndexType.FUNCTIONAL; + } + } + + static class HashIndexAnswer extends AbstractIndexAnswer { + + @Override + IndexType getType() { + return IndexType.HASH; + } + } + + static class KeyIndexAnswer extends AbstractIndexAnswer { + + @Override + IndexType getType() { + return IndexType.KEY; + } + } + + @EnableIndexes + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { + ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, + LocalRegionEntity.class, ReplicateRegionEntity.class })) + static class IndexedPersistentEntityConfiguration extends CacheConfiguration { + + } +} 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 index 6e939b4f..92cb0ab2 100644 --- 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 @@ -18,6 +18,8 @@ package org.springframework.data.gemfire.config.annotation.test.entities; import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.mapping.annotation.Indexed; import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; /** @@ -38,7 +40,10 @@ public class PartitionRegionEntity { @Id private Long id; + @Indexed(expression = "first_name", from = "/LoyalCustomers", type = IndexType.FUNCTIONAL) private String firstName; + + @Indexed(name = "LastNameIdx") private String lastName; }