/* * 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 java.util.Optional; import org.apache.geode.cache.Region; import org.apache.geode.cache.lucene.LuceneIndex; import org.apache.geode.cache.query.Index; 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.gemfire.mapping.annotation.LuceneIndexed; import org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean; 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 EnableIndexing} annotation * to dynamically create GemFire/Geode {@link org.apache.geode.cache.Region} {@link Index 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.search.lucene.LuceneIndexFactoryBean * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty * @see org.springframework.data.gemfire.mapping.annotation.Indexed * @see org.apache.geode.cache.Region * @see org.apache.geode.cache.lucene.LuceneIndex * @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 EnableIndexing * @see java.lang.annotation.Annotation * @see java.lang.Class */ protected Class<? extends Annotation> getEnableIndexingAnnotationType() { return EnableIndexing.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 #getEnableIndexingAnnotationType() * @see java.lang.Class#getName() */ protected String getEnableIndexingAnnotationTypeName() { return getEnableIndexingAnnotationType().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 #getEnableIndexingAnnotationType() * @see java.lang.Class#getSimpleName() */ @SuppressWarnings("unused") protected String getEnableIndexingAnnotationTypeSimpleName() { return getEnableIndexingAnnotationType().getSimpleName(); } /** * @inheritDoc */ @Override protected GemfirePersistentEntity<?> postProcess(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, GemfirePersistentEntity<?> persistentEntity) { GemfirePersistentEntity<?> localPersistentEntity = super.postProcess(importingClassMetadata, registry, persistentEntity); if (isAnnotationPresent(importingClassMetadata, getEnableIndexingAnnotationTypeName())) { AnnotationAttributes enableIndexingAttributes = getAnnotationAttributes(importingClassMetadata, getEnableIndexingAnnotationTypeName()); localPersistentEntity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> { persistentProperty.findAnnotation(Id.class).ifPresent(idAnnotation -> registerIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity, persistentProperty, IndexType.KEY, idAnnotation, registry)); persistentProperty.findAnnotation(Indexed.class).ifPresent(indexedAnnotation -> registerIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity, persistentProperty, indexedAnnotation.type(), indexedAnnotation, registry)); persistentProperty.findAnnotation(LuceneIndexed.class).ifPresent( luceneIndexAnnotation -> registerLuceneIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity, persistentProperty, luceneIndexAnnotation, 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 enableIndexingAttributes {@link AnnotationAttributes} containing meta-data * for the {@link EnableIndexing} 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 */ protected void registerIndexBeanDefinition(AnnotationAttributes enableIndexingAttributes, GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, IndexType indexType, Annotation indexAnnotation, BeanDefinitionRegistry registry) { Optional.ofNullable(indexAnnotation).ifPresent(localIndexAnnotation -> { AnnotationAttributes indexedAttributes = getAnnotationAttributes(localIndexAnnotation); BeanDefinitionBuilder indexFactoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(IndexFactoryBean.class); String indexName = resolveName(persistentEntity, persistentProperty, indexedAttributes, indexType); indexFactoryBeanBuilder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); indexFactoryBeanBuilder.addPropertyValue("define", resolveDefine(enableIndexingAttributes)); indexFactoryBeanBuilder.addPropertyValue("expression", resolveExpression(persistentEntity, persistentProperty, indexedAttributes)); indexFactoryBeanBuilder.addPropertyValue("from", resolveFrom(persistentEntity, persistentProperty, indexedAttributes)); indexFactoryBeanBuilder.addPropertyValue("name", indexName); indexFactoryBeanBuilder.addPropertyValue("override", resolveOverride(persistentEntity, persistentProperty, indexedAttributes)); indexFactoryBeanBuilder.addPropertyValue("type", resolveType(persistentEntity, persistentProperty, indexedAttributes, indexType).toString()); registry.registerBeanDefinition(indexName, indexFactoryBeanBuilder.getBeanDefinition()); }); } /** * Registers a {@link LuceneIndex} for the {@link GemfirePersistentProperty} on the {@link GemfirePersistentEntity} * using the {@link Annotation} meta-data to define the Index. * * @param enableIndexingAttributes {@link AnnotationAttributes} containing meta-data * for the {@link EnableIndexing} annotation. * @param persistentEntity {@link GemfirePersistentEntity} containing the {@link GemfirePersistentProperty} * to be indexed. * @param persistentProperty {@link GemfirePersistentProperty} for which the {@link LuceneIndex} will be created. * @param luceneIndexAnnotation {@link LuceneIndexed} {@link Annotation}. * @param registry {@link BeanDefinitionRegistry} used to register the {@link LuceneIndex} 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.mapping.GemfirePersistentEntity * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty * @see org.springframework.data.gemfire.mapping.annotation.LuceneIndexed */ @SuppressWarnings("unused") protected void registerLuceneIndexBeanDefinition(AnnotationAttributes enableIndexingAttributes, GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, Annotation luceneIndexAnnotation, BeanDefinitionRegistry registry) { Optional.ofNullable(luceneIndexAnnotation).ifPresent(localLuceneIndexAnnotation -> { AnnotationAttributes luceneIndexAttributes = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(localLuceneIndexAnnotation)); BeanDefinitionBuilder luceneIndexFactoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(LuceneIndexFactoryBean.class); String indexName = luceneIndexAttributes.getString("name"); boolean destroy = (luceneIndexAttributes.containsKey("destroy") && luceneIndexAttributes.getBoolean("destroy")); luceneIndexFactoryBeanBuilder.addPropertyValue("destroy", destroy); luceneIndexFactoryBeanBuilder.addPropertyValue("fields", persistentProperty.getName()); luceneIndexFactoryBeanBuilder.addPropertyValue("indexName", indexName); luceneIndexFactoryBeanBuilder.addPropertyValue("regionPath", persistentEntity.getRegionName()); registry.registerBeanDefinition(indexName, luceneIndexFactoryBeanBuilder.getBeanDefinition()); }); } /* (non-Javadoc) */ private boolean resolveDefine(AnnotationAttributes enableIndexingAttributes) { return (enableIndexingAttributes.containsKey("define") && enableIndexingAttributes.getBoolean("define")); } /* (non-Javadoc) */ @SuppressWarnings("unused") private String resolveExpression(GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes) { String expression = (indexedAttributes.containsKey("expression") ? indexedAttributes.getString("expression") : null); return (StringUtils.hasText(expression) ? expression : persistentProperty.getName()); } /* (non-Javadoc) */ @SuppressWarnings("unused") private String resolveFrom(GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes) { String from = (indexedAttributes.containsKey("from") ? indexedAttributes.getString("from") : null); return (StringUtils.hasText(from) ? from : persistentEntity.getRegionName()); } /* (non-Javadoc) */ private String resolveName(GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes, IndexType indexType) { String indexName = (indexedAttributes.containsKey("name") ? indexedAttributes.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 boolean resolveOverride(GemfirePersistentEntity persistentEntity, GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes) { return (indexedAttributes.containsKey("override") && indexedAttributes.getBoolean("override")); } /* (non-Javadoc) */ @SuppressWarnings("unused") private IndexType resolveType(GemfirePersistentEntity<?> persistentEntity, GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes, IndexType indexType) { IndexType resolvedIndexType = (indexedAttributes.containsKey("type") ? indexedAttributes.getEnum("type") : null); return Optional.ofNullable(resolvedIndexType).orElse(indexType); } }