SGF-370 - Add multi-Index definition and creation support.

This commit is contained in:
John Blum
2015-05-08 17:01:11 -07:00
parent 4c7cc4fe8d
commit a33a121365
6 changed files with 825 additions and 109 deletions

View File

@@ -25,10 +25,13 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionService;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.query.Index;
import com.gemstone.gemfire.cache.query.IndexExistsException;
import com.gemstone.gemfire.cache.query.IndexInvalidException;
import com.gemstone.gemfire.cache.query.IndexStatistics;
import com.gemstone.gemfire.cache.query.QueryService;
/**
@@ -47,6 +50,7 @@ import com.gemstone.gemfire.cache.query.QueryService;
*/
public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, BeanNameAware {
private boolean define = false;
private boolean override = true;
private Index index;
@@ -61,6 +65,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
private String expression;
private String from;
private String imports;
private String indexName;
private String name;
public void afterPropertiesSet() throws Exception {
@@ -68,15 +73,15 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
queryService = lookupQueryService();
Assert.notNull(queryService, "A QueryService is required for Index creation!");
Assert.hasText(expression, "The Index 'expression' is required!");
Assert.hasText(from, "The Index 'from' clause (a Region's full-path) is required!");
Assert.notNull(queryService, "QueryService is required to create an Index!");
Assert.hasText(expression, "Index 'expression' is required!");
Assert.hasText(from, "Index 'from' clause (a Region's full-path) is required!");
if (IndexType.isKey(indexType)) {
Assert.isNull(imports, "The 'imports' property is not supported for a Key Index.");
}
String indexName = (StringUtils.hasText(name) ? name : beanName);
indexName = (StringUtils.hasText(name) ? name : beanName);
Assert.hasText(indexName, "The Index bean id or name is required!");
@@ -103,7 +108,13 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
try {
if (IndexType.isKey(indexType)) {
return queryService.createKeyIndex(indexName, expression, from);
if (isDefine()) {
queryService.defineKeyIndex(indexName, expression, from);
return new IndexWrapper(queryService, indexName);
}
else {
return queryService.createKeyIndex(indexName, expression, from);
}
}
else if (IndexType.isHash(indexType)) {
return createHashIndex(queryService, indexName, expression, from, imports);
@@ -132,21 +143,45 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
Index createFunctionalIndex(QueryService queryService, String indexName, String expression, String from,
String imports) throws Exception {
if (StringUtils.hasText(imports)) {
return queryService.createIndex(indexName, expression, from, imports);
if (isDefine()) {
queryService.defineIndex(indexName, expression, from , imports);
}
else {
return queryService.createIndex(indexName, expression, from, imports);
}
}
else {
return queryService.createIndex(indexName, expression, from);
if (isDefine()) {
queryService.defineIndex(indexName, expression, from);
}
else {
return queryService.createIndex(indexName, expression, from);
}
}
return new IndexWrapper(queryService, indexName);
}
Index createHashIndex(QueryService queryService, String indexName, String expression, String from,
String imports) throws Exception {
if (StringUtils.hasText(imports)) {
return queryService.createHashIndex(indexName, expression, from, imports);
if (isDefine()) {
queryService.defineHashIndex(indexName, expression, from, imports);
}
else {
return queryService.createHashIndex(indexName, expression, from, imports);
}
}
else {
return queryService.createHashIndex(indexName, expression, from);
if (isDefine()) {
queryService.defineHashIndex(indexName, expression, from);
}
else {
return queryService.createHashIndex(indexName, expression, from);
}
}
return new IndexWrapper(queryService, indexName);
}
Index getExistingIndex(QueryService queryService, String indexName) {
@@ -160,6 +195,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
}
public Index getObject() {
index = (index != null ? index : getExistingIndex(queryService, indexName));
return index;
}
@@ -200,6 +236,25 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
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
*/
@@ -221,6 +276,13 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
this.imports = imports;
}
/**
* @param override the override to set
*/
public void setOverride(boolean override) {
this.override = override;
}
/**
* @param type the type to set
*/
@@ -239,11 +301,103 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean<Index>, B
this.indexType = indexType;
}
/**
* @param override the override to set
*/
public void setOverride(boolean override) {
this.override = override;
/* (non-Javadoc) */
protected static final class IndexWrapper implements Index {
private Index index;
private final QueryService queryService;
private final String indexName;
protected IndexWrapper(final QueryService queryService, final 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() {
Assert.state(queryService != null, "The QueryService was not properly initialized!");
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 com.gemstone.gemfire.cache.query.IndexType getType() {
return getIndex().getType();
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2010-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.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.query.QueryService;
/**
* The CreateDefinedIndexesApplicationListener class is a Spring ApplicationListener used to create
* all the GemFire Cache Region Indexes "defined" using the QueryService.defineXXXX(..) methods.
*
* @author John Blum
* @see org.springframework.context.ApplicationListener
* @see org.springframework.context.event.ContextRefreshedEvent
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.query.QueryService
* @since 1.7.0
*/
public class CreateDefinedIndexesApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
protected final Log logger = initLogger();
/**
* Attempts to create all defined Indexes using the QueryService, defineXXXX(..) API once
* the Spring ApplicationContext has been refreshed.
*
* @param event the ContextRefreshedEvent fired when the Spring ApplicationContext gets refreshed.
* @see #getCache(org.springframework.context.event.ContextRefreshedEvent)
* @see org.springframework.context.event.ContextRefreshedEvent
* @see com.gemstone.gemfire.cache.Cache#getQueryService()
* @see com.gemstone.gemfire.cache.client.ClientCache#getLocalQueryService()
* @see com.gemstone.gemfire.cache.query.QueryService#createDefinedIndexes()
*/
@Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
GemFireCache cache = getCache(event);
QueryService queryService = (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService()
: cache.getQueryService());
queryService.createDefinedIndexes();
}
catch (Exception ignore) {
logger.warn(String.format("unable to create defined Indexes (if any): %1$s", ignore.getMessage()));
}
}
/* (non-Javadoc) */
Log initLogger() {
return LogFactory.getLog(getClass());
}
/* (non-Javadoc) */
protected GemFireCache getCache(final ContextRefreshedEvent event) {
try {
return event.getApplicationContext().getBean(GemFireCache.class);
}
catch (BeansException e) {
return CacheFactory.getAnyInstance();
}
}
}

View File

@@ -16,7 +16,12 @@
package org.springframework.data.gemfire.config;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.gemfire.IndexFactoryBean;
@@ -34,6 +39,20 @@ import org.w3c.dom.Element;
*/
class IndexParser extends AbstractSimpleBeanDefinitionParser {
private static final AtomicBoolean registered = new AtomicBoolean(false);
protected static void registerCreateDefinedIndexesApplicationListener(ParserContext parserContext) {
if (registered.compareAndSet(false, true)) {
AbstractBeanDefinition createDefinedIndexesApplicationListener = BeanDefinitionBuilder
.genericBeanDefinition(CreateDefinedIndexesApplicationListener.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.getBeanDefinition();
BeanDefinitionReaderUtils.registerWithGeneratedName(createDefinedIndexesApplicationListener,
parserContext.getRegistry());
}
}
protected Class<?> getBeanClass(Element element) {
return IndexFactoryBean.class;
}
@@ -45,6 +64,7 @@ class IndexParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
registerCreateDefinedIndexesApplicationListener(parserContext);
ParsingUtils.setPropertyReference(element, builder, "cache-ref", "cache");
super.doParse(element, parserContext, builder);
}