/*
* Copyright 2011-2013 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;
import java.util.Collection;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionService;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexExistsException;
import org.apache.geode.cache.query.IndexInvalidException;
import org.apache.geode.cache.query.IndexNameConflictException;
import org.apache.geode.cache.query.IndexStatistics;
import org.apache.geode.cache.query.QueryService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Spring FactoryBean for easy declarative creation of GemFire Indexes.
*
* @author Costin Leau
* @author David Turanski
* @author John Blum
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.BeanNameAware
* @see org.springframework.beans.factory.FactoryBean
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.data.gemfire.IndexFactoryBean.IndexWrapper
* @see org.apache.geode.cache.RegionService
* @see org.apache.geode.cache.query.Index
* @see org.apache.geode.cache.query.QueryService
* @since 1.0.0
*/
public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, BeanNameAware, BeanFactoryAware {
private boolean define = false;
private boolean override = true;
private BeanFactory beanFactory;
private Index index;
private IndexType indexType;
private QueryService queryService;
private RegionService cache;
private String beanName;
private String expression;
private String from;
private String imports;
private String indexName;
private String name;
/**
* @inheritDoc
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "The GemFire Cache reference must not be null!");
queryService = lookupQueryService();
Assert.notNull(queryService, "QueryService is required to create an Index");
Assert.hasText(expression, "Index 'expression' is required");
Assert.hasText(from, "Index 'from clause' is required");
if (IndexType.isKey(indexType)) {
Assert.isNull(imports, "'imports' are not supported with a KEY Index");
}
indexName = (StringUtils.hasText(name) ? name : beanName);
Assert.hasText(indexName, "Index 'name' is required");
index = createIndex(queryService, indexName);
}
/* (non-Javadoc) */
QueryService doLookupQueryService() {
return (queryService != null ? queryService
: (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService() : cache.getQueryService()));
}
/* (non-Javadoc) */
QueryService lookupQueryService() {
if (getBeanFactory().containsBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)) {
return getBeanFactory().getBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE,
QueryService.class);
}
else {
return registerQueryServiceBean(doLookupQueryService());
}
}
/* (non-Javadoc) */
QueryService registerQueryServiceBean(QueryService queryService) {
if (isDefine()) {
((ConfigurableBeanFactory) getBeanFactory()).registerSingleton(
GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE, queryService);
}
return queryService;
}
/* (non-Javadoc) */
Index createIndex(QueryService queryService, String indexName) throws Exception {
Index existingIndex = getExistingIndex(queryService, indexName);
if (existingIndex != null) {
if (override) {
queryService.removeIndex(existingIndex);
}
else {
return existingIndex;
}
}
try {
if (IndexType.isKey(indexType)) {
return createKeyIndex(queryService, indexName, expression, from);
}
else if (IndexType.isHash(indexType)) {
return createHashIndex(queryService, indexName, expression, from, imports);
}
else {
return createFunctionalIndex(queryService, indexName, expression, from, imports);
}
}
catch (IndexExistsException e) {
throw new GemfireIndexException(String.format(
"An Index with a different name having the same definition as this Index (%1$s) already exists",
indexName), e);
}
catch (IndexNameConflictException e) {
// NOTE technically, the only way for an IndexNameConflictException to be thrown is if
// queryService.remove(existingIndex) above silently fails, since otherwise, when override is 'false',
// the existingIndex is already being returned. Given this state of affairs, an Index with the provided
// name is unresolvable based on what the user intended to happen, so just rethrow an Exception.
throw new GemfireIndexException(String.format(
"Failed to remove the existing Index%1$sbefore re-creating Index with name (%2$s)",
(override ? " on override " : " "), indexName), e);
}
catch (Exception e) {
if (existingIndex != null) {
Collection<Index> indexes = queryService.getIndexes();
if (CollectionUtils.isEmpty(indexes) || !indexes.contains(existingIndex)) {
queryService.getIndexes().add(existingIndex);
return existingIndex;
}
}
throw e;
}
}
/* (non-Javadoc) */
Index createKeyIndex(QueryService queryService, String indexName, String expression, String from) throws Exception {
if (isDefine()) {
queryService.defineKeyIndex(indexName, expression, from);
return new IndexWrapper(queryService, indexName);
}
else {
return queryService.createKeyIndex(indexName, expression, from);
}
}
/* (non-Javadoc) */
Index createHashIndex(QueryService queryService, String indexName, String expression, String from,
String imports) throws Exception {
boolean hasImports = StringUtils.hasText(imports);
if (isDefine()) {
if (hasImports) {
queryService.defineHashIndex(indexName, expression, from, imports);
}
else {
queryService.defineHashIndex(indexName, expression, from);
}
return new IndexWrapper(queryService, indexName);
}
else {
if (hasImports) {
return queryService.createHashIndex(indexName, expression, from, imports);
}
else {
return queryService.createHashIndex(indexName, expression, from);
}
}
}
/* (non-Javadoc) */
Index createFunctionalIndex(QueryService queryService, String indexName, String expression, String from,
String imports) throws Exception {
boolean hasImports = StringUtils.hasText(imports);
if (isDefine()) {
if (hasImports) {
queryService.defineIndex(indexName, expression, from , imports);
}
else {
queryService.defineIndex(indexName, expression, from);
}
return new IndexWrapper(queryService, indexName);
}
else {
if (hasImports) {
return queryService.createIndex(indexName, expression, from, imports);
}
else {
return queryService.createIndex(indexName, expression, from);
}
}
}
/* (non-Javadoc) */
Index getExistingIndex(QueryService queryService, String indexName) {
for (Index index : CollectionUtils.nullSafeCollection(queryService.getIndexes())) {
if (index.getName().equalsIgnoreCase(indexName)) {
return index;
}
}
return null;
}
/**
* @inheritDoc
*/
@Override
public Index getObject() {
index = (index != null ? index : getExistingIndex(queryService, indexName));
return index;
}
/**
* @inheritDoc
*/
@Override
public Class<?> getObjectType() {
return (index != null ? index.getClass() : Index.class);
}
/**
* @inheritDoc
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* Sets the underlying cache used for creating indexes.
*
* @param cache cache used for creating indexes.
*/
public void setCache(RegionService cache) {
this.cache = cache;
}
/**
* Sets the query service used for creating indexes.
*
* @param service query service used for creating indexes.
*/
public void setQueryService(QueryService service) {
this.queryService = service;
}
/* (non-Javadoc) */
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/* (non-Javadoc) */
protected BeanFactory getBeanFactory() {
Assert.state(beanFactory != null, "'beanFactory' was not properly initialized");
return beanFactory;
}
/* (non-Javadoc) */
@Override
public void setBeanName(String name) {
this.beanName = name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets a boolean condition to indicate whether the Index declared and defined by this FactoryBean will only be
* defined initially, or defined and created. If defined-only, the IndexFactoryBean will receive a callback at
* the end of the Spring container lifecycle to subsequently "create" all "defined-only" Indexes once, in a
* single operation.
*
* @param define a boolean value indicating the define or define/create status. If true, the Index declared
* by this FactoryBean will only be defined initially and subsequently created when this SmartLifecycle bean
* receives an appropriate callback from the Spring container; if false, the Index will be created immediately.
*/
public void setDefine(boolean define) {
this.define = define;
}
/* (non-Javadoc) */
protected boolean isDefine() {
return define;
}
/**
* @param expression the expression to set
*/
public void setExpression(String expression) {
this.expression = expression;
}
/**
* @param from the from to set
*/
public void setFrom(String from) {
this.from = from;
}
/**
* @param imports the imports to set
*/
public void setImports(String imports) {
this.imports = imports;
}
/**
* @param override the override to set
*/
public void setOverride(boolean override) {
this.override = override;
}
/**
* @param type the type to set
*/
public void setType(String type) {
setType(IndexType.valueOfIgnoreCase(type));
}
/**
* Sets the type of GemFire Index to create.
*
* @param indexType the IndexType enumerated value indicating the type of GemFire Index
* that will be created by this Spring FactoryBean.
* @see org.springframework.data.gemfire.IndexType
*/
public void setType(IndexType indexType) {
this.indexType = indexType;
}
/* (non-Javadoc) */
protected static final class IndexWrapper implements Index {
private Index index;
private final QueryService queryService;
private final String indexName;
protected IndexWrapper(QueryService queryService, String indexName) {
Assert.notNull(queryService, "QueryService must not be null");
Assert.hasText(indexName, "The name of the Index must be specified!");
this.queryService = queryService;
this.indexName = indexName;
}
protected synchronized Index getIndex() {
if (this.index == null) {
String localIndexName = getIndexName();
for (Index localIndex : getQueryService().getIndexes()) {
if (localIndex.getName().equals(localIndexName)) {
this.index = localIndex;
break;
}
}
if (this.index == null) {
throw new GemfireIndexException(new IndexInvalidException(String.format(
"index with name (%1$s) was not found", localIndexName)));
}
}
return index;
}
protected String getIndexName() {
Assert.state(StringUtils.hasText(indexName), "The Index 'name' was not properly initialized!");
return indexName;
}
protected QueryService getQueryService() {
return queryService;
}
@Override
public String getName() {
return getIndex().getName();
}
@Override
public String getCanonicalizedFromClause() {
return getIndex().getCanonicalizedFromClause();
}
@Override
public String getCanonicalizedIndexedExpression() {
return getIndex().getCanonicalizedIndexedExpression();
}
@Override
public String getCanonicalizedProjectionAttributes() {
return getIndex().getCanonicalizedProjectionAttributes();
}
@Override
public String getFromClause() {
return getIndex().getFromClause();
}
@Override
public String getIndexedExpression() {
return getIndex().getIndexedExpression();
}
@Override
public String getProjectionAttributes() {
return getIndex().getProjectionAttributes();
}
@Override
public Region<?, ?> getRegion() {
return getIndex().getRegion();
}
@Override
public IndexStatistics getStatistics() {
return getIndex().getStatistics();
}
@Override
@SuppressWarnings("deprecation")
public org.apache.geode.cache.query.IndexType getType() {
return getIndex().getType();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof IndexWrapper || obj instanceof Index)) {
return false;
}
if (obj instanceof IndexWrapper) {
return (getIndexName().equals(((IndexWrapper) obj).getIndexName()));
}
return getIndex().equals(obj);
}
@Override
public int hashCode() {
int hashValue = 37;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getIndexName());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(index);
return hashValue;
}
@Override
public String toString() {
return (index != null ? String.valueOf(index) : getIndexName());
}
}
}