/* * 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.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.apache.geode.cache.Cache; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.RegionAttributes; import org.apache.geode.cache.RegionFactory; import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.lucene.LuceneIndex; import org.apache.geode.cache.lucene.LuceneService; 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.Matchers; 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.IndexFactoryBean; 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 EnableIndexing} and {@link IndexConfiguration} class. * * @author John Blum * @see org.junit.Test * @see org.mockito.Mockito * @see EnableIndexing * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration * @since 1.9.0 */ public class EnableIndexingConfigurationUnitTests { private static final Set<Index> indexes = new HashSet<>(); private ConfigurableApplicationContext applicationContext; @After public void tearDown() { Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); indexes.clear(); } /* (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 void assertLuceneIndex(LuceneIndex index, String name, String regionPath, String... fields) { assertThat(index).isNotNull(); assertThat(index.getName()).isEqualTo(name); assertThat(index.getRegionPath()).isEqualTo(regionPath); assertThat(index.getFieldNames()).contains(fields); assertThat(index.getFieldNames()).hasSize(fields.length); } /* (non-Javadoc) */ protected ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) { ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); applicationContext.registerShutdownHook(); return applicationContext; } @Test public void persistentEntityIndexesCreatedSuccessfully() { applicationContext = newApplicationContext(IndexedPersistentEntityConfiguration.class); assertLuceneIndexes(applicationContext); assertOqlIndexes(applicationContext); } private void assertLuceneIndexes(ConfigurableApplicationContext applicationContext) { LuceneIndex luceneIndex = applicationContext.getBean("TitleLuceneIdx", LuceneIndex.class); assertLuceneIndex(luceneIndex, "TitleLuceneIdx", "Customers", "title"); } private void assertOqlIndexes(ConfigurableApplicationContext applicationContext) { 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", "surname", "Customers", IndexType.HASH); } @Test public void persistentEntityIndexesWillNotBeCreated() { applicationContext = newApplicationContext(NoIndexesCreatedForIndexedPersistentEntityConfiguration.class); Map<String, Index> indexes = applicationContext.getBeansOfType(Index.class); assertThat(indexes).isNotNull(); assertThat(indexes.isEmpty()).isTrue(); } @Test public void indexAnnotatedEntityPropertyDoesNotOverrideIndexBeanDefinition() { applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration.class); Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class); assertIndex(lastNameIdx, "LastNameIdx", "last_name", "/People", IndexType.HASH); } @Test public void indexAnnotatedEntityPropertyOverridesIndexBeanDefinition() { applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration.class); Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class); assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx", "first_name", "/LoyalCustomers", IndexType.FUNCTIONAL); } @Configuration @SuppressWarnings("unused") static class GemFireConfiguration { @Bean @SuppressWarnings("unchecked") Cache gemfireCache() throws Exception { return mockQueryService(mockRegionFactory(mock(Cache.class, "MockGemFireCache"))); } Cache mockQueryService(Cache mockCache) throws Exception { QueryService mockQueryService = mock(QueryService.class); when(mockCache.getQueryService()).thenReturn(mockQueryService); when(mockQueryService.getIndexes()).thenReturn(indexes); 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; } @SuppressWarnings("unchecked") Cache mockRegionFactory(Cache mockCache) { RegionFactory mockRegionFactory = mock(RegionFactory.class); 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); return mockCache; } @Bean LuceneService luceneService() { LuceneService mockLuceneService = mock(LuceneService.class); doAnswer(invocation -> { LuceneIndex mockLuceneIndex = mock(LuceneIndex.class); String indexName = invocation.getArgument(0); String regionPath = invocation.getArgument(1); when(mockLuceneIndex.getName()).thenReturn(indexName); when(mockLuceneIndex.getRegionPath()).thenReturn(regionPath); when(mockLuceneIndex.getFieldNames()).thenReturn(resolveFieldNames(invocation)); when(mockLuceneService.getIndex(eq(indexName), eq(regionPath))).thenReturn(mockLuceneIndex); return mockLuceneIndex; }).when(mockLuceneService).createIndex(anyString(), anyString(), Matchers.<String[]>anyVararg()); return mockLuceneService; } @SuppressWarnings("all") String[] resolveFieldNames(InvocationOnMock invocation) { String[] fieldNames = new String[invocation.getArguments().length - 2]; System.arraycopy(invocation.getArguments(), 2, fieldNames, 0, fieldNames.length); return fieldNames; } } static abstract class AbstractIndexAnswer implements Answer<Index> { @Override public Index answer(InvocationOnMock invocation) throws Throwable { String name = invocation.getArgument(0); String expression = invocation.getArgument(1); String from = invocation.getArgument(2); 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()); indexes.add(mockIndex); 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; } } @EnableIndexing @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 GemFireConfiguration { } @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, LocalRegionEntity.class, ReplicateRegionEntity.class })) static class NoIndexesCreatedForIndexedPersistentEntityConfiguration extends GemFireConfiguration { } @EnableIndexing @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, LocalRegionEntity.class, ReplicateRegionEntity.class })) static class IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration extends GemFireConfiguration { @Bean @SuppressWarnings("unused") IndexFactoryBean lastNameIndex(GemFireCache gemfireCache) { IndexFactoryBean lastNameIndex = new IndexFactoryBean(); lastNameIndex.setCache(gemfireCache); lastNameIndex.setExpression("last_name"); lastNameIndex.setFrom("/People"); lastNameIndex.setName("LastNameIdx"); lastNameIndex.setType(IndexType.HASH); return lastNameIndex; } } @EnableIndexing @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, LocalRegionEntity.class, ReplicateRegionEntity.class })) static class IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration extends GemFireConfiguration { @Bean @SuppressWarnings("unused") IndexFactoryBean firstNameIndex(GemFireCache gemfireCache) { IndexFactoryBean firstNameIndex = new IndexFactoryBean(); firstNameIndex.setCache(gemfireCache); firstNameIndex.setExpression("given_name"); firstNameIndex.setFrom("/ProspectiveCustomers"); firstNameIndex.setName("CustomersFirstNameFunctionalIdx"); firstNameIndex.setType(IndexType.HASH); return firstNameIndex; } } }