diff --git a/src/main/asciidoc/reference/indexing.adoc b/src/main/asciidoc/reference/indexing.adoc new file mode 100644 index 00000000..9476782c --- /dev/null +++ b/src/main/asciidoc/reference/indexing.adoc @@ -0,0 +1,238 @@ +[[bootstrap:indexing]] += Configuring an Index + +Pivotal GemFire allows Indexes (or Indices) to be created on Region data to improve the performance of OQL queries. + +In _Spring Data GemFire_ (SDG), Indexes are declared with the `index` element: + +[source,xml] +---- + +---- + +In _Spring Data GemFire's_ XML schema (a.k.a. SDG namespace), `Index` bean declarations are not bound to a _Region_, +unlike GemFire's native `cache.xml`. Rather, they are top-level elements just like `<gfe:cache>`. This allows +a developer to declare any number of Indexes on any _Region_ whether they were just created or already exist, +a significant improvement over GemFire's native `cache.xml` format. + +An `Index` must have a name. A developer may give the `Index` an explicit name using the `name` attribute, +otherwise the _bean name_ (i.e. value of the `id` attribute) of the `Index` bean definition is used as +the `Index` name. + +The `expression` and `from` clause form the main components of an `Index`, identifying the data to index +(i.e. the _Region_ identified in the `from` clause) along with what criteria (i.e. `expression`) is used +to index the data. The `expression` should be based on what application domain object fields are used +in the predicate of application-defined OQL queries used to query and lookup the objects stored +in the _Region_. + +For example, if I have a `Customer` that has a `lastName` property... + +[source,java] +---- +@Region("Customers") +class Customer { + + @Id + Long id; + + String lastName; + String firstName; + + ... +} +---- + +And, I also have an application defined SD[G] _Repository_ to query for `Customers`... + +[source,java] +---- +interface CustomerRepository extends GemfireRepository { + + Customer findByLastName(String lastName); + + ... +} +---- + +Then, the SD[G] _Repository_ finder/query method would result in the following OQL statement being executed... + +[source,java] +---- +SELECT * FROM /Customers c WHERE c.lastName = '$1' +---- + +Therefore, I might want to create an `Index` like so... + +[source,xml] +---- + +---- + +The `from` clause must refer to a valid, existing _Region_ and is how an `Index` gets applied to a _Region_. +This is *not* _Sprig Data GemFire_ specific; this is a feature of Pivotal GemFire. + +The `Index` `type` maybe 1 of 3 enumerated values defined by _Spring Data GemFire's_ +http://docs.spring.io/spring-data-gemfire/docs/current/api/org/springframework/data/gemfire/IndexType.html[IndexType] +enumeration: `FUNCTIONAL`, `HASH` and `PRIMARY_KEY`. + +Each of the enumerated values correspond to one of the http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html[QueryService] +`create[|Key|Hash]Index` methods invoked when the actual `Index` is to be created (or "defined"; more on "defining" +Indexes below). For instance, if the `IndexType` is `PRIMARY_KEY`, then the +http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#createKeyIndex-java.lang.String-java.lang.String-java.lang.String-[QueryService.createKeyIndex(..)] +is invoked to create a `KEY` `Index`. + +The default is `FUNCTIONAL` and results in one of the `QueryService.createIndex(..)` methods +being invoked. + +See the _Spring Data GemFire_ XML schema for a full set of options. + +For more information on Indexing in Pivotal GemFire, see http://gemfire90.docs.pivotal.io/geode/developing/query_index/query_index.html[Working with Indexes] +in Pivotal GemFire's User Guide. + +== Defining Indexes + +In addition to creating Indexes upfront as `Index` bean definitions are processed by _Spring Data GemFire_ +on _Spring_ container initialization, you may also *define* all of your application Indexes prior to creating +them by using the `define` attribute, like so... + +[source,xml] +---- + +---- + +When `define` is set to `true` (defaults to `false`), this will not actually create the `Index` right then and there. +All "defined" Indexes are created all at once, when the _Spring_ `ApplicationContext` is "refreshed", or, that is, +when a `ContextRefreshedEvent` is published by the _Spring_ container. _Spring Data GemFire_ registers itself as +an `ApplicationListener` listening for the `ContextRefreshedEvent`. When fired, _Spring Data GemFire_ will call +http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#createDefinedIndexes--[QueryService.createDefinedIndexes()]. + +Defining Indexes and creating them all at once helps promote speed and efficiency when creating Indexes. + +See http://gemfire90.docs.pivotal.io/geode/developing/query_index/create_multiple_indexes.html[Creating Multiple Indexes at Once] +for more details. + +== `IgnoreIfExists` and `Override` + +Two _Spring Data GemFire_ `Index` configuration options warrant special mention here: `ignoreIfExists` and `override`. + +These options correspond to the `ignore-if-exists` and `override` attributes on the `<gfe:index>` element +in _Spring Data GemFire's_ XML schema, respectively. + +WARNING: Make sure you absolutely understand what you are doing before using either of these options. These options can +affect the performance and/or resources (e.g. memory) consumed by your application at runtime. As such, both of +these options are disabled (i.e. set to `false`) in SDG by default. + +NOTE: These options are only available in _Spring Data GemFire_ and exist to workaround known limitations +with Pivotal GemFire; there are no equivalent options or functionality available in GemFire itself. + +Each option significantly differs in behavior and entirely depends on the type of GemFire `Index` _Exception_ thrown. +This also means that neither option has any effect if a GemFire Index-type _Exception_ is *not* thrown. These options +are meant to specifically handle GemFire `IndexExistsExceptions` and `IndexNameConflictExceptions`, which can occur +for various, sometimes obscure reasons. But, in general... + +* An http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/IndexExistsException.html[IndexExistsException] +is thrown when there exists another `Index` with the same definition but different name when attempting to +create an `Index`. + +* An http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/IndexNameConflictException.html[IndexNameConflictException] +is thrown when there exists another `Index` with the same name but possibly different definition when attempting to +create an `Index`. + +_Spring Data GemFire's_ default behavior is to *_fail-fast_*, always! So, neither `Index` _Exception_ will be "handled" +by default; these `Index` _Exceptions_ are simply wrapped in a SDG `GemfireIndexException` and rethrown. If you wish +for _Spring Data GemFire_ to handle them for you, then you can set either of these `Index` bean definition options. + +`IgnoreIfExists` always takes *precedence* over `Override`, primarily because it uses less resources given it returns +the "existing" `Index` in both exceptional cases. + +=== `IgnoreIfExists` Behavior + +When an `IndexExistsException` is thrown and `ignoreIfExists` is set to `true` (or `<gfe:index ignore-if-exists="true">`), +then the `Index` that would have been created by this `Index` bean definition / declaration will be "*ignored*", +and the "existing" `Index` will be returned. + +There is very little consequence in returning the "existing" `Index` since the `Index` "definition" is the same, +as deemed by GemFire itself, *not* SDG. + +However, this also means that *no* `Index` with the "`name`" specified in your `Index` bean definition / declaration +will "actually" exist from GemFire's perspective either (i.e. with +http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes--[QueryService.getIndexes()]). +Therefore, you should be careful when writing OQL query statements that use _Query Hints_, especially _Hints_ that refer +to the application `Index` being "*ignored*". Those _Query Hints_ will need to be changed. + +Now, when an `IndexNameConflictException` is thrown and `ignoreIfExists` is set to `true` (or `<gfe:index ignore-if-exists="true">`), +then the `Index` that would have been created by this `Index` bean definition / declaration will also be "*ignored*", +and the "existing" Index will be returned, just like when an `IndexExistsException` is thrown. + +However, there is more risk in returning the "existing" `Index` and "*ignoring*" the application's definition +of the `Index` when an `IndexNameConflictException` is thrown since, for a `IndexNameConflictException`, while the "names" +of the conflicting Indexes are the same, the "definitions" could very well be different! This obviously could have +implications for OQL queries specific to the application, where you would presume the Indexes were defined specifically +with the application data access patterns and queries in mind. However, if like named Indexes differ in definition, +this might not be the case. So, make sure you verify. + +NOTE: SDG makes a best effort to inform the user when the `Index` being ignored is significantly different +in its definition from the "existing" `Index`. However, in order for SDG to accomplish this, it must be able to "find" +the existing `Index`, which is looked up using the GemFire API (the only means available). + + +=== `Override` Behavior + +When an `IndexExistsException` is thrown and `override` is set to `true` (or `<gfe:index override="true">`), then +the `Index` is effectively "_renamed_". Remember, `IndexExistsExceptions` are thrown when multiple Indexes exist, +all having the same "definition" but different "names". + +_Spring Data GemFire_ can only accomplish this using GemFire's API, by first "_removing_" the "existing" `Index` +and then "_recreating_" the `Index` with the *new* name. It is possible that either the remove or subsequent +create invocation could fail. There is no way to execute both actions atomically and rollback this joint operation +if either fails. + +However, if it succeeds, then you have the same problem as before with the "_ignoreIfExists_" option. Any existing OQL +query statement using "_Query Hints_" referring to the old `Index` by name must be changed. + +Now, when an `IndexNameConflictException` is thrown and `override` is set to `true` (or `<gfe:index override="true">`), +then potentially the "existing" `Index` will be "_re-defined_". I say "potentially", because it is possible for the +"like-named", "existing" `Index` to have exactly the same definition and name when an `IndexNameConflictException` +is thrown. + +If so, SDG is *smart* and will just return the "existing" Index as is, even on `override`. There is no harm in this +since both the "name" and the "definition" are exactly the same. Of course, SDG can only accomplish this when +SDG is able to "find" the "existing" `Index`, which is dependent on GemFire's APIs. If it cannot find it, +nothing happens and a SDG `GemfireIndexException` is thrown wrapping the `IndexNameConflictException`. + +However, when the "definition" of the "existing" `Index` is different, then SDG will attempt to "_recreate_" the `Index` +using the `Index` definition specified in the `Index` bean definition /declaration. Make sure this is what you want +and make sure the `Index` definition matches your expectations and application requirements. + +=== How does `IndexNameConflictExceptions` actually happen? + +It is probably not all that uncommon for `IndexExistsExceptions` to be thrown, especially when +multiple configuration sources are used to configure GemFire (e.g. _Spring Data GemFire_, GemFire _Cluster Config_, +maybe GemFire native `cache.xml`, the API, etc, etc). You should definitely prefer 1 configuration method here +and stick with it. + +_However, when does an `IndexNameConflictException` get thrown?_ + +One particular case is an `Index` defined on a `PARTITION` _Region_ (PR). When an `Index` is defined on +a `PARTITION` _Region_ (e.g. "X"), GemFire distributes the `Index` definition (and name) to other peer members +in the cluster that also host the same `PARTITION` _Region_ (i.e. "X"). The distribution of this `Index` definition +to and subsequent creation of this `Index` by peer members on a "need-to-know" basis (i.e. those hosting the same PR) +is performed asynchronously. + +During this window of time, it is possible that these "pending" PR `Indexes` will not be identifiable by GemFire, +such as with a call to http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes--[QueryService.getIndexes()] +or with http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes-org.apache.geode.cache.Region-[QueryService.getIndexes(:Region)], +or even with http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndex-org.apache.geode.cache.Region-java.lang.String-[QueryService.getIndex(:Region, indexName:String)]. + +As such, the only way for SDG or other GemFire cache client applications (not involving _Spring_) to know for sure, +is to just attempt to create the `Index`. If it fails with either an `IndexNameConflictException`, +or even an `IndexExistsException`, then you will know. This is because the `QueryService` `Index` creation waits on +"pending" `Index` definitions, where as the other GemFire API calls do not. + +In any case, SDG makes a best effort and attempts to inform the user what has or is happening along with +the corrective action. Given all GemFire `QueryService.createIndex(..)` methods are synchronous, "blocking" operations, +then the state of GemFire should be consistent and accessible after either of these Index-type _Exceptions_ are thrown, +in which case, SDG can inspect the state of the system and respond/act accordingly, based on the user's +desired configuration. + +In all other cases, SDG will simply *_fail-fast_*! diff --git a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java index b275452d..bf477a0b 100644 --- a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java +++ b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java @@ -38,6 +38,14 @@ import com.gemstone.gemfire.cache.query.IndexNameConflictException; @SuppressWarnings({ "serial", "unused" }) public class GemfireIndexException extends DataIntegrityViolationException { + public GemfireIndexException(Exception cause) { + this(cause.getMessage(), cause); + } + + public GemfireIndexException(String message, Exception cause) { + super(message, cause); + } + public GemfireIndexException(IndexCreationException cause) { this(cause.getMessage(), cause); } @@ -77,5 +85,4 @@ public class GemfireIndexException extends DataIntegrityViolationException { public GemfireIndexException(String message, IndexNameConflictException cause) { super(message, cause); } - } diff --git a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java index a346dad8..f733d54c 100644 --- a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java @@ -16,53 +16,55 @@ package org.springframework.data.gemfire; -import java.util.Collection; - -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; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.SpringUtils.defaultIfNull; 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.IndexNameConflictException; import com.gemstone.gemfire.cache.query.IndexStatistics; import com.gemstone.gemfire.cache.query.QueryService; +import org.springframework.beans.factory.BeanFactory; +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.support.AbstractFactoryBeanSupport; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + /** - * Spring FactoryBean for easy declarative creation of GemFire Indexes. + * Spring {@link FactoryBean} for simple and 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.beans.factory.config.ConfigurableBeanFactory * @see org.springframework.data.gemfire.IndexFactoryBean.IndexWrapper + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @see com.gemstone.gemfire.cache.Region * @see com.gemstone.gemfire.cache.RegionService * @see com.gemstone.gemfire.cache.query.Index * @see com.gemstone.gemfire.cache.query.QueryService * @since 1.0.0 */ -public class IndexFactoryBean implements InitializingBean, FactoryBean, BeanNameAware, BeanFactoryAware { +public class IndexFactoryBean extends AbstractFactoryBeanSupport implements InitializingBean { + + public static final String BASIC_INDEX_DEFINITION = "{ expression = '%1$s', from = '%2$s', type = %3$s }"; + + public static final String DETAILED_INDEX_DEFINITION = + "{ name = '%1$s', expression = '%2$s', from = '%3$s', imports = '%4$s', type = %5$s }"; private boolean define = false; - private boolean override = true; - - private BeanFactory beanFactory; + private boolean ignoreIfExists = false; + private boolean override = false; private Index index; @@ -72,72 +74,126 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B private RegionService cache; - private String beanName; private String expression; private String from; private String imports; private String indexName; private String name; + @Override public void afterPropertiesSet() throws Exception { - Assert.notNull(cache, "The GemFire Cache reference must not be null!"); - queryService = lookupQueryService(); + this.cache = resolveCache(); + this.indexName = resolveIndexName(); + this.queryService = resolveQueryService(); - 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"); + assertIndexDefinitionConfiguration(); - if (IndexType.isKey(indexType)) { - Assert.isNull(imports, "'imports' are not supported with a KEY Index"); - } + this.index = createIndex(this.queryService, this.indexName); - indexName = (StringUtils.hasText(name) ? name : beanName); - - Assert.hasText(indexName, "Index 'name' is required"); - - index = createIndex(queryService, indexName); + registerAlias(getBeanName(), this.indexName); } /* (non-Javadoc) */ - QueryService doLookupQueryService() { - return (queryService != null ? queryService - : (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService() : cache.getQueryService())); + private void assertIndexDefinitionConfiguration() { + + Assert.hasText(this.expression, "Index expression is required"); + Assert.hasText(this.from, "Index from clause is required"); + + if (IndexType.isKey(this.indexType)) { + Assert.isTrue(StringUtils.isEmpty(this.imports), "Imports are not supported with a KEY Index"); + } + } + + /* (non-Javadoc) */ + RegionService resolveCache() { + + RegionService resolvedCache = (this.cache != null ? this.cache : GemfireUtils.resolveGemFireCache()); + + Assert.state(resolvedCache != null, "Cache is required"); + + return resolvedCache; + } + + /* (non-Javadoc) */ + String resolveIndexName() { + + String resolvedIndexName = (StringUtils.hasText(this.name) ? this.name : getBeanName()); + + Assert.hasText(resolvedIndexName, "Index name is required"); + + return resolvedIndexName; + } + + /* (non-Javadoc) */ + QueryService resolveQueryService() { + + QueryService resolvedQueryService = (this.queryService != null ? this.queryService : lookupQueryService()); + + Assert.state(resolvedQueryService != null, "QueryService is required to create an Index"); + + return resolvedQueryService; } /* (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()); - } + + String queryServiceBeanName = GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE; + + BeanFactory beanFactory = getBeanFactory(); + + return (beanFactory != null && beanFactory.containsBean(queryServiceBeanName) + ? beanFactory.getBean(queryServiceBeanName, QueryService.class) + : registerQueryServiceBean(queryServiceBeanName, doLookupQueryService())); } /* (non-Javadoc) */ - QueryService registerQueryServiceBean(QueryService queryService) { - if (isDefine()) { - ((ConfigurableBeanFactory) getBeanFactory()).registerSingleton( - GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE, queryService); + QueryService doLookupQueryService() { + + return (queryService != null ? queryService + : (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService() + : cache.getQueryService())); + } + + /* (non-Javadoc) */ + QueryService registerQueryServiceBean(String beanName, QueryService queryService) { + + BeanFactory beanFactory = getBeanFactory(); + + if (beanFactory instanceof ConfigurableBeanFactory) { + if (isDefine()) { + ((ConfigurableBeanFactory) beanFactory).registerSingleton(beanName, queryService); + } } return queryService; } /* (non-Javadoc) */ - Index createIndex(QueryService queryService, String indexName) throws Exception { - Index existingIndex = getExistingIndex(queryService, indexName); + void registerAlias(String beanName, String indexName) { - if (existingIndex != null) { - if (override) { - queryService.removeIndex(existingIndex); - } - else { - return existingIndex; + BeanFactory beanFactory = getBeanFactory(); + + if (beanFactory instanceof ConfigurableBeanFactory) { + if (beanName != null && !beanName.equals(indexName)) { + ((ConfigurableBeanFactory) beanFactory).registerAlias(beanName, indexName); } } + } + + /* (non-Javadoc) */ + Index createIndex(QueryService queryService, String indexName) throws Exception { + return createIndex(queryService, indexName, false); + } + + /* (non-Javadoc) */ + private Index createIndex(QueryService queryService, String indexName, boolean retryAttempted) throws Exception { + + IndexType indexType = this.indexType; + + String expression = this.expression; + String from = this.from; + String imports = this.imports; try { if (IndexType.isKey(indexType)) { @@ -150,32 +206,174 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B 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 indexes = queryService.getIndexes(); + catch (IndexExistsException cause) { - if (CollectionUtils.isEmpty(indexes) || !indexes.contains(existingIndex)) { - queryService.getIndexes().add(existingIndex); - return existingIndex; - } + // Same definition, different name + + Index existingIndexByDefinition = + tryToFindExistingIndexByDefinition(queryService, expression, from, indexType); + + if (isIgnorable(existingIndexByDefinition)) { + + logWarning("WARNING! You are choosing to ignore this Index [%1$s] and return the existing" + + " Index having the same basic definition [%2$s] but with a different name [%3$s];" + + " Make sure no OQL Query Hints refer to this Index by name [%1$s]", + indexName, toBasicIndexDefinition(), existingIndexByDefinition.getName()); + + return handleIgnore(existingIndexByDefinition); } + else if (isOverridable(existingIndexByDefinition, retryAttempted)) { - throw e; + // Log an informational warning to caution the user about using the override + logWarning("WARNING! You are attempting to 'override' an existing Index [%1$s]" + + " having the same basic definition [%2$s] as the Index that will be created" + + " by this IndexFactoryBean [%3$s]; 'Override' effectively 'renames' the existing" + + " Index [%1$s] by removing it then recreating it under the new name [%3$s] with" + + " the same definition; You should be careful to update any existing OQL Query" + + " Hints referring to the old Index name [%1$s] to now use the new name [%3$s]", + existingIndexByDefinition.getName(), toBasicIndexDefinition(), indexName); + + return handleOverride(existingIndexByDefinition, queryService, indexName); + } + else { + + String existingIndexName = (existingIndexByDefinition != null + ? existingIndexByDefinition.getName() : "unknown"); + + throw new GemfireIndexException(String.format( + "An Index with a different name [%1$s] having the same definition [%2$s] already exists;" + + " You may attempt to override the existing Index [%1$s] with the new name [%3$s]" + + " by setting the 'override' property to 'true'", + existingIndexName, toBasicIndexDefinition(), indexName), cause); + } } + catch (IndexNameConflictException cause) { + + // Same name; possibly different definition + + Index existingIndexByName = tryToFindExistingIndexByName(queryService, indexName); + + if (isIgnorable(existingIndexByName)) { + return handleIgnore(warnOnIndexDefinitionMismatch(existingIndexByName, indexName, "Returning")); + } + else if (isOverridable(existingIndexByName, retryAttempted)) { + return handleSmartOverride(warnOnIndexDefinitionMismatch(existingIndexByName, indexName, + "Overriding"), queryService, indexName); + } + else { + + String existingIndexDefinition = (existingIndexByName != null + ? String.format(DETAILED_INDEX_DEFINITION, existingIndexByName.getName(), + existingIndexByName.getIndexedExpression(), existingIndexByName.getFromClause(), "unknown", + existingIndexByName.getType()) + : "unknown"); + + throw new GemfireIndexException(String.format( + "An Index with the same name [%1$s] having possibly a different definition already exists;" + + " you may choose to ignore this Index definition [%2$s] and use the existing Index" + + " definition [%3$s] by setting the 'ignoreIfExists' property to 'true'", + indexName, toDetailedIndexDefinition(), existingIndexDefinition), cause); + } + } + catch (Exception cause) { + throw new GemfireIndexException(String.format("Failed to create Index [%s]", + toDetailedIndexDefinition()), cause); + } + } + + /* (non-Javadoc) */ + private boolean isIgnorable(Index existingIndex) { + return (isIgnoreIfExists() && existingIndex != null); + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + private boolean isIndexDefinitionMatch(Index index) { + + boolean result = false; + + if (index != null) { + + IndexType thisIndexType = defaultIfNull(this.indexType, IndexType.FUNCTIONAL); + + result = ObjectUtils.nullSafeEquals(index.getIndexedExpression(), this.expression) + && ObjectUtils.nullSafeEquals(index.getFromClause(), this.from) + && ObjectUtils.nullSafeEquals(IndexType.valueOf(index.getType()), thisIndexType); + + } + + return result; + } + + /* (non-Javadoc) */ + private boolean isNotIndexDefinitionMatch(Index index) { + return !isIndexDefinitionMatch(index); + } + + /* (non-Javadoc) */ + private boolean isOverridable(Index existingIndex, boolean retryAttempted) { + return (isOverride() && existingIndex != null && !retryAttempted); + } + + /* (non-Javadoc) */ + private Index warnOnIndexDefinitionMismatch(Index existingIndex, String indexName, String action) { + + if (isNotIndexDefinitionMatch(existingIndex)) { + + String existingIndexDefinition = String.format(BASIC_INDEX_DEFINITION, existingIndex.getIndexedExpression(), + existingIndex.getFromClause(), IndexType.valueOf(existingIndex.getType())); + + logWarning("WARNING! %1$s existing Index [%2$s] having a definition [%3$s]" + + " that does not match the Index defined [%4$s] by this IndexFactoryBean [%5$s]", + action, existingIndex.getName(), existingIndexDefinition, toBasicIndexDefinition(), indexName); + } + + return existingIndex; + } + + /* (non-Javadoc) */ + private Index handleIgnore(Index existingIndex) { + + registerAlias(getBeanName(), existingIndex.getName()); + + return existingIndex; + } + + /* (non-Javadoc) */ + private Index handleOverride(Index existingIndex, QueryService queryService, String indexName) { + try { + // No way to tell whether the QueryService.remove(:Index) was successful or not! o.O + // Should return a boolean! Does it throw an RuntimeException? Javadoc is useless; #sigh + queryService.removeIndex(existingIndex); + + return createIndex(queryService, indexName, true); + } + catch (Exception cause) { + throw new GemfireIndexException(String.format( + "Attempt to 'override' existing Index [%1$s] with the Index that would be created" + + " by this IndexFactoryBean [%2$s] failed; you should verify the state of" + + " your system and make sure the previously existing Index [%1$s] still exits", + existingIndex.getName(), indexName), cause); + } + } + + /* (non-Javadoc) */ + private Index handleSmartOverride(Index existingIndex, QueryService queryService, String indexName) { + + return (existingIndex.getName().equalsIgnoreCase(indexName) && isIndexDefinitionMatch(existingIndex) + ? existingIndex + : handleOverride(existingIndex, queryService, indexName)); + } + + /* (non-Javadoc) */ + String toBasicIndexDefinition() { + return String.format(BASIC_INDEX_DEFINITION, this.expression, this.from, this.indexType); + } + + /* (non-Javadoc) */ + String toDetailedIndexDefinition() { + return String.format(DETAILED_INDEX_DEFINITION, + this.name, this.expression, this.from, this.imports, this.indexType); } /* (non-Javadoc) */ @@ -190,8 +388,8 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B } /* (non-Javadoc) */ - Index createHashIndex(QueryService queryService, String indexName, String expression, String from, - String imports) throws Exception { + Index createHashIndex(QueryService queryService, String indexName, String expression, String from, String imports) + throws Exception { boolean hasImports = StringUtils.hasText(imports); @@ -242,8 +440,25 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B } /* (non-Javadoc) */ - Index getExistingIndex(QueryService queryService, String indexName) { - for (Index index : CollectionUtils.nullSafeCollection(queryService.getIndexes())) { + Index tryToFindExistingIndexByDefinition(QueryService queryService, + String expression, String fromClause, IndexType indexType) { + + for (Index index : nullSafeCollection(queryService.getIndexes())) { + if (index.getIndexedExpression().equalsIgnoreCase(expression) + && index.getFromClause().equalsIgnoreCase(fromClause) + && indexType.equals(IndexType.valueOf(index.getType()))) { + + return index; + } + } + + return null; + } + + /* (non-Javadoc) */ + Index tryToFindExistingIndexByName(QueryService queryService, String indexName) { + + for (Index index : nullSafeCollection(queryService.getIndexes())) { if (index.getName().equalsIgnoreCase(indexName)) { return index; } @@ -252,125 +467,258 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B return null; } - public Index getObject() { - index = (index != null ? index : getExistingIndex(queryService, indexName)); - return index; + /** + * Returns a reference to the {@link Index} created by this {@link IndexFactoryBean}. + * + * @return a reference to the {@link Index} created by this {@link IndexFactoryBean}. + * @see com.gemstone.gemfire.cache.query.Index + */ + public Index getIndex() { + return this.index; } + /* (non-Javadoc) */ + @Override + public Index getObject() { + + this.index = (this.index != null ? this.index + : tryToFindExistingIndexByName(resolveQueryService(), resolveIndexName())); + + return this.index; + } + + /* (non-Javadoc) */ + @Override public Class getObjectType() { + Index index = getIndex(); return (index != null ? index.getClass() : Index.class); } - public boolean isSingleton() { - return true; - } - /** - * Sets the underlying cache used for creating indexes. + * Sets a reference to the {@link RegionService}. * - * @param cache cache used for creating indexes. + * @param cache reference to the {@link RegionService}. + * @see com.gemstone.gemfire.cache.RegionService */ public void setCache(RegionService cache) { this.cache = cache; } /** - * Sets the query service used for creating indexes. + * Sets the name of the {@link Index}. * - * @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 + * @param name {@link String} containing the name given to the {@link Index}. */ 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. + * Sets the {@link QueryService} used to create the {@link Index}. * - * @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. + * @param service {@link QueryService} used to create the {@link Index}. + * @see com.gemstone.gemfire.cache.query.QueryService + */ + public void setQueryService(QueryService service) { + this.queryService = service; + } + + /** + * Sets a boolean condition to indicate whether the {@link Index} declared and defined by this + * {@link IndexFactoryBean} will only be defined initially, or defined and created. If defined-only, + * the {@link IndexFactoryBean} will receive a callback at the end of the Spring container lifecycle + * to subsequently "create" all "defined-only" {@link Index Indexes} once, in a single operation. + * + * @param define a boolean value indicating the define or define/create status. If {@literal true}, + * the {@link Index} declared by this {@link IndexFactoryBean} will only be defined initially + * and subsequently created when this bean receives an appropriate callback from the Spring container; + * if {@literal false}, the {@link Index} will be created immediately. */ public void setDefine(boolean define) { this.define = define; } - /* (non-Javadoc) */ + /** + * Returns a boolean indicating whether the {@link Index} declared and defined by this {@link IndexFactoryBean} + * will only be defined initially, or defined and created. If defined-only, the {@link IndexFactoryBean} + * will receive a callback at the end of the Spring container lifecycle to subsequently "create" all "defined-only" + * {@link Index Indexes} once, in a single operation. + * + * @return a boolean value indicating the define or define/create status. If {@literal true}, the {@link Index} + * declared by this {@link IndexFactoryBean} will only be defined initially and subsequently created when this bean + * receives an appropriate callback from the Spring container; if {@literal false}, the {@link Index} + * will be created immediately. + */ protected boolean isDefine() { return define; } /** - * @param expression the expression to set + * @param expression Index expression to set */ public void setExpression(String expression) { this.expression = expression; } /** - * @param from the from to set + * @param from Index from clause to set */ public void setFrom(String from) { this.from = from; } /** - * @param imports the imports to set + * @param imports Index imports to set */ public void setImports(String imports) { this.imports = imports; } /** - * @param override the override to set + * Configures whether to ignore the {@link Index} defined by this {@link IndexFactoryBean} + * when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown. + * + * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition + * but with another name. + * + * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name + * but possibly a different definition. + * + * When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown, + * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation + * and the name of the existing {@link Index} is added as an alias for this bean. + * + * When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown, + * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation. + * A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index} + * are different. + * + * {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}. + * + * Defaults to {@literal false}. + * + * @param ignore boolean value indicating whether to ignore the {@link Index} defined by + * this {@link IndexFactoryBean}. Default is {@literal false}. + * @see #setOverride(boolean) + */ + public void setIgnoreIfExists(boolean ignore) { + this.ignoreIfExists = ignore; + } + + /** + * Determines whether to ignore the {@link Index} defined by this {@link IndexFactoryBean} + * when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown. + * + * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition + * but with another name. + * + * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name + * but possibly a different definition. + * + * When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown, + * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation + * and the name of the existing {@link Index} is added as an alias for this bean. + * + * When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown, + * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation. + * A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index} + * are different. + * + * {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}. + * + * Defaults to {@literal false}. + * + * @return a boolean value indicating whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}. + * Default is {@literal false}. + * @see #setIgnoreIfExists(boolean) + */ + public boolean isIgnoreIfExists() { + return this.ignoreIfExists; + } + + /** + * Configures whether to override an existing {@link Index} having the same definition but different name + * as the {@link Index} that would be created by this {@link IndexFactoryBean}. + * + * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition + * but with another name. + * + * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name + * but possibly a different definition. + * + * With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override + * is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index} + * will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean} + * under the new {@link #resolveIndexName() name} having the same definition. + * + * With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown, + * then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition. + * When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding + * effectively just rebuilds the {@link Index}. + * + * {@literal ignoreIfExists} takes precedence over {@literal override}. + * + * Defaults to {@literal false}. + * + * @param override boolean value indicating whether an existing {@link Index} will be removed and recreated + * by this {@link IndexFactoryBean}. Default is {@literal false}. + * @see #setIgnoreIfExists(boolean) */ public void setOverride(boolean override) { this.override = override; } /** - * @param type the type to set + * Determines whether to override an existing {@link Index} having the same definition but different name + * as the {@link Index} that would be created by this {@link IndexFactoryBean}. + * + * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition + * but with another name. + * + * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name + * but possibly a different definition. + * + * With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override + * is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index} + * will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean} + * under the new {@link #resolveIndexName() name} having the same definition. + * + * With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown, + * then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition. + * When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding + * effectively just rebuilds the {@link Index}. + * + * {@literal ignoreIfExists} takes precedence over {@literal override}. + * + * Defaults to {@literal false}. + * + * @return a boolean value indicating whether an existing {@link Index} will be removed and recreated + * by this {@link IndexFactoryBean}. Default is {@literal false}. + * @see #setOverride(boolean) + */ + public boolean isOverride() { + return this.override; + } + + /** + * Set the {@link IndexType type} of the {@link Index} as a {@link String}. + * + * @param type {@link String} specifying the {@link IndexType type} of the {@link Index}. + * @see org.springframework.data.gemfire.IndexType#valueOf(String) + * @see #setType(IndexType) */ public void setType(String type) { setType(IndexType.valueOfIgnoreCase(type)); } /** - * Sets the type of GemFire Index to create. + * Set the {@link IndexType type} of the {@link Index}. * - * @param indexType the IndexType enumerated value indicating the type of GemFire Index - * that will be created by this Spring FactoryBean. + * @param type {@link IndexType} indicating the type of the {@link Index}. * @see org.springframework.data.gemfire.IndexType */ - public void setType(IndexType indexType) { - this.indexType = indexType; + public void setType(IndexType type) { + this.indexType = type; } /* (non-Javadoc) */ @@ -382,19 +730,23 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B 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!"); + protected IndexWrapper(QueryService queryService, String indexName) { + + Assert.notNull(queryService, "QueryService is required"); + Assert.hasText(indexName, "Name of Index is required"); + this.queryService = queryService; this.indexName = indexName; - } - protected synchronized Index getIndex() { + /* (non-Javadoc) */ + protected synchronized Index resolveIndex() { + if (this.index == null) { + String localIndexName = getIndexName(); - for (Index localIndex : getQueryService().getIndexes()) { + for (Index localIndex : nullSafeCollection(getQueryService().getIndexes())) { if (localIndex.getName().equals(localIndexName)) { this.index = localIndex; break; @@ -402,77 +754,85 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B } if (this.index == null) { - throw new GemfireIndexException(new IndexInvalidException(String.format( - "index with name (%1$s) was not found", localIndexName))); + throw new GemfireIndexException(String.format("Index with name [%s] was not found", localIndexName), + (Exception) null); } } return index; } - protected String getIndexName() { - Assert.state(StringUtils.hasText(indexName), "The Index 'name' was not properly initialized!"); - return indexName; + /* (non-Javadoc) */ + protected Index getIndex() { + return this.index; } + /* (non-Javadoc) */ + protected String getIndexName() { + Assert.state(StringUtils.hasText(this.indexName), "Index name is required"); + return this.indexName; + } + + /* (non-Javadoc) */ protected QueryService getQueryService() { - return queryService; + return this.queryService; } @Override public String getName() { - return getIndex().getName(); + return resolveIndex().getName(); } @Override public String getCanonicalizedFromClause() { - return getIndex().getCanonicalizedFromClause(); + return resolveIndex().getCanonicalizedFromClause(); } @Override public String getCanonicalizedIndexedExpression() { - return getIndex().getCanonicalizedIndexedExpression(); + return resolveIndex().getCanonicalizedIndexedExpression(); } @Override public String getCanonicalizedProjectionAttributes() { - return getIndex().getCanonicalizedProjectionAttributes(); + return resolveIndex().getCanonicalizedProjectionAttributes(); } @Override public String getFromClause() { - return getIndex().getFromClause(); + return resolveIndex().getFromClause(); } @Override public String getIndexedExpression() { - return getIndex().getIndexedExpression(); + return resolveIndex().getIndexedExpression(); } @Override public String getProjectionAttributes() { - return getIndex().getProjectionAttributes(); + return resolveIndex().getProjectionAttributes(); } @Override public Region getRegion() { - return getIndex().getRegion(); + return resolveIndex().getRegion(); } @Override public IndexStatistics getStatistics() { - return getIndex().getStatistics(); + return resolveIndex().getStatistics(); } @Override @SuppressWarnings("deprecation") public com.gemstone.gemfire.cache.query.IndexType getType() { - return getIndex().getType(); + return resolveIndex().getType(); } @Override - public boolean equals(final Object obj) { - if (obj == this) { + public boolean equals(Object obj) { + + if (this == obj) { return true; } @@ -484,21 +844,23 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B return (getIndexName().equals(((IndexWrapper) obj).getIndexName())); } - return getIndex().equals(obj); + return resolveIndex().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()); + return (this.index != null ? String.valueOf(this.index) : getIndexName()); } } - } diff --git a/src/main/java/org/springframework/data/gemfire/IndexType.java b/src/main/java/org/springframework/data/gemfire/IndexType.java index d1a936eb..9acd503e 100644 --- a/src/main/java/org/springframework/data/gemfire/IndexType.java +++ b/src/main/java/org/springframework/data/gemfire/IndexType.java @@ -25,6 +25,7 @@ package org.springframework.data.gemfire; */ @SuppressWarnings({ "deprecation", "unused" }) public enum IndexType { + FUNCTIONAL(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL), HASH(com.gemstone.gemfire.cache.query.IndexType.HASH), PRIMARY_KEY(com.gemstone.gemfire.cache.query.IndexType.PRIMARY_KEY), @@ -49,7 +50,7 @@ public enum IndexType { * @return a boolean value indicating whether the IndexType is a "FUNCTIONAL" Index. * @see #isFunctional() */ - public static boolean isFunctional(final IndexType indexType) { + public static boolean isFunctional(IndexType indexType) { return (indexType != null && indexType.isFunctional()); } @@ -60,7 +61,7 @@ public enum IndexType { * @return a boolean value indicating whether the IndexType is a "HASH" Index. * @see #isHash() */ - public static boolean isHash(final IndexType indexType) { + public static boolean isHash(IndexType indexType) { return (indexType != null && indexType.isHash()); } @@ -71,7 +72,7 @@ public enum IndexType { * @return a boolean value indicating whether the IndexType is a "KEY" Index. * @see #isFunctional() */ - public static boolean isKey(final IndexType indexType) { + public static boolean isKey(IndexType indexType) { return (indexType != null && indexType.isKey()); } @@ -84,7 +85,8 @@ public enum IndexType { * any IndexType in this enumeration. * @see com.gemstone.gemfire.cache.query.IndexType */ - public static IndexType valueOf(final com.gemstone.gemfire.cache.query.IndexType gemfireIndexType) { + public static IndexType valueOf(com.gemstone.gemfire.cache.query.IndexType gemfireIndexType) { + for (IndexType indexType : values()) { if (indexType.getGemfireIndexType().equals(gemfireIndexType)) { return indexType; @@ -101,7 +103,8 @@ public enum IndexType { * @return an IndexType matching the given String. * @see java.lang.String#equalsIgnoreCase(String) */ - public static IndexType valueOfIgnoreCase(final String value) { + public static IndexType valueOfIgnoreCase(String value) { + for (IndexType indexType : values()) { if (indexType.name().equalsIgnoreCase(value)) { return indexType; @@ -147,5 +150,4 @@ public enum IndexType { public boolean isKey() { return (this.equals(KEY) || this.equals(PRIMARY_KEY)); } - } 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 index db6db076..0cdfe2dc 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java @@ -111,12 +111,15 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { 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) { @@ -162,6 +165,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { IndexType indexType, Annotation indexAnnotation, BeanDefinitionRegistry registry) { if (indexAnnotation != null) { + AnnotationAttributes indexAnnotationAttributes = AnnotationAttributes.fromMap( AnnotationUtils.getAnnotationAttributes(indexAnnotation)); @@ -181,8 +185,12 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { indexFactoryBeanBuilder.addPropertyValue("from", resolveFrom(persistentEntity, persistentProperty, indexAnnotationAttributes)); + indexFactoryBeanBuilder.addPropertyValue("ignoreIfExists", Boolean.TRUE); + indexFactoryBeanBuilder.addPropertyValue("name", indexName); + indexFactoryBeanBuilder.addPropertyValue("override", Boolean.FALSE); + indexFactoryBeanBuilder.addPropertyValue("type", resolveType(persistentEntity, persistentProperty, indexAnnotationAttributes, indexType).toString()); @@ -199,6 +207,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { /* (non-Javadoc) */ @SuppressWarnings("unused") private String resolveExpression(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes) { String expression = (indexAnnotationAttributes.containsKey("expression") @@ -210,6 +219,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { /* (non-Javadoc) */ @SuppressWarnings("unused") private String resolveFrom(GemfirePersistentEntity persistentEntity, + GemfirePersistentProperty persistentProperty, AnnotationAttributes indexAnnotationAttributes) { String from = (indexAnnotationAttributes.containsKey("from") diff --git a/src/main/java/org/springframework/data/gemfire/config/xml/IndexParser.java b/src/main/java/org/springframework/data/gemfire/config/xml/IndexParser.java index 8e92573e..0681e0c2 100644 --- a/src/main/java/org/springframework/data/gemfire/config/xml/IndexParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/xml/IndexParser.java @@ -42,8 +42,10 @@ class IndexParser extends AbstractSimpleBeanDefinitionParser { private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); /* (non-Javadoc) */ - protected static void registerDefinedIndexesApplicationListener(ParserContext parserContext) { + private static void registerDefinedIndexesApplicationListener(ParserContext parserContext) { + if (REGISTERED.compareAndSet(false, true)) { + AbstractBeanDefinition createDefinedIndexesApplicationListener = BeanDefinitionBuilder .rootBeanDefinition(DefinedIndexesApplicationListener.class) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) diff --git a/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java new file mode 100644 index 00000000..0e277ae3 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java @@ -0,0 +1,298 @@ +/* + * Copyright 2017 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.support; + +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +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.util.Assert; + +/** + * The {@link AbstractFactoryBeanSupport} class is an abstract Spring {@link FactoryBean} base class implementation + * encapsulating operations common to SDG's {@link FactoryBean} implementations. + * + * @author John Blum + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.BeanNameAware + * @see org.springframework.beans.factory.FactoryBean + * @since 1.0.0 + */ +public abstract class AbstractFactoryBeanSupport implements FactoryBean, + BeanClassLoaderAware, BeanFactoryAware, BeanNameAware { + + protected static final boolean DEFAULT_SINGLETON = true; + + private ClassLoader beanClassLoader; + + private BeanFactory beanFactory; + + private final Log log = newLog(); + + private String beanName; + + /** + * Constructs a new instance of {@link Log} to log statements printed by Spring Data GemFire/Geode. + * + * @return a new instance of {@link Log}. + * @see org.apache.commons.logging.LogFactory#getLog(Class) + * @see org.apache.commons.logging.Log + */ + protected Log newLog() { + return LogFactory.getLog(getClass()); + } + + /** + * Sets a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes. + * + * @param classLoader {@link ClassLoader} used by the Spring container to load and create bean classes. + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) + * @see java.lang.ClassLoader + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + /** + * Returns a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes. + * + * @return the {@link ClassLoader} used by the Spring container to load and create bean classes. + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) + * @see java.lang.ClassLoader + */ + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + /** + * Sets a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared. + * + * @param beanFactory reference to the declaring Spring {@link BeanFactory}. + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory) + * @see org.springframework.beans.factory.BeanFactory + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared. + * + * @return a reference to the declaring Spring {@link BeanFactory}. + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory) + * @see org.springframework.beans.factory.BeanFactory + */ + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Sets the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * + * @param name {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String) + * @see java.lang.String + */ + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + /** + * Returns the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * + * @return the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String) + * @see java.lang.String + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Returns a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}. + * + * @return a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}. + * @see org.apache.commons.logging.Log + */ + protected Log getLog() { + return this.log; + } + + /** + * Indicates that this {@link FactoryBean} produces a single bean instance. + * + * @return {@literal true} by default. + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + @Override + public boolean isSingleton() { + return DEFAULT_SINGLETON; + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at debug level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logDebug(Supplier) + */ + protected void logDebug(String message, Object... args) { + logDebug(StringFormatSupplier.of(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at debug level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isDebugEnabled() + * @see org.apache.commons.logging.Log#debug(Object) + * @see #getLog() + */ + protected void logDebug(Supplier message) { + + Log log = getLog(); + + if (log.isDebugEnabled()) { + log.debug(message.get()); + } + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at info level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logInfo(Supplier) + */ + protected void logInfo(String message, Object... args) { + logInfo(StringFormatSupplier.of(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at info level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isInfoEnabled() + * @see org.apache.commons.logging.Log#info(Object) + * @see #getLog() + */ + protected void logInfo(Supplier message) { + + Log log = getLog(); + + if (log.isInfoEnabled()) { + log.info(message.get()); + } + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at warn level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logWarning(Supplier) + */ + protected void logWarning(String message, Object... args) { + logWarning(StringFormatSupplier.of(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at warn level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isWarnEnabled() + * @see org.apache.commons.logging.Log#warn(Object) + * @see #getLog() + */ + protected void logWarning(Supplier message) { + + Log log = getLog(); + + if (log.isWarnEnabled()) { + log.warn(message.get()); + } + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at error level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logError(Supplier) + */ + protected void logError(String message, Object... args) { + logError(StringFormatSupplier.of(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at error level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isErrorEnabled() + * @see org.apache.commons.logging.Log#error(Object) + * @see #getLog() + */ + protected void logError(Supplier message) { + + Log log = getLog(); + + if (log.isErrorEnabled()) { + log.error(message.get()); + } + } + + protected interface Supplier { + + T get(); + + } + + protected static class StringFormatSupplier implements Supplier { + + private final Object[] args; + + private final String message; + + protected static StringFormatSupplier of(String message, Object... args) { + return new StringFormatSupplier(message, nullSafeArray(args, Object.class)); + } + + private StringFormatSupplier(String message, Object... args) { + + Assert.hasText(message, "Message is required"); + + this.message = message; + this.args = args; + } + + @Override + public String get() { + return String.format(this.message, this.args); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java index 17c681ab..8e994df0 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java @@ -17,20 +17,21 @@ package org.springframework.data.gemfire.util; -import org.springframework.util.StringUtils; - import com.gemstone.gemfire.cache.Cache; import com.gemstone.gemfire.cache.CacheClosedException; import com.gemstone.gemfire.cache.CacheFactory; import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.client.ClientCache; import com.gemstone.gemfire.cache.client.ClientCacheFactory; import com.gemstone.gemfire.distributed.DistributedSystem; import com.gemstone.gemfire.internal.cache.GemFireCacheImpl; +import org.springframework.util.StringUtils; + /** - * CacheUtils is an abstract utility class encapsulating common operations for working with GemFire Cache - * and ClientCache instances. + * {@link CacheUtils} is an abstract utility class encapsulating common operations for working with + * GemFire {@link Cache} and {@link ClientCache} instances. * * @author John Blum * @see org.springframework.data.gemfire.util.DistributedSystemUtils @@ -52,6 +53,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ @SuppressWarnings("all") public static boolean isClient(GemFireCache cache) { + boolean client = (cache instanceof ClientCache); if (cache instanceof GemFireCacheImpl) { @@ -63,6 +65,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ public static boolean isDurable(ClientCache clientCache) { + DistributedSystem distributedSystem = getDistributedSystem(clientCache); // NOTE technically the following code snippet would be more useful/valuable but is not "testable"! @@ -75,6 +78,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ @SuppressWarnings("all") public static boolean isPeer(GemFireCache cache) { + boolean peer = (cache instanceof Cache); if (cache instanceof GemFireCacheImpl) { @@ -124,5 +128,21 @@ public abstract class CacheUtils extends DistributedSystemUtils { catch (CacheClosedException ignore) { return null; } + catch (IllegalStateException ignore) { + return null; + } + } + + /* (non-Javadoc) */ + public static GemFireCache resolveGemFireCache() { + + GemFireCache resolvedGemFireCache = getClientCache(); + + return (resolvedGemFireCache != null ? resolvedGemFireCache : getCache()); + } + + /* (non-Javadoc) */ + public static String toRegionPath(String regionName) { + return String.format("%1$s%2$s", Region.SEPARATOR, regionName); } } diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.9.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.9.xsd index 5b599ba4..67c24bfe 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.9.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.9.xsd @@ -2551,11 +2551,27 @@ Corresponds to the regionPath parameter in createIndex methods. ]]> - - + + + + + + + diff --git a/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java index 4b385729..0be8c08c 100644 --- a/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java @@ -16,22 +16,22 @@ package org.springframework.data.gemfire; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.query.Index; +import com.gemstone.gemfire.cache.query.IndexExistsException; +import com.gemstone.gemfire.cache.query.IndexNameConflictException; +import com.gemstone.gemfire.cache.query.QueryService; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -39,31 +39,50 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Import; -import com.gemstone.gemfire.cache.Cache; -import com.gemstone.gemfire.cache.query.Index; -import com.gemstone.gemfire.cache.query.IndexExistsException; - /** - * The IndexConflictsIntegrationTest class... + * Integration tests with test cases testing the numerous conflicting {@link Index} configurations. + * + * An {@link IndexExistsException} is thrown when 2 or more {@link Index Indexes} share the same definition + * but have different names. + * + * An {@link IndexNameConflictException} is thrown when 2 or more {@link Index Indexes} share the same name + * but have potentially different definitions. * * @author John Blum * @see org.junit.Test - * @link https://jira.spring.io/browse/SGF-432 + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.Import + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.query.Index + * @see IndexFactoryBean traps IndexExistsException instead of IndexNameConflictException + * @see Improve IndexFactoryBean's resilience and options for handling GemFire IndexExistsExceptions and IndexNameConflictExceptions * @since 1.6.3 */ public class IndexConflictsIntegrationTest { - protected void assertIndex(Index index, String expectedName, String expectedExpression, String expectedFromClause, + private static final AtomicBoolean IGNORE = new AtomicBoolean(false); + private static final AtomicBoolean OVERRIDE = new AtomicBoolean(false); + + private ConfigurableApplicationContext applicationContext; + + private void assertIndex(Index index, String expectedName, String expectedExpression, String expectedFromClause, IndexType expectedType) { - assertThat(index, is(notNullValue())); - assertThat(index.getName(), is(equalTo(expectedName))); - assertThat(index.getIndexedExpression(), is(equalTo(expectedExpression))); - assertThat(index.getFromClause(), is(equalTo(expectedFromClause))); - assertThat(IndexType.valueOf(index.getType()), is(equalTo(expectedType))); + assertThat(index).isNotNull(); + assertThat(index.getName()).isEqualTo(expectedName); + assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression); + assertThat(index.getFromClause()).isEqualTo(expectedFromClause); + assertThat(IndexType.valueOf(index.getType())).isEqualTo(expectedType); } - protected boolean close(ConfigurableApplicationContext applicationContext) { + private void assertIndexCount(int count) { + assertThat(getIndexCount()).isEqualTo(count); + } + + private boolean close(ConfigurableApplicationContext applicationContext) { + if (applicationContext != null) { applicationContext.close(); return !(applicationContext.isActive() || applicationContext.isRunning()); @@ -72,185 +91,260 @@ public class IndexConflictsIntegrationTest { return true; } + private Index getIndex(String indexName) { + + for (Index index : nullSafeCollection(getQueryService().getIndexes())) { + if (index.getName().equalsIgnoreCase(indexName)) { + return index; + } + } + + return null; + } + + private int getIndexCount() { + return nullSafeCollection(getQueryService().getIndexes()).size(); + } + + private QueryService getQueryService() { + return this.applicationContext.getBean("gemfireCache", GemFireCache.class).getQueryService(); + } + + private boolean hasIndex(String indexName) { + return (getIndex(indexName) != null); + } + + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + return new AnnotationConfigApplicationContext(annotatedClasses); + } + @Before public void setup() { - System.getProperties().remove("gemfire.cache.region.index.override"); - - assertThat(System.getProperties().containsKey("gemfire.cache.region.index.override"), is(false)); + assertThat(IGNORE.get()).isFalse(); + assertThat(OVERRIDE.get()).isFalse(); } - @Test(expected = BeanCreationException.class) - public void indexDefinitionConflictThrowsException() { - ConfigurableApplicationContext applicationContext = null; + @After + public void tearDown() { + OVERRIDE.set(false); + IGNORE.set(false); + + assertThat(close(this.applicationContext)).isTrue(); + } + + @Test + public void indexDefinitionConflictIgnoresIndex() { + + assertThat(IGNORE.compareAndSet(false, true)).isTrue(); + + this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class); + + assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue(); + assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue(); + assertIndexCount(1); + assertThat(hasIndex("customerIdIndex")).isTrue(); + assertThat(hasIndex("customerIdentifierIndex")).isFalse(); + + Index customersIdIndex = getIndex("customerIdIndex"); + + assertIndex(customersIdIndex, "customerIdIndex", + "id", "/Customers", IndexType.PRIMARY_KEY); + } + + @Test + public void indexDefinitionConflictOverridesIndex() { + + assertThat(OVERRIDE.compareAndSet(false, true)).isTrue(); + + this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class); + + assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue(); + assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue(); + assertIndexCount(1); + assertThat(hasIndex("customerIdIndex")).isFalse(); + assertThat(hasIndex("customerIdentifierIndex")).isTrue(); + + Index customersIdentifierIndex = getIndex("customerIdentifierIndex"); + + assertIndex(customersIdentifierIndex, "customerIdentifierIndex", + "id", "/Customers", IndexType.PRIMARY_KEY); + } + + @Test(expected = IndexExistsException.class) + public void indexDefinitionConflictThrowsIndexExistsException() throws Throwable { try { - applicationContext = new AnnotationConfigApplicationContext( - IndexDefinitionConflictGemFireConfiguration.class); + this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class); } catch (BeanCreationException expected) { - assertThat(expected.getMessage(), containsString("Error creating bean with name 'customerIdentityIndex'" - + " defined in org.springframework.data.gemfire.IndexConflictsIntegrationTest$IndexDefinitionConflictGemFireConfiguration:" - + " Invocation of init method failed")); - assertThat(expected.getCause(), is(instanceOf(GemfireIndexException.class))); - assertThat(expected.getCause().getMessage(), - containsString("An Index with a different name having the same definition" - + " as this Index (customerIdentityIndex) already exists")); - assertThat(expected.getCause().getCause(), is(instanceOf(IndexExistsException.class))); - assertThat(expected.getCause().getCause().getMessage(), is(equalTo("Similar Index Exists"))); - throw expected; - } - finally { - assertThat(close(applicationContext), is(true)); + assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerIdentifierIndex'"); + + assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class); + + String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION, + "id", "/Customers", IndexType.PRIMARY_KEY); + + assertThat(expected.getCause()).hasMessageStartingWith(String.format( + "An Index with a different name [customerIdIndex] having the same definition [%s] already exists", + existingIndexDefinition)); + + assertThat(expected.getCause()).hasCauseInstanceOf(IndexExistsException.class); + + assertThat(expected.getCause().getCause()).hasMessage("Similar Index Exists"); + + assertThat(expected.getCause().getCause()).hasNoCause(); + + throw expected.getCause().getCause(); } } @Test - public void indexNameConflictOverridesExistingIndex() { - ConfigurableApplicationContext applicationContext = null; + public void indexNameConflictIgnoresIndex() { - try { - applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class); + assertThat(IGNORE.compareAndSet(false, true)).isTrue(); - assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2))); + this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class); - Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class); + assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue(); + assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue(); + assertIndexCount(1); + assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue(); - assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1))); + Index customerLastNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME); - Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class); - - assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME, - "lastName", "/Customers", IndexType.HASH); - - Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class); - - assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME, - "firstName", "/Customers", IndexType.FUNCTIONAL); - - assertThat(customerFirstNameIndex, is(not(sameInstance(customerLastNameIndex)))); - assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(), - is(sameInstance(customerFirstNameIndex))); - } - finally { - assertThat(close(applicationContext), is(true)); - } + assertIndex(customerLastNameIndex, IndexNameConflictConfiguration.INDEX_NAME, + "lastName", "/Customers", IndexType.HASH); } @Test - public void indexNameConflictReturnsExistingIndex() { - ConfigurableApplicationContext applicationContext = null; + public void indexNameConflictOverridesIndex() { + assertThat(OVERRIDE.compareAndSet(false, true)).isTrue(); + + this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class); + + assertThat(this.applicationContext.getBeansOfType(Index.class)).hasSize(2); + assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue(); + assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue(); + assertIndexCount(1); + assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue(); + + Index customerFirstNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME); + + assertIndex(customerFirstNameIndex, IndexNameConflictConfiguration.INDEX_NAME, + "firstName", "/Customers", IndexType.FUNCTIONAL); + } + + @Test(expected = IndexNameConflictException.class) + public void indexNameConflictThrowsIndexNameConflictException() throws Throwable { try { - System.setProperty("gemfire.cache.region.index.override", Boolean.FALSE.toString()); - - assertThat(System.getProperty("gemfire.cache.region.index.override", "true"), - is(equalTo(Boolean.FALSE.toString()))); - - applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class); - - assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2))); - - Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class); - - assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1))); - - Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class); - - assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME, - "lastName", "/Customers", IndexType.HASH); - - Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class); - - assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME, - "lastName", "/Customers", IndexType.HASH); - - assertThat(customerFirstNameIndex, is(sameInstance(customerLastNameIndex))); - assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(), - is(sameInstance(customerLastNameIndex))); + this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class); } - finally { - System.getProperties().remove("gemfire.cache.region.index.override"); + catch (BeanCreationException expected) { - if (applicationContext != null) { - applicationContext.close(); - } + assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerFirstNameIndex'"); + + assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class); + + assertThat(expected.getCause()).hasMessageStartingWith(String.format( + "An Index with the same name [%s] having possibly a different definition already exists;", + IndexNameConflictConfiguration.INDEX_NAME)); + + assertThat(expected.getCause()).hasCauseInstanceOf(IndexNameConflictException.class); + + assertThat(expected.getCause().getCause()).hasMessage(String.format("Index named ' %s ' already exists.", + IndexNameConflictConfiguration.INDEX_NAME)); + + assertThat(expected.getCause().getCause()).hasNoCause(); + + throw expected.getCause().getCause(); } } @Configuration @SuppressWarnings("unused") - public static class BaseGemFireConfiguration { + public static class GemFireConfiguration { - @Bean public Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + gemfireProperties.setProperty("name", IndexConflictsIntegrationTest.class.getSimpleName()); gemfireProperties.setProperty("mcast-port", "0"); gemfireProperties.setProperty("log-level", "warning"); + return gemfireProperties; } @Bean public CacheFactoryBean gemfireCache() { + CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); + + cacheFactoryBean.setClose(true); cacheFactoryBean.setProperties(gemfireProperties()); - cacheFactoryBean.setUseBeanFactoryLocator(false); + return cacheFactoryBean; } @Bean(name = "Customers") - public ReplicatedRegionFactoryBean customersRegion(Cache gemfireCache) { + public ReplicatedRegionFactoryBean customersRegion(GemFireCache gemfireCache) { + ReplicatedRegionFactoryBean customersRegionFactory = new ReplicatedRegionFactoryBean(); + customersRegionFactory.setCache(gemfireCache); - customersRegionFactory.setName("Customers"); + customersRegionFactory.setClose(false); customersRegionFactory.setPersistent(false); + return customersRegionFactory; } } @Configuration - @Import(BaseGemFireConfiguration.class) + @Import(GemFireConfiguration.class) @SuppressWarnings("unused") - public static class IndexDefinitionConflictGemFireConfiguration { + public static class IndexDefinitionConflictConfiguration { @Bean - public IndexFactoryBean customerIdIndex(Cache gemfireCache) { + public IndexFactoryBean customerIdIndex(GemFireCache gemfireCache) { + IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + indexFactoryBean.setCache(gemfireCache); indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Customers"); indexFactoryBean.setType(IndexType.PRIMARY_KEY); + return indexFactoryBean; } @Bean @DependsOn("customerIdIndex") - public IndexFactoryBean customerIdentityIndex(Cache gemfireCache) { + public IndexFactoryBean customerIdentifierIndex(GemFireCache gemfireCache) { + IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + indexFactoryBean.setCache(gemfireCache); indexFactoryBean.setExpression("id"); + indexFactoryBean.setIgnoreIfExists(IGNORE.get()); indexFactoryBean.setFrom("/Customers"); + indexFactoryBean.setOverride(OVERRIDE.get()); indexFactoryBean.setType(IndexType.PRIMARY_KEY); + return indexFactoryBean; } } @Configuration - @Import(BaseGemFireConfiguration.class) + @Import(GemFireConfiguration.class) @SuppressWarnings("unused") - public static class IndexNameConflictGemFireConfiguration { + public static class IndexNameConflictConfiguration { protected static final String INDEX_NAME = "CustomerNameIdx"; @Bean - public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { - return new PropertyPlaceholderConfigurer(); - } - - @Bean - public IndexFactoryBean customerLastNameIndex(Cache gemfireCache, - @Value("${gemfire.cache.region.index.override:true}") boolean override) { + public IndexFactoryBean customerLastNameIndex(GemFireCache gemfireCache) { IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); @@ -258,7 +352,6 @@ public class IndexConflictsIntegrationTest { indexFactoryBean.setExpression("lastName"); indexFactoryBean.setFrom("/Customers"); indexFactoryBean.setName(INDEX_NAME); - indexFactoryBean.setOverride(override); indexFactoryBean.setType(IndexType.HASH); return indexFactoryBean; @@ -266,20 +359,19 @@ public class IndexConflictsIntegrationTest { @Bean @DependsOn("customerLastNameIndex") - public IndexFactoryBean customerFirstNameIndex(Cache gemfireCache, - @Value("${gemfire.cache.region.index.override:true}") boolean override) { + public IndexFactoryBean customerFirstNameIndex(GemFireCache gemfireCache) { IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); indexFactoryBean.setCache(gemfireCache); indexFactoryBean.setExpression("firstName"); indexFactoryBean.setFrom("/Customers"); + indexFactoryBean.setIgnoreIfExists(IGNORE.get()); indexFactoryBean.setName(INDEX_NAME); - indexFactoryBean.setOverride(override); + indexFactoryBean.setOverride(OVERRIDE.get()); indexFactoryBean.setType(IndexType.FUNCTIONAL); return indexFactoryBean; } } - } diff --git a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java index 5bc28f02..20c0075b 100644 --- a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java @@ -16,65 +16,59 @@ package org.springframework.data.gemfire; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.isA; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.hamcrest.Matchers.startsWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.same; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.data.gemfire.config.xml.GemfireConstants; -import org.springframework.data.util.ReflectionUtils; - import com.gemstone.gemfire.cache.Cache; 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.IndexNameConflictException; import com.gemstone.gemfire.cache.query.IndexStatistics; import com.gemstone.gemfire.cache.query.QueryService; +import org.apache.commons.logging.Log; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.data.gemfire.config.xml.GemfireConstants; + /** - * The IndexFactoryBeanTest class is a test suite of test cases testing the contract and functionality - * of the IndexFactoryBean class. + * Unit tests for {@link IndexFactoryBean}. * * @author John Blum * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.Spy + * @see org.mockito.runners.MockitoJUnitRunner * @see org.springframework.data.gemfire.IndexFactoryBean * @see com.gemstone.gemfire.cache.Cache * @see com.gemstone.gemfire.cache.client.ClientCache @@ -82,27 +76,27 @@ import com.gemstone.gemfire.cache.query.QueryService; * @see com.gemstone.gemfire.cache.query.QueryService * @since 1.5.2 */ +@RunWith(MockitoJUnitRunner.class) public class IndexFactoryBeanTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); + @Mock + private BeanFactory mockBeanFactory; - private Cache mockCache = mock(Cache.class, "IndexFactoryBeanTest.MockCache"); + @Mock + private Cache mockCache; - private IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + @Spy + private IndexFactoryBean indexFactoryBean; - private QueryService mockQueryService = mock(QueryService.class, "IndexFactoryBeanTest.MockQueryService"); + @Mock + private Log mockLog; - protected IndexFactoryBean newIndexFactoryBean() { - IndexFactoryBean indexFactoryBean = new IndexFactoryBean() { - @Override QueryService lookupQueryService() { - return mockQueryService; - } - }; + @Mock + private QueryService mockQueryService; - indexFactoryBean.setCache(mockCache); - - return indexFactoryBean; + @Before + public void setup() { + when(mockCache.getQueryService()).thenReturn(mockQueryService); } @After @@ -110,26 +104,67 @@ public class IndexFactoryBeanTest { indexFactoryBean.setBeanFactory(null); indexFactoryBean.setCache(null); indexFactoryBean.setDefine(false); + indexFactoryBean.setExpression(null); + indexFactoryBean.setFrom(null); + indexFactoryBean.setImports(null); + indexFactoryBean.setQueryService(null); + indexFactoryBean.setType((IndexType) null); } - @Test - public void afterPropertiesSetIsSuccessful() throws Exception { - BeanFactory mockBeanFactory = mock(BeanFactory.class, "testAfterPropertiesSetIsSuccessful.MockBeanFactory"); + private Index mockIndex(String name) { - Cache mockCache = mock(Cache.class, "testAfterPropertiesSetIsSuccessful.MockCache"); + Index mockIndex = mock(Index.class, name); - Index mockIndex = mock(Index.class, "testAfterPropertiesSetIsSuccessful.MockIndex"); + when(mockIndex.getName()).thenReturn(name); - QueryService mockQueryService = mock(QueryService.class, "testAfterPropertiesSetIsSuccessful.MockQueryService"); + return mockIndex; + } - when(mockBeanFactory.containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE))).thenReturn(false); - when(mockCache.getQueryService()).thenReturn(mockQueryService); - when(mockQueryService.createKeyIndex(eq("TestKeyIndex"), eq("id"), eq("/Example"))).thenReturn(mockIndex); + private Index mockIndexWithDefinition(String name, String expression, String fromClause, IndexType type) { - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + Index mockIndex = mockIndex(name); + + when(mockIndex.getIndexedExpression()).thenReturn(expression); + when(mockIndex.getFromClause()).thenReturn(fromClause); + when(mockIndex.getType()).thenReturn(type.getGemfireIndexType()); + + return mockIndex; + } + + private QueryService mockQueryService(String name) { + return mock(QueryService.class, name); + } + + private IndexFactoryBean newIndexFactoryBean() { + + IndexFactoryBean indexFactoryBean = spy(new IndexFactoryBean() { + @Override + protected Log newLog() { + return mockLog; + } + }); indexFactoryBean.setBeanFactory(mockBeanFactory); indexFactoryBean.setCache(mockCache); + indexFactoryBean.setQueryService(mockQueryService); + + return indexFactoryBean; + } + + @Test + @SuppressWarnings("all") + public void afterPropertiesSetIsSuccessful() throws Exception { + + ConfigurableBeanFactory mockConfigurableBeanFactory = mock(ConfigurableBeanFactory.class); + + Index mockIndex = mock(Index.class, "testAfterPropertiesSetIsSuccessful.MockIndex"); + + when(mockQueryService.createKeyIndex(eq("TestKeyIndex"), eq("id"), eq("/Example"))).thenReturn(mockIndex); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setBeanFactory(mockConfigurableBeanFactory); + indexFactoryBean.setBeanName("KeyIndexBean"); indexFactoryBean.setDefine(false); indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Example"); @@ -137,133 +172,209 @@ public class IndexFactoryBeanTest { indexFactoryBean.setType("key"); indexFactoryBean.afterPropertiesSet(); - assertEquals(mockIndex, indexFactoryBean.getObject()); - assertSame(mockIndex, indexFactoryBean.getObject()); // assert Index really is a 'Singleton' - assertTrue(Index.class.isAssignableFrom(indexFactoryBean.getObjectType())); - assertTrue(indexFactoryBean.isSingleton()); + assertThat(indexFactoryBean.getIndex()).isEqualTo(mockIndex); - verify(mockBeanFactory, times(1)).containsBean( - eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); - verify(mockCache, times(1)).getQueryService(); - verify(mockQueryService, times(1)).createKeyIndex(eq("TestKeyIndex"), eq("id"), eq("/Example")); + Index actualIndex = indexFactoryBean.getObject(); + + assertThat(actualIndex).isEqualTo(mockIndex); + assertThat(indexFactoryBean.getObject()).isSameAs(actualIndex); + assertThat(Index.class).isAssignableFrom(indexFactoryBean.getObjectType()); + assertThat(indexFactoryBean.isSingleton()).isTrue(); + + verify(indexFactoryBean, times(1)).getBeanName(); + verify(indexFactoryBean, never()).lookupQueryService(); + verify(mockConfigurableBeanFactory, times(1)) + .registerAlias(eq("KeyIndexBean"), eq("TestKeyIndex")); + verify(mockQueryService, times(1)) + .createKeyIndex(eq("TestKeyIndex"), eq("id"), eq("/Example")); + verifyZeroInteractions(mockCache); } @Test(expected = IllegalArgumentException.class) + public void afterPropertiesSetWithNoIndexName() throws Exception { + try { + newIndexFactoryBean().afterPropertiesSet(); + } + catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("Index name is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalStateException.class) public void afterPropertiesSetWithNullCache() throws Exception { try { new IndexFactoryBean().afterPropertiesSet(); } - catch (IllegalArgumentException expected) { - assertEquals("The GemFire Cache reference must not be null!", expected.getMessage()); + catch (IllegalStateException expected) { + assertThat(expected).hasMessage("Cache is required"); + assertThat(expected).hasNoCause(); + throw expected; } } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalStateException.class) public void afterPropertiesSetWithNullQueryService() throws Exception { - try { - IndexFactoryBean indexFactoryBean = new IndexFactoryBean() { - @Override QueryService lookupQueryService() { - return null; - } - }; + IndexFactoryBean indexFactoryBean = spy(new IndexFactoryBean()); + + doReturn(null).when(indexFactoryBean).lookupQueryService(); + + try { indexFactoryBean.setCache(mockCache); + indexFactoryBean.setName("TestIndex"); indexFactoryBean.afterPropertiesSet(); } - catch (IllegalArgumentException expected) { - assertEquals("QueryService is required to create an Index", expected.getMessage()); + catch (IllegalStateException expected) { + assertThat(expected).hasMessage("QueryService is required to create an Index"); + assertThat(expected).hasNoCause(); + throw expected; } + finally { + verify(indexFactoryBean, times(1)).lookupQueryService(); + } } @Test(expected = IllegalArgumentException.class) public void afterPropertiesSetWithUnspecifiedExpression() throws Exception { try { - newIndexFactoryBean().afterPropertiesSet(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("Index 'expression' is required", expected.getMessage()); + assertThat(expected).hasMessage("Index expression is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void afterPropertiesSetWithUnspecifiedFromClause() throws Exception { + try { + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setExpression("id"); + indexFactoryBean.afterPropertiesSet(); + } + catch (Exception expected) { + assertThat(expected).hasMessage("Index from clause is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void afterPropertiesSetUsingImportsWithInvalidIndexKeyType() throws Exception { + try { + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setImports("org.example.DomainType"); + indexFactoryBean.setType("PriMary_Key"); + indexFactoryBean.afterPropertiesSet(); + } + catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("Imports are not supported with a KEY Index"); + assertThat(expected).hasNoCause(); + throw expected; } } @Test - public void afterPropertiesSetWithUnspecifiedFromClause() throws Exception { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectCause(is(nullValue(Throwable.class))); - expectedException.expectMessage("Index 'from clause' is required"); - - IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - - indexFactoryBean.setExpression("id"); - indexFactoryBean.afterPropertiesSet(); + public void resolveCacheFromCacheProperty() { + assertThat(newIndexFactoryBean().resolveCache()).isSameAs(mockCache); } - @Test(expected = IllegalArgumentException.class) - public void afterPropertiesSetWithUnspecifiedIndexName() throws Exception { + @Test(expected = IllegalStateException.class) + public void resolveCacheThrowsExceptionForUnresolvableCache() { try { IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - indexFactoryBean.setExpression("id"); - indexFactoryBean.setFrom("/Example"); - indexFactoryBean.afterPropertiesSet(); + indexFactoryBean.setCache(null); + indexFactoryBean.resolveCache(); } - catch (IllegalArgumentException expected) { - assertEquals("Index 'name' is required", expected.getMessage()); + catch (IllegalStateException expected) { + assertThat(expected).hasMessage("Cache is required"); + assertThat(expected).hasNoCause(); + throw expected; } } + @Test + public void resolveIndexNameFromBeanNameProperty() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setBeanName("TestIndexBeanName"); + indexFactoryBean.setName(null); + + assertThat(indexFactoryBean.resolveIndexName()).isEqualTo("TestIndexBeanName"); + } + + @Test + public void resolveIndexNameFromNameProperty() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setBeanName("TestIndexBeanName"); + indexFactoryBean.setName("TestIndex"); + + assertThat(indexFactoryBean.resolveIndexName()).isEqualTo("TestIndex"); + } + @Test(expected = IllegalArgumentException.class) - public void afterPropertiesSetUsingImportsWithInvalidType() throws Exception { + public void resolveIndexNameThrowsExceptionForUnresolvableIndexName() { try { IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - indexFactoryBean.setExpression("id"); - indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setImports("org.example.DomainType"); - indexFactoryBean.setName("TestIndex"); - indexFactoryBean.setType("PriMary_Key"); - indexFactoryBean.afterPropertiesSet(); + indexFactoryBean.setBeanName(null); + indexFactoryBean.setName(null); + indexFactoryBean.resolveIndexName(); } catch (IllegalArgumentException expected) { - assertEquals("'imports' are not supported with a KEY Index", expected.getMessage()); + assertThat(expected).hasMessage("Index name is required"); + assertThat(expected).hasNoCause(); + throw expected; } } @Test public void doLookupQueryService() { - QueryService mockQueryServiceOne = mock(QueryService.class, "testLookupQueryService.MockQueryService.One"); - QueryService mockQueryServiceTwo = mock(QueryService.class, "testLookupQueryService.MockQueryService.Two"); - Cache mockCache = mock(Cache.class, "testDoLookupQueryService.MockCache"); + QueryService mockQueryServiceOne = mockQueryService("testDoLookupQueryService.MockQueryService.One"); - when(mockCache.getQueryService()).thenReturn(mockQueryServiceTwo); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); - - indexFactoryBean.setCache(mockCache); indexFactoryBean.setQueryService(mockQueryServiceOne); - assertSame(mockQueryServiceOne, indexFactoryBean.doLookupQueryService()); + assertThat(indexFactoryBean.doLookupQueryService()).isSameAs(mockQueryServiceOne); verify(mockCache, never()).getQueryService(); } @Test public void doLookupQueryServiceOnClientCache() { - ClientCache mockClientCache = mock(ClientCache.class, "testDoLookupQueryServiceOnClientCache.MockClientCache"); - QueryService mockQueryService = mock(QueryService.class, "testDoLookupQueryServiceOnClientCache.MockQueryService"); + ClientCache mockClientCache = mock(ClientCache.class); + QueryService mockQueryService = mockQueryService("testDoLookupQueryServiceOnClientCache.MockQueryService"); when(mockClientCache.getLocalQueryService()).thenReturn(mockQueryService); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setCache(mockClientCache); indexFactoryBean.setQueryService(null); - assertSame(mockQueryService, indexFactoryBean.doLookupQueryService()); + assertThat(indexFactoryBean.doLookupQueryService()).isSameAs(mockQueryService); verify(mockClientCache, times(1)).getLocalQueryService(); verify(mockClientCache, never()).getQueryService(); @@ -271,399 +382,824 @@ public class IndexFactoryBeanTest { @Test public void doLookupQueryServiceOnPeerCache() { - Cache mockCache = mock(Cache.class, "testDoLookupQueryServiceOnPeerCache.MockCache"); - QueryService mockQueryService = mock(QueryService.class, "testDoLookupQueryServiceOnPeerCache.MockQueryService"); - - when(mockCache.getQueryService()).thenReturn(mockQueryService); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setCache(mockCache); indexFactoryBean.setQueryService(null); - assertSame(mockQueryService, indexFactoryBean.doLookupQueryService()); + assertThat(indexFactoryBean.doLookupQueryService()).isSameAs(mockQueryService); verify(mockCache, times(1)).getQueryService(); } @Test public void lookupQueryServiceFromBeanFactory() { - BeanFactory mockBeanFactory = mock(BeanFactory.class, "testLookupQueryServiceFromBeanFactory.MockBeanFactory"); - QueryService mockQueryService = mock(QueryService.class, "testLookupQueryServiceFromBeanFactory.MockQueryService"); + QueryService mockQueryService = + mockQueryService("testLookupQueryServiceFromBeanFactory.MockQueryService"); when(mockBeanFactory.containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE))) .thenReturn(true); when(mockBeanFactory.getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), eq(QueryService.class))).thenReturn(mockQueryService); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - indexFactoryBean.setBeanFactory(mockBeanFactory); + assertThat(indexFactoryBean.lookupQueryService()).isSameAs(mockQueryService); - QueryService actualQueryService = indexFactoryBean.lookupQueryService(); - - assertSame(mockQueryService, actualQueryService); - - verify(mockBeanFactory, times(1)).containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); - verify(mockBeanFactory, times(1)).getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), - eq(QueryService.class)); + verify(mockBeanFactory, times(1)).containsBean( + eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); + verify(mockBeanFactory, times(1)).getBean( + eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), eq(QueryService.class)); + verify(mockCache, never()).getQueryService(); } @Test public void lookupQueryServiceFromCache() { - BeanFactory mockBeanFactory = mock(BeanFactory.class, "testLookupQueryServiceFromCache.MockBeanFactory"); + + QueryService mockQueryService = mockQueryService("testLookupQueryServiceFromCache.MockQueryService"); when(mockBeanFactory.containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE))) .thenReturn(false); + when(mockCache.getQueryService()).thenReturn(mockQueryService); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - indexFactoryBean.setBeanFactory(mockBeanFactory); indexFactoryBean.setDefine(false); - indexFactoryBean.setQueryService(mockQueryService); + indexFactoryBean.setQueryService(null); - QueryService actualQueryService = indexFactoryBean.lookupQueryService(); + assertThat(indexFactoryBean.lookupQueryService()).isSameAs(mockQueryService); - assertSame(mockQueryService, actualQueryService); + verify(mockBeanFactory, times(1)).containsBean( + eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); + verify(mockBeanFactory, never()).getBean( + eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), eq(QueryService.class)); + verify(mockCache, times(1)).getQueryService(); + } - verify(mockBeanFactory, times(1)).containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); - verify(mockBeanFactory, never()).getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), - eq(QueryService.class)); + @Test + public void resolveQueryServiceReturnsIndexFactoryBeanConfiguredQueryService() { + assertThat(newIndexFactoryBean().resolveQueryService()).isSameAs(mockQueryService); + } + + @Test + public void resolveQueryServiceReturnsQueryServiceFromLookup() { + + QueryService mockQueryService = + mockQueryService("testResolveQueryServiceReturnsQueryServiceFromLookup.MockQueryService"); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setQueryService(null); + + doReturn(mockQueryService).when(indexFactoryBean).lookupQueryService(); + + assertThat(indexFactoryBean.resolveQueryService()).isSameAs(mockQueryService); + + verify(indexFactoryBean, times(1)).lookupQueryService(); + } + + @Test(expected = IllegalStateException.class) + public void resolveQueryServiceThrowsExceptionForUnresolvableQueryService() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + try { + indexFactoryBean.setQueryService(null); + doReturn(null).when(indexFactoryBean).lookupQueryService(); + indexFactoryBean.resolveQueryService(); + } + catch (IllegalStateException expected) { + assertThat(expected).hasMessage("QueryService is required to create an Index"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(indexFactoryBean, times(1)).lookupQueryService(); + } } @Test public void registerQueryServiceBeanWhenIndexIsCreated() { + ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class, "testRegisterQueryServiceBeanWhenIndexIsCreated.MockBeanFactory"); - QueryService mockQueryService = mock(QueryService.class, "testRegisterQueryServiceBeanWhenIndexIsCreated.MockQueryService"); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setBeanFactory(mockBeanFactory); indexFactoryBean.setDefine(false); - assertSame(mockQueryService, indexFactoryBean.registerQueryServiceBean(mockQueryService)); + assertThat(indexFactoryBean.registerQueryServiceBean("queryServiceBeanName", mockQueryService)) + .isSameAs(mockQueryService); - verify(mockBeanFactory, never()).registerSingleton( - eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), same(mockQueryService)); + verify(mockBeanFactory, never()).registerSingleton(eq("queryServiceBeanName"), same(mockQueryService)); } @Test public void registerQueryServiceBeanWhenIndexIsDefined() { + ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class, "testRegisterQueryServiceBeanWhenIndexIsDefined.MockBeanFactory"); - QueryService mockQueryService = mock(QueryService.class, "testRegisterQueryServiceBeanWhenIndexIsDefined.MockQueryService"); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setBeanFactory(mockBeanFactory); indexFactoryBean.setDefine(true); - assertSame(mockQueryService, indexFactoryBean.registerQueryServiceBean(mockQueryService)); + assertThat(indexFactoryBean.registerQueryServiceBean("queryServiceBeanName", mockQueryService)) + .isSameAs(mockQueryService); - verify(mockBeanFactory, times(1)).registerSingleton( - eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), same(mockQueryService)); + verify(mockBeanFactory, times(1)) + .registerSingleton(eq("queryServiceBeanName"), same(mockQueryService)); } @Test - public void createIndexReturningNewKeyIndex() throws Exception { - Index mockIndex = mock(Index.class, "createIndexReturningNewKeyIndex.MockIndex"); + public void registerAliasWhenBeanFactoryIsNull() { - QueryService mockQueryService = mock(QueryService.class, "createIndexReturningNewKeyIndex.MockQueryService"); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); - when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"))).thenReturn(mockIndex); + indexFactoryBean.setBeanFactory(null); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + indexFactoryBean.registerAlias("IndexBean", "TestIndex"); + } + + @Test + public void registerAliasWhenBeanFactoryIsNotConfigurable() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.registerAlias("IndexBean", "TestIndex"); + + verifyZeroInteractions(mockBeanFactory); + } + + @Test + public void registerAliasWhenBeanNameAndIndexNameMatch() { + + ConfigurableBeanFactory mockConfigurableBeanFactory = mock(ConfigurableBeanFactory.class); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setBeanFactory(mockConfigurableBeanFactory); + indexFactoryBean.registerAlias("TestIndex", "TestIndex"); + + assertThat(indexFactoryBean.getBeanFactory()).isSameAs(mockConfigurableBeanFactory); + + verifyZeroInteractions(mockConfigurableBeanFactory); + } + + @Test + public void registerAliasWhenBeanNameAndIndexNameAreDifferent() { + + ConfigurableBeanFactory mockConfigurableBeanFactory = mock(ConfigurableBeanFactory.class); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setBeanFactory(mockConfigurableBeanFactory); + indexFactoryBean.registerAlias("IndexBean", "TestIndex"); + + assertThat(indexFactoryBean.getBeanFactory()).isSameAs(mockConfigurableBeanFactory); + + verify(mockConfigurableBeanFactory, times(1)) + .registerAlias(eq("IndexBean"), eq("TestIndex")); + } + + @Test + public void createIndexReturnsNewKeyIndex() throws Exception { + + Index mockIndex = mockIndex("testCreateIndexReturnsNewKeyIndex.MockIndex"); + + when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"))) + .thenReturn(mockIndex); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Example"); indexFactoryBean.setName("KeyIndex"); - indexFactoryBean.setOverride(false); indexFactoryBean.setType("key"); Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "KeyIndex"); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); - verify(mockQueryService, times(1)).createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")); + verify(mockQueryService, times(1)) + .createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")); } @Test - public void createIndexReturningNewHashIndex() throws Exception { - Index mockIndex = mock(Index.class, "createIndexReturningNewHashIndex.MockIndex"); + public void createIndexReturnsNewHashIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "createIndexReturningNewHashIndex.MockQueryService"); + Index mockIndex = mockIndex("testCreateIndexReturnsNewHashIndex.MockIndex"); - when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); when(mockQueryService.createHashIndex(eq("HashIndex"), eq("name"), eq("/Animals"), eq("org.example.Dog"))) .thenReturn(mockIndex); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setExpression("name"); indexFactoryBean.setFrom("/Animals"); indexFactoryBean.setImports("org.example.Dog"); indexFactoryBean.setName("HashIndex"); - indexFactoryBean.setOverride(false); indexFactoryBean.setType("HasH"); Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "HashIndex"); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); - verify(mockQueryService, times(1)).createHashIndex(eq("HashIndex"), eq("name"), eq("/Animals"), - eq("org.example.Dog")); + verify(mockQueryService, times(1)) + .createHashIndex(eq("HashIndex"), eq("name"), eq("/Animals"), eq("org.example.Dog")); } @Test - public void createIndexReturningNewFunctionalIndex() throws Exception { - Index mockIndex = mock(Index.class, "createIndexReturningNewFunctionalIndex.MockIndex"); + public void createIndexReturnsNewFunctionalIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, - "createIndexReturningNewFunctionalIndex.MockQueryService"); + Index mockIndex = mockIndex("testCreateIndexReturnsNewFunctionalIndex.MockIndex"); - when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); when(mockQueryService.createIndex(eq("FunctionalIndex"), eq("someField"), eq("/Example"))) .thenReturn(mockIndex); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setExpression("someField"); indexFactoryBean.setFrom("/Example"); indexFactoryBean.setImports(" "); indexFactoryBean.setName("FunctionalIndex"); - indexFactoryBean.setOverride(false); indexFactoryBean.setType((String) null); Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "FunctionalIndex"); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); - verify(mockQueryService, times(1)).createIndex(eq("FunctionalIndex"), eq("someField"), eq("/Example")); + verify(mockQueryService, times(1)) + .createIndex(eq("FunctionalIndex"), eq("someField"), eq("/Example")); } - @Test - public void createIndexOverridesExistingIndex() throws Exception { - Index mockExistingIndex = mock(Index.class, "createIndexOverridesExistingIndex.MockExistingIndex"); - Index mockOverridingIndex = mock(Index.class, "createIndexOverridesExistingIndex.MockOverridingIndex"); + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexExistsException() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "createIndexOverridesExistingIndex.MockQueryService"); + Index mockIndex = + mockIndexWithDefinition("MockIndex", "id", "/Example", IndexType.PRIMARY_KEY); - when(mockExistingIndex.getName()).thenReturn("ExistingIndex"); - when(mockOverridingIndex.getName()).thenReturn("OverridingIndex"); - when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockExistingIndex)); - when(mockQueryService.createHashIndex(eq("ExistingIndex"), eq("someField"), eq("/Example"), - eq("example.DomainType"))).thenReturn(mockOverridingIndex); + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - indexFactoryBean.setExpression("someField"); + doThrow(new IndexExistsException("TEST")).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); + + indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setImports("example.DomainType"); - indexFactoryBean.setName("OverridingIndex"); - indexFactoryBean.setType("HASH"); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); - Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "ExistingIndex"); + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isFalse(); - assertSame(mockOverridingIndex, actualIndex); + try { + indexFactoryBean.createIndex(mockQueryService, "TestIndex"); + } + catch (GemfireIndexException expected) { - verifyZeroInteractions(mockOverridingIndex); - verify(mockExistingIndex, times(1)).getName(); - verify(mockQueryService, times(1)).removeIndex(same(mockExistingIndex)); - verify(mockQueryService, times(1)).createHashIndex(eq("ExistingIndex"), eq("someField"), eq("/Example"), - eq("example.DomainType")); + assertThat(expected).hasMessageStartingWith(String.format( + "An Index with a different name [MockIndex] having the same definition [%s] already exists;" + + " You may attempt to override the existing Index [MockIndex] with the new name [TestIndex]" + + " by setting the 'override' property to 'true'", indexFactoryBean.toBasicIndexDefinition())); + + assertThat(expected).hasCauseInstanceOf(IndexExistsException.class); + assertThat(expected.getCause()).hasMessage("TEST"); + assertThat(expected.getCause()).hasNoCause(); + + throw expected; + } + finally { + verify(mockQueryService, times(1)).getIndexes(); + + verify(indexFactoryBean, times(1)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + } + } + + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexExistsExceptionAndCannotFindExistingIndexByDefinition() throws Exception { + + when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexExistsException("TEST")).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); + + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setIgnoreIfExists(true); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.KEY); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + + try { + indexFactoryBean.createIndex(mockQueryService, "TestIndex"); + } + catch (GemfireIndexException expected) { + + assertThat(expected).hasMessageStartingWith(String.format( + "An Index with a different name [unknown] having the same definition [%s] already exists;" + + " You may attempt to override the existing Index [unknown] with the new name [TestIndex]" + + " by setting the 'override' property to 'true'", indexFactoryBean.toBasicIndexDefinition())); + + assertThat(expected).hasCauseInstanceOf(IndexExistsException.class); + assertThat(expected.getCause()).hasMessage("TEST"); + assertThat(expected.getCause()).hasNoCause(); + + throw expected; + } + finally { + verify(mockQueryService, times(1)).getIndexes(); + + verify(indexFactoryBean, times(1)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + } } @Test - public void createIndexReturnsExistingIndex() throws Exception { - Index mockExistingIndex = mock(Index.class, "createIndexReturnsExistingIndex.MockExistingIndex"); - Index mockNewIndex = mock(Index.class, "createIndexReturnsExistingIndex.MockNewIndex"); + public void createIndexThrowsIndexExistsExceptionAndIgnoresThisIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "createIndexReturnsExistingIndex.MockQueryService"); + Index mockIndex = + mockIndexWithDefinition("MockIndex", "id", "/Example", IndexType.PRIMARY_KEY); - when(mockExistingIndex.getName()).thenReturn("ExistingIndex"); - when(mockNewIndex.getName()).thenReturn("NewIndex"); - when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockExistingIndex, mockNewIndex)); + when(mockLog.isWarnEnabled()).thenReturn(true); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); - indexFactoryBean.setName("ExistingIndex"); - indexFactoryBean.setOverride(false); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "ExistingIndex"); + doThrow(new IndexExistsException("TEST")).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); - assertSame(mockExistingIndex, actualIndex); + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setIgnoreIfExists(true); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex); + + verify(indexFactoryBean, times(1)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + + verify(mockLog, times(1)).warn( + eq(String.format("WARNING! You are choosing to ignore this Index [TestIndex] and return the existing Index" + + " having the same basic definition [%s] but with a different name [MockIndex];" + + " Make sure no OQL Query Hints refer to this Index by name [TestIndex]", + indexFactoryBean.toBasicIndexDefinition()))); - verify(mockExistingIndex, times(1)).getName(); - verify(mockNewIndex, never()).getName(); verify(mockQueryService, times(1)).getIndexes(); } @Test - public void createIndexThrowsIndexNameConflictExceptionOnOverride() throws Exception { - Index mockExistingIndex = mock(Index.class, - "createIndexThrowsIndexNameConflictExceptionOnOverride.MockExistingIndex"); + public void createIndexThrowsIndexExistsExceptionAndOverridesExistingIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, - "createIndexThrowsIndexNameConflictExceptionOnOverride.MockQueryService"); + Index mockIndex = + mockIndexWithDefinition("MockIndex", "id", "/Example", IndexType.PRIMARY_KEY); - when(mockExistingIndex.getName()).thenReturn("ExistingIndex"); - when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockExistingIndex)); - when(mockQueryService.createIndex(any(String.class), any(String.class), any(String.class))) - .thenThrow(new IndexNameConflictException("TEST")); + Index testIndex = + mockIndexWithDefinition("TestIndex", "id", "/Example", IndexType.PRIMARY_KEY); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + when(mockLog.isWarnEnabled()).thenReturn(true); - indexFactoryBean.setExpression("someField"); + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexExistsException("TEST")).doReturn(testIndex).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); + + indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setName("ExistingIndex"); - indexFactoryBean.setType("Functional"); - - try { - expectedException.expect(GemfireIndexException.class); - expectedException.expectCause(isA(IndexNameConflictException.class)); - expectedException.expectMessage( - "Failed to remove the existing Index on override before re-creating Index with name (ExistingIndex)"); - - indexFactoryBean.createIndex(mockQueryService, "ExistingIndex"); - } - finally { - verify(mockExistingIndex, times(1)).getName(); - verify(mockQueryService, times(1)).getIndexes(); - verify(mockQueryService, times(1)).createIndex(eq("ExistingIndex"), eq("someField"), eq("/Example")); - } - } - - @Test - public void createIndexThrowsIndexExistsException() throws Exception { - QueryService mockQueryService = mock(QueryService.class, - "createIndexThrowsIndexExistsException.MockQueryService"); - - when(mockQueryService.getIndexes()).thenReturn(null); - when(mockQueryService.createKeyIndex(eq("NewIndex"), eq("someField"), eq("/Example"))) - .thenThrow(new IndexExistsException("TEST")); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); - - indexFactoryBean.setExpression("someField"); - indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setName("NewIndex"); + indexFactoryBean.setIgnoreIfExists(false); + indexFactoryBean.setName("TestIndex"); indexFactoryBean.setOverride(true); - indexFactoryBean.setType("PRIMARY_KEY"); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); - try { - expectedException.expect(GemfireIndexException.class); - expectedException.expectCause(isA(IndexExistsException.class)); - expectedException.expectMessage( - "An Index with a different name having the same definition as this Index (NewIndex) already exists"); + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(testIndex); - indexFactoryBean.createIndex(mockQueryService, "NewIndex"); - } - finally { - verify(mockQueryService, times(1)).getIndexes(); - verify(mockQueryService, never()).removeIndex(any(Index.class)); - verify(mockQueryService, times(1)).createKeyIndex(eq("NewIndex"), eq("someField"), eq("/Example")); - } + verify(indexFactoryBean, times(2)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + + verify(mockLog, times(1)).warn( + eq(String.format("WARNING! You are attempting to 'override' an existing Index [MockIndex]" + + " having the same basic definition [%s] as the Index that will be created by this" + + " IndexFactoryBean [TestIndex]; 'Override' effectively 'renames' the existing Index [MockIndex]" + + " by removing it then recreating it under the new name [TestIndex] with the same definition;" + + " You should be careful to update any existing OQL Query Hints referring to the old" + + " Index name [MockIndex] to now use the new name [TestIndex]", + indexFactoryBean.toBasicIndexDefinition()))); + + verify(mockQueryService, times(1)).getIndexes(); } - @Test - public void createIndexAddsExistingIndexOnAnyException() throws Exception { - final Index mockExistingIndex = mock(Index.class, "createIndexAddsExistingIndexOnAnyException.MockExistingIndex"); - final Index mockIndexTwo = mock(Index.class, "createIndexAddsExistingIndexOnException.MockIndexTwo"); + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexExistsExceptionAndOverrideThrowsRuntimeException() throws Exception { - final List indexes = new ArrayList(1); + Index mockIndex = + mockIndexWithDefinition("MockIndex", "id", "/Example", IndexType.PRIMARY_KEY); - indexes.add(mockIndexTwo); + when(mockLog.isWarnEnabled()).thenReturn(true); - QueryService mockQueryService = mock(QueryService.class, - "createIndexAddsExistingIndexOnAnyException.MockQueryService"); + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)) + .thenReturn(Collections.emptyList()); - when(mockExistingIndex.getName()).thenReturn("ExistingIndex"); - when(mockIndexTwo.getName()).thenReturn("NewIndex"); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); - when(mockQueryService.getIndexes()).then(new Answer>() { - private boolean called = false; + doThrow(new IndexExistsException("TEST")).doThrow(new RuntimeException("RETRY")).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); - private synchronized boolean wasCalledOnce() { - boolean localCalled = this.called; - this.called = true; - return localCalled; - } - - @Override - public Collection answer(final InvocationOnMock invocationOnMock) throws Throwable { - return (wasCalledOnce() ? indexes : Collections.singletonList(mockExistingIndex)); - } - }); - - when(mockQueryService.createIndex(eq("ExistingIndex"), eq("someField"), eq("/Example"))) - .thenThrow(new RuntimeException("TEST")); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); - - indexFactoryBean.setExpression("someField"); + indexFactoryBean.setExpression("id"); indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setName("ExistingIndex"); + indexFactoryBean.setIgnoreIfExists(false); + indexFactoryBean.setName("TestIndex"); indexFactoryBean.setOverride(true); - indexFactoryBean.setType("FUNCTIONAL"); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); - assertEquals(1, indexes.size()); - assertFalse(indexes.contains(mockExistingIndex)); - - Index actualIndex = indexFactoryBean.createIndex(mockQueryService, "ExistingIndex"); - - assertThat(actualIndex, is(sameInstance(mockExistingIndex))); - assertThat(indexes.size(), is(equalTo(2))); - assertThat(indexes.contains(mockExistingIndex), is(true)); - assertThat(indexes.contains(mockIndexTwo), is(true)); - - verify(mockQueryService, times(3)).getIndexes(); - verify(mockQueryService, times(1)).removeIndex(same(mockExistingIndex)); - verify(mockQueryService, times(1)).createIndex(eq("ExistingIndex"), eq("someField"), eq("/Example")); - } - - @Test(expected = RuntimeException.class) - public void createIndexThrowsExceptionOnAbsoluteFailure() throws Exception { - Index mockExistingIndex = mock(Index.class, "createIndexThrowsExceptionOnAbsoluteFailure.MockIndex.Existing"); - - QueryService mockQueryService = mock(QueryService.class, - "createIndexThrowsExceptionOnAbsoluteFailure.MockQueryService"); - - when(mockExistingIndex.getName()).thenReturn("ExistingIndex"); - when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockExistingIndex)); - when(mockQueryService.createIndex(eq("ExistingIndex"), eq("someField"), eq("/Example"))) - .thenThrow(new RuntimeException("test")); - - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); - - indexFactoryBean.setExpression("someField"); - indexFactoryBean.setFrom("/Example"); - indexFactoryBean.setName("ExistingIndex"); + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isTrue(); try { - indexFactoryBean.createIndex(mockQueryService, "ExistingIndex"); + indexFactoryBean.createIndex(mockQueryService, "TestIndex"); } - catch (RuntimeException expected) { - assertEquals("test", expected.getMessage()); + catch (GemfireIndexException expected) { + + assertThat(expected).hasMessageStartingWith( + "Attempt to 'override' existing Index [MockIndex] with the Index that would be created by this" + + " IndexFactoryBean [TestIndex] failed; you should verify the state of your system" + + " and make sure the previously existing Index [MockIndex] still exits"); + + assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class); + assertThat(expected.getCause()).hasMessageStartingWith( + String.format("Failed to create Index [%s]", indexFactoryBean.toDetailedIndexDefinition())); + assertThat(expected.getCause()).hasCauseInstanceOf(RuntimeException.class); + assertThat(expected.getCause().getCause()).hasMessageStartingWith("RETRY"); + assertThat(expected.getCause().getCause()).hasNoCause(); + throw expected; } finally { - verify(mockQueryService, times(2)).getIndexes(); - verify(mockQueryService, times(1)).removeIndex(same(mockExistingIndex)); - verify(mockQueryService, times(1)).createIndex(eq("ExistingIndex"), eq("someField"), eq("/Example")); + + verify(indexFactoryBean, times(2)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + + verify(mockLog, times(1)).warn( + eq(String.format("WARNING! You are attempting to 'override' an existing Index [MockIndex]" + + " having the same basic definition [%s] as the Index that will be created by this" + + " IndexFactoryBean [TestIndex]; 'Override' effectively 'renames' the existing Index [MockIndex]" + + " by removing it then recreating it under the new name [TestIndex] with the same definition;" + + " You should be careful to update any existing OQL Query Hints referring to the old" + + " Index name [MockIndex] to now use the new name [TestIndex]", + indexFactoryBean.toBasicIndexDefinition()))); + + verify(mockQueryService, times(1)).getIndexes(); + } + } + + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexNameConflictException() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("TestIndex", "purchaseDate", "/Orders", IndexType.HASH); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean) + .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class)); + + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setType(IndexType.FUNCTIONAL); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isFalse(); + + try { + indexFactoryBean.createIndex(mockQueryService, "TestIndex"); + } + catch (GemfireIndexException expected) { + + String existingIndexDefinition = String.format(IndexFactoryBean.DETAILED_INDEX_DEFINITION, + mockIndex.getName(), mockIndex.getIndexedExpression(), mockIndex.getFromClause(), "unknown", + IndexType.valueOf(mockIndex.getType())); + + assertThat(expected).hasMessageStartingWith(String.format( + "An Index with the same name [TestIndex] having possibly a different definition already exists;" + + " you may choose to ignore this Index definition [%1$s] and use the existing Index" + + " definition [%2$s] by setting the 'ignoreIfExists' property to 'true'", + indexFactoryBean.toDetailedIndexDefinition(), existingIndexDefinition)); + + assertThat(expected).hasCauseInstanceOf(IndexNameConflictException.class); + assertThat(expected.getCause()).hasMessage("TEST"); + assertThat(expected.getCause()).hasNoCause(); + + throw expected; + } + finally { + verify(indexFactoryBean, times(1)) + .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), + eq("id"), eq("/Example"), any(String.class)); + + verifyZeroInteractions(mockLog); + + verify(mockQueryService, times(1)).getIndexes(); + } + } + + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexNameConflictExceptionAndCannotFindExistingIndexByName() throws Exception { + + when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean) + .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class)); + + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setIgnoreIfExists(true); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.FUNCTIONAL); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + + try { + indexFactoryBean.createIndex(mockQueryService, "TestIndex"); + } + catch (GemfireIndexException expected) { + + assertThat(expected).hasMessageStartingWith(String.format( + "An Index with the same name [TestIndex] having possibly a different definition already exists;" + + " you may choose to ignore this Index definition [%s] and use the existing Index" + + " definition [unknown] by setting the 'ignoreIfExists' property to 'true'", + indexFactoryBean.toDetailedIndexDefinition())); + + assertThat(expected).hasCauseInstanceOf(IndexNameConflictException.class); + assertThat(expected.getCause()).hasMessage("TEST"); + assertThat(expected.getCause()).hasNoCause(); + + throw expected; + } + finally { + verify(indexFactoryBean, times(1)) + .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), + eq("id"), eq("/Example"), any(String.class)); + + verifyZeroInteractions(mockLog); + + verify(mockQueryService, times(1)).getIndexes(); + } + } + + @Test + public void createIndexThrowsIndexNameConflictExceptionAndIgnoresThisIndex() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("TestIndex", "price", "/Orders", IndexType.FUNCTIONAL); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean) + .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class)); + + indexFactoryBean.setExpression("price"); + indexFactoryBean.setFrom("/Orders"); + indexFactoryBean.setIgnoreIfExists(true); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.FUNCTIONAL); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex); + + verify(indexFactoryBean, times(1)) + .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), eq("price"), + eq("/Orders"), any(String.class)); + + verifyZeroInteractions(mockLog); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test + public void createIndexThrowsIndexNameConflictExceptionAndIgnoresThisIndexWithWarning() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("TestIndex", "id", "/Orders", IndexType.PRIMARY_KEY); + + when(mockLog.isWarnEnabled()).thenReturn(true); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean) + .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class)); + + indexFactoryBean.setExpression("price"); + indexFactoryBean.setFrom("/Orders"); + indexFactoryBean.setIgnoreIfExists(true); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.FUNCTIONAL); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex); + + String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION, + "id", "/Orders", IndexType.PRIMARY_KEY); + + verify(indexFactoryBean, times(1)) + .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), eq("price"), + eq("/Orders"), any(String.class)); + + verify(mockLog, times(1)).warn(String.format( + "WARNING! Returning existing Index [TestIndex] having a definition [%1$s] that does not match" + + " the Index defined [%2$s] by this IndexFactoryBean [TestIndex]", + existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition())); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test + public void createIndexThrowsIndexNameConflictExceptionAndOverridesExistingIndex() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("TestIndex", "id", "/Example", IndexType.PRIMARY_KEY); + + Index testIndex = + mockIndexWithDefinition("TestIndex", "id", "/Example", IndexType.PRIMARY_KEY); + + assertThat(mockIndex).isNotSameAs(testIndex); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).doReturn(testIndex).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); + + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setIgnoreIfExists(false); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + + // NOTE: this test case is also testing the smart "override" handling logic, which just + // returns the existing Index if both the name and definition are the same on when an + // IndexNameConflictException is thrown. + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex); + + verify(indexFactoryBean, times(1)) + .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example")); + + verifyZeroInteractions(mockLog); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test + public void createIndexThrowsIndexNameConflictExceptionAndOverridesExistingIndexWithWarning() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("TestIndex", "price", "/Orders", IndexType.FUNCTIONAL); + + Index testIndex = + mockIndexWithDefinition("TestIndex", "purchaseDate", "/Orders", IndexType.HASH); + + assertThat(mockIndex).isNotSameAs(testIndex); + + when(mockLog.isWarnEnabled()).thenReturn(true); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).doReturn(testIndex).when(indexFactoryBean) + .createHashIndex(any(QueryService.class), anyString(), anyString(), anyString(), any(String.class)); + + indexFactoryBean.setExpression("purchaseDate"); + indexFactoryBean.setFrom("/Orders"); + indexFactoryBean.setIgnoreIfExists(false); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.HASH); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(testIndex); + + String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION, + mockIndex.getIndexedExpression(), mockIndex.getFromClause(), IndexType.valueOf(mockIndex.getType())); + + verify(indexFactoryBean, times(2)).createHashIndex(eq(mockQueryService), + eq("TestIndex"), eq("purchaseDate"), eq("/Orders"), any(String.class)); + + verify(mockLog, times(1)).warn(eq(String.format( + "WARNING! Overriding existing Index [TestIndex] having a definition [%1$s] that does not match" + + " the Index defined [%2$s] by this IndexFactoryBean [TestIndex]", + existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition()))); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test(expected = GemfireIndexException.class) + public void createIndexThrowsIndexNameConflictExceptionAndOverrideThrowsRuntimeException() throws Exception { + + Index mockIndex = + mockIndexWithDefinition("MockIndex", "purchaseDate", "/Example", + IndexType.HASH); + + when(mockLog.isWarnEnabled()).thenReturn(true); + + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)) + .thenReturn(Collections.emptyList()); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doThrow(new IndexNameConflictException("TEST")).doThrow(new RuntimeException("RETRY")).when(indexFactoryBean) + .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString()); + + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Example"); + indexFactoryBean.setIgnoreIfExists(false); + indexFactoryBean.setName("MockIndex"); + indexFactoryBean.setOverride(true); + indexFactoryBean.setType(IndexType.PRIMARY_KEY); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + + try { + indexFactoryBean.createIndex(mockQueryService, "MockIndex"); + } + catch (GemfireIndexException expected) { + + assertThat(expected).hasMessageStartingWith( + "Attempt to 'override' existing Index [MockIndex] with the Index that would be created by this" + + " IndexFactoryBean [MockIndex] failed; you should verify the state of your system" + + " and make sure the previously existing Index [MockIndex] still exits"); + + assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class); + assertThat(expected.getCause()).hasMessageStartingWith( + String.format("Failed to create Index [%s]", indexFactoryBean.toDetailedIndexDefinition())); + assertThat(expected.getCause()).hasCauseInstanceOf(RuntimeException.class); + assertThat(expected.getCause().getCause()).hasMessageStartingWith("RETRY"); + assertThat(expected.getCause().getCause()).hasNoCause(); + + throw expected; + } + finally { + + String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION, + mockIndex.getIndexedExpression(), mockIndex.getFromClause(), IndexType.valueOf(mockIndex.getType())); + + verify(indexFactoryBean, times(2)) + .createKeyIndex(eq(mockQueryService), eq("MockIndex"), eq("id"), eq("/Example")); + + verify(mockLog, times(1)).warn(eq(String.format( + "WARNING! Overriding existing Index [MockIndex] having a definition [%1$s] that does not match" + + " the Index defined [%2$s] by this IndexFactoryBean [MockIndex]", + existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition()))); + + verify(mockQueryService, times(1)).getIndexes(); } } @Test public void createFunctionalIndex() throws Exception { - Index mockIndex = mock(Index.class, "testCreateFunctionalIndex.MockIndex"); - QueryService mockQueryService = mock(QueryService.class, "testCreateFunctionalIndex.MockQueryService"); + Index mockIndex = mockIndex("testCreateFunctionalIndex.MockIndex"); when(mockQueryService.createIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders"))) .thenReturn(mockIndex); @@ -671,275 +1207,436 @@ public class IndexFactoryBeanTest { Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, "FunctionalIndex", "purchaseDate", "/Orders", null); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); - verify(mockQueryService, times(1)).createIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")); + verify(mockQueryService, times(1)) + .createIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")); verify(mockQueryService, never()).defineIndex(anyString(), anyString(), anyString()); } @Test public void createFunctionalIndexWithImports() throws Exception { - Index mockIndex = mock(Index.class, "testCreateFunctionalIndexWithImports.MockIndex"); - QueryService mockQueryService = mock(QueryService.class, "testCreateFunctionalIndexWithImports.MockQueryService"); + Index mockIndex = mockIndex("testCreateFunctionalIndexWithImports.MockIndex"); - when(mockQueryService.createIndex(eq("FunctionalIndexWithImports"), eq("purchaseDate"), eq("/Orders"), - eq("org.example.Order"))).thenReturn(mockIndex); + when(mockQueryService.createIndex(eq("FunctionalIndexWithImports"), eq("purchaseDate"), + eq("/Orders"), eq("example.Order"))).thenReturn(mockIndex); Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, "FunctionalIndexWithImports", - "purchaseDate", "/Orders", "org.example.Order"); + "purchaseDate", "/Orders", "example.Order"); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); + + verify(mockQueryService, times(1)) + .createIndex(eq("FunctionalIndexWithImports"), eq("purchaseDate"), eq("/Orders"), + eq("example.Order")); - verify(mockQueryService, times(1)).createIndex(eq("FunctionalIndexWithImports"), eq("purchaseDate"), - eq("/Orders"), eq("org.example.Order")); verify(mockQueryService, never()).defineIndex(anyString(), anyString(), anyString(), anyString()); } @Test public void defineFunctionalIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testDefineFunctionalIndex.MockQueryService"); - - doAnswer(new Answer() { - @Override public Void answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(mockQueryService).defineIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")); indexFactoryBean.setDefine(true); Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, "FunctionalIndex", "purchaseDate", "/Orders", null); - assertTrue(actualIndex instanceof IndexFactoryBean.IndexWrapper); - assertSame(mockQueryService, ((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()); - assertEquals("FunctionalIndex", ((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()); + assertThat(actualIndex).isInstanceOf(IndexFactoryBean.IndexWrapper.class); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()).isEqualTo("FunctionalIndex"); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()).isSameAs(mockQueryService); verify(mockQueryService, never()).createIndex(anyString(), anyString(), anyString()); - verify(mockQueryService, times(1)).defineIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")); + verify(mockQueryService, times(1)) + .defineIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")); } @Test public void defineFunctionalIndexWithImports() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testDefineFunctionalIndexWithImports.MockQueryService"); - - doAnswer(new Answer() { - @Override public Void answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(mockQueryService).defineIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders"), - eq("org.example.Order")); indexFactoryBean.setDefine(true); - Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, "FunctionalIndexWithImports", - "purchaseDate", "/Orders", "org.example.Order"); + Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, + "FunctionalIndexWithImports", "purchaseDate", "/Orders", "example.Order"); - assertTrue(actualIndex instanceof IndexFactoryBean.IndexWrapper); - assertSame(mockQueryService, ((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()); - assertEquals("FunctionalIndexWithImports", ((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()); + assertThat(actualIndex).isInstanceOf(IndexFactoryBean.IndexWrapper.class); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()).isEqualTo("FunctionalIndexWithImports"); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()).isSameAs(mockQueryService); verify(mockQueryService, never()).createIndex(anyString(), anyString(), anyString(), anyString()); - verify(mockQueryService, times(1)).defineIndex(eq("FunctionalIndexWithImports"), eq("purchaseDate"), - eq("/Orders"), eq("org.example.Order")); + verify(mockQueryService, times(1)).defineIndex(eq("FunctionalIndexWithImports"), + eq("purchaseDate"), eq("/Orders"), eq("example.Order")); } @Test public void createHashIndex() throws Exception { - Index mockIndex = mock(Index.class, "testCreateHashIndex.MockIndex"); - QueryService mockQueryService = mock(QueryService.class, "testCreateHashIndex.MockQueryService"); + Index mockIndex = mockIndex("testCreateHashIndex.MockIndex"); - when(mockQueryService.createHashIndex(eq("HashIndex"), eq("name"), eq("/People"))).thenReturn(mockIndex); + when(mockQueryService.createHashIndex(eq("HashIndex"), eq("name"), eq("/People"))) + .thenReturn(mockIndex); - Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndex", "name", "/People", " "); + Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndex", + "name", "/People", " "); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); + + verify(mockQueryService, times(1)) + .createHashIndex(eq("HashIndex"), eq("name"), eq("/People")); - verify(mockQueryService, times(1)).createHashIndex(eq("HashIndex"), eq("name"), eq("/People")); verify(mockQueryService, never()).defineHashIndex(anyString(), anyString(), anyString()); } @Test public void createHashIndexWithImports() throws Exception { - Index mockIndex = mock(Index.class, "testCreateHashIndexWithImports.MockIndex"); - QueryService mockQueryService = mock(QueryService.class, "testCreateHashIndexWithImports.MockQueryService"); + Index mockIndex = mockIndex("testCreateHashIndexWithImports.MockIndex"); when(mockQueryService.createHashIndex(eq("HashIndexWithImports"), eq("name"), eq("/People"), - eq("org.example.Person"))).thenReturn(mockIndex); + eq("example.Person"))).thenReturn(mockIndex); Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndexWithImports", - "name", "/People", "org.example.Person"); + "name", "/People", "example.Person"); - assertSame(mockIndex, actualIndex); + assertThat(actualIndex).isSameAs(mockIndex); + + verify(mockQueryService, times(1)).createHashIndex(eq("HashIndexWithImports"), + eq("name"), eq("/People"), eq("example.Person")); - verify(mockQueryService, times(1)).createHashIndex(eq("HashIndexWithImports"), eq("name"), eq("/People"), - eq("org.example.Person")); verify(mockQueryService, never()).defineHashIndex(anyString(), anyString(), anyString(), anyString()); } @Test public void defineHashIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testDefineHashIndex.MockQueryService"); - - doAnswer(new Answer() { - @Override public Void answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(mockQueryService).defineHashIndex(eq("HashIndex"), eq("name"), eq("/People")); indexFactoryBean.setDefine(true); Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndex", "name", "/People", " "); - assertTrue(actualIndex instanceof IndexFactoryBean.IndexWrapper); - assertSame(mockQueryService, ((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()); - assertEquals("HashIndex", ((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()); + assertThat(actualIndex).isInstanceOf(IndexFactoryBean.IndexWrapper.class); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()).isEqualTo("HashIndex"); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()).isSameAs(mockQueryService); verify(mockQueryService, never()).createHashIndex(anyString(), anyString(), anyString()); - verify(mockQueryService, times(1)).defineHashIndex(eq("HashIndex"), eq("name"), eq("/People")); + verify(mockQueryService, times(1)) + .defineHashIndex(eq("HashIndex"), eq("name"), eq("/People")); } @Test - public void defineHashIndexWithImports() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testDefineHashIndexWithImports.MockQueryService"); - - doAnswer(new Answer() { - @Override public Void answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(mockQueryService).defineHashIndex(eq("HashIndexWithImports"), eq("name"), eq("/People"), - eq("org.example.Person")); + public void defineHashIndexWithImport() throws Exception { indexFactoryBean.setDefine(true); Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndexWithImports", - "name", "/People", "org.example.Person"); + "name", "/People", "example.Person"); - assertTrue(actualIndex instanceof IndexFactoryBean.IndexWrapper); - assertSame(mockQueryService, ((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()); - assertEquals("HashIndexWithImports", ((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()); + assertThat(actualIndex).isInstanceOf(IndexFactoryBean.IndexWrapper.class); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()).isEqualTo("HashIndexWithImports"); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()).isSameAs(mockQueryService); verify(mockQueryService, never()).createHashIndex(anyString(), anyString(), anyString(), anyString()); - verify(mockQueryService, times(1)).defineHashIndex(eq("HashIndexWithImports"), eq("name"), eq("/People"), - eq("org.example.Person")); + verify(mockQueryService, times(1)).defineHashIndex(eq("HashIndexWithImports"), + eq("name"), eq("/People"), eq("example.Person")); } @Test public void createKeyIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testCreateKeyIndex.MockQueryService"); - Index mockIndex = mock(Index.class, "testCreateKeyIndex.MockKeyIndex"); + Index mockIndex = mockIndex("testCreateKeyIndex.MockKeyIndex"); - when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"))).thenReturn(mockIndex); + when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"))) + .thenReturn(mockIndex); - indexFactoryBean.setDefine(false); + Index actualIndex = indexFactoryBean.createKeyIndex(mockQueryService, "KeyIndex", + "id", "/Example"); - Index actualIndex = indexFactoryBean.createKeyIndex(mockQueryService, "KeyIndex", "id", "/Example"); + assertThat(actualIndex).isSameAs(mockIndex); - assertSame(mockIndex, actualIndex); + verify(mockQueryService, times(1)) + .createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")); - verify(mockQueryService, times(1)).createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")); verify(mockQueryService, never()).defineKeyIndex(anyString(), anyString(), anyString()); } @Test public void defineKeyIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testDefineKeyIndex.MockQueryService"); indexFactoryBean.setDefine(true); - Index actualIndex = indexFactoryBean.createKeyIndex(mockQueryService, "KeyIndex", "id", "/Example"); + Index actualIndex = indexFactoryBean.createKeyIndex(mockQueryService, "KeyIndex", + "id", "/Example"); - assertTrue(actualIndex instanceof IndexFactoryBean.IndexWrapper); - - IndexFactoryBean.IndexWrapper indexWrapper = (IndexFactoryBean.IndexWrapper) actualIndex; - - assertSame(mockQueryService, indexWrapper.getQueryService()); - assertEquals("KeyIndex", indexWrapper.getIndexName()); + assertThat(actualIndex).isInstanceOf(IndexFactoryBean.IndexWrapper.class); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getIndexName()).isEqualTo("KeyIndex"); + assertThat(((IndexFactoryBean.IndexWrapper) actualIndex).getQueryService()).isSameAs(mockQueryService); verify(mockQueryService, never()).createKeyIndex(anyString(), anyString(), anyString()); - verify(mockQueryService, times(1)).defineKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")); + verify(mockQueryService, times(1)).defineKeyIndex(eq("KeyIndex"), + eq("id"), eq("/Example")); } @Test - public void getExistingIndex() { - Index mockIndexOne = mock(Index.class, "testGetExistingIndex.MockIndex.One"); - Index mockIndexTwo = mock(Index.class, "testGetExistingIndex.MockIndex.Two"); - Index mockIndexThree = mock(Index.class, "testGetExistingIndex.MockIndex.Two"); + public void tryToFindExistingIndexByDefinitionReturnsIndex() { - QueryService mockQueryService = mock(QueryService.class, "testGetExistingIndex.MockQueryService"); + Index mockIndexOne = + mockIndexWithDefinition("PrimaryIndex", "id", "/People", IndexType.HASH); + + Index mockIndexTwo = + mockIndexWithDefinition("SecondaryIndex", "id", "/People", IndexType.PRIMARY_KEY); + + Index mockIndexThree = + mockIndexWithDefinition("TernaryIndex", "purchaseDate", "/Orders", + IndexType.FUNCTIONAL); - when(mockIndexOne.getName()).thenReturn("PrimaryIndex"); - when(mockIndexTwo.getName()).thenReturn("SecondaryIndex"); - when(mockIndexThree.getName()).thenReturn("TernaryIndex"); when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree)); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, null)); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, "")); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, " ")); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, "Primary Index")); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, "Secondary_Index")); - assertNull(indexFactoryBean.getExistingIndex(mockQueryService, "QuadIndex")); - assertSame(mockIndexOne, indexFactoryBean.getExistingIndex(mockQueryService, "PRIMARYINDEX")); - assertSame(mockIndexTwo, indexFactoryBean.getExistingIndex(mockQueryService, "SecondaryIndex")); - assertSame(mockIndexThree, indexFactoryBean.getExistingIndex(mockQueryService, "ternaryindex")); + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/People", IndexType.PRIMARY_KEY)).isSameAs(mockIndexTwo); + + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "purchaseDate", "/Orders", IndexType.FUNCTIONAL)).isSameAs(mockIndexThree); + + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/People", IndexType.HASH)).isSameAs(mockIndexOne); + + verify(mockQueryService, times(3)).getIndexes(); } @Test - public void getObjectLooksUpIndex() throws Exception { - QueryService mockQueryService = mock(QueryService.class, "testGetObjectLooksUpIndex.MockQueryService"); + public void tryToFindExistingIndexByDefinitionReturnsNull() { - when(mockQueryService.getIndexes()).then(new Answer>() { - private final AtomicInteger counter = new AtomicInteger(1); - @Override public Collection answer(final InvocationOnMock invocation) throws Throwable { - Index mockIndex = mock(Index.class, "getObjectLooksUpIndex.MockIndex"); - when(mockIndex.getName()).thenReturn(String.format("MockIndex%1$s", counter.getAndIncrement())); - return Collections.singletonList(mockIndex); - } - }); + Index mockIndex = + mockIndexWithDefinition("PrimaryIndex", "id", "/People", IndexType.HASH); - IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); - indexFactoryBean.setQueryService(mockQueryService); - indexFactoryBean.setName("MockIndex1"); - ReflectionUtils.setField(IndexFactoryBean.class.getDeclaredField("indexName"), indexFactoryBean, "MockIndex1"); + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "key", "/People", IndexType.HASH)).isNull(); - Index actualIndex = indexFactoryBean.getObject(); + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/Persons", IndexType.HASH)).isNull(); - assertNotNull(actualIndex); - assertEquals("MockIndex1", actualIndex.getName()); + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/People", IndexType.KEY)).isNull(); - Index sameIndex = indexFactoryBean.getObject(); - - assertSame(actualIndex, sameIndex); + verify(mockQueryService, times(3)).getIndexes(); } @Test - public void indexFactoryBeanCreatesSingleIndex() { - assertTrue(indexFactoryBean.isSingleton()); + public void tryToFindExistingIndexByDefinitionWithQueryServiceHavingNoIndexesReturnsNull() { + + when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); + + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/People", IndexType.KEY)).isNull(); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test + public void tryToFindExistingIndexByDefinitionWithQueryServiceReturningNullIndexesReturnsNull() { + + when(mockQueryService.getIndexes()).thenReturn(null); + + assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService, + "id", "/People", IndexType.KEY)).isNull(); + + verify(mockQueryService, times(1)).getIndexes(); + } + + @Test + public void tryToFindExistingIndexByNameReturnsIndex() { + + Index mockIndexOne = mockIndex("PrimaryIndex"); + Index mockIndexTwo = mockIndex("SecondaryIndex"); + Index mockIndexThree = mockIndex("TernaryIndex"); + + when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree)); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "PRIMARYINDEX")) + .isSameAs(mockIndexOne); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "SecondaryIndex")) + .isSameAs(mockIndexTwo); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "ternaryindex")) + .isSameAs(mockIndexThree); + + verify(mockQueryService, times(3)).getIndexes(); + } + + @Test + public void tryToFindExistingIndexByNameReturnsNull() { + + Index mockIndexOne = mockIndex("PrimaryIndex"); + Index mockIndexTwo = mockIndex("SecondaryIndex"); + Index mockIndexThree = mockIndex("TernaryIndex"); + + when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree)); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, null)).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, " ")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Primary Index")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Secondary_Index")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "QuadIndex")).isNull(); + + verify(mockQueryService, times(6)).getIndexes(); + } + + @Test + public void tryToFindExistingIndexByNameWithQueryServiceHavingNoIndexesReturnsNull() { + + when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")).isNull(); + + verify(mockQueryService, times(3)).getIndexes(); + } + + @Test + public void tryToFindExistingIndexByNameWithQueryServiceReturningNullIndexesReturnsNull() { + + when(mockQueryService.getIndexes()).thenReturn(null); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")).isNull(); + + assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")).isNull(); + + verify(mockQueryService, times(3)).getIndexes(); + } + + @Test + public void getObjectReturnsFactoryIndex() throws Exception { + + Index mockIndex = mockIndex("testGetObjectReturnsFactoryIndex.MockIndex"); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doReturn(mockIndex).when(indexFactoryBean).createIndex(eq(mockQueryService), + eq("testGetObjectReturnsFactoryIndex.MockIndex")); + + indexFactoryBean.setName("testGetObjectReturnsFactoryIndex.MockIndex"); + indexFactoryBean.setExpression("id"); + indexFactoryBean.setFrom("/Mock"); + indexFactoryBean.afterPropertiesSet(); + + assertThat(indexFactoryBean.getIndex()).isEqualTo(mockIndex); + assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex); + assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex); + + verify(indexFactoryBean, times(1)) + .createIndex(eq(mockQueryService), eq("testGetObjectReturnsFactoryIndex.MockIndex")); + } + + @Test + public void getObjectReturnsLookedUpIndex() throws Exception { + + Index mockIndexOne = mockIndex("MockIndexOne"); + Index mockIndexTwo = mockIndex("MockIndexTwo"); + + when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo)); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setName("MockIndexTwo"); + + assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndexTwo); + } + + @Test + public void getObjectReturnsNull() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + indexFactoryBean.setName("TestIndex"); + + assertThat(indexFactoryBean.getObject()).isNull(); + } + + @Test + public void getObjectTypeReturnsIndexClassType() { + assertThat(newIndexFactoryBean().getObjectType()).isEqualTo(Index.class); + } + + @Test + @SuppressWarnings("all") + public void getObjectTypeReturnsMockIndexClassType() throws Exception { + + Index mockIndex = mockIndex("MockIndex"); + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + doReturn(mockIndex).when(indexFactoryBean).getIndex(); + + assertThat(indexFactoryBean.getObjectType()).isEqualTo(mockIndex.getClass()); + } + + @Test + public void isSingletonReturnsTrue() { + assertThat(indexFactoryBean.isSingleton()).isTrue(); + } + + @Test + public void setAndIsIgnoreIfExists() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + + indexFactoryBean.setIgnoreIfExists(true); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + + indexFactoryBean.setIgnoreIfExists(false); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse(); + } + + @Test + public void setAndIsOverride() { + + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + + assertThat(indexFactoryBean.isOverride()).isFalse(); + + indexFactoryBean.setOverride(true); + + assertThat(indexFactoryBean.isOverride()).isTrue(); + + indexFactoryBean.setOverride(false); + + assertThat(indexFactoryBean.isOverride()).isFalse(); } @Test public void defineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext() throws Exception { + ConfigurableBeanFactory mockBeanFactory = mock(ConfigurableBeanFactory.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockBeanFactory"); - Cache mockCacheOne = mock(Cache.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockCacheOne"); - Cache mockCacheTwo = mock(Cache.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockCacheTwo"); + Cache mockCacheOne = + mock(Cache.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockCacheOne"); - QueryService mockQueryServiceOne = mock(QueryService.class, - "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockQueryServiceOne"); - - QueryService mockQueryServiceTwo = mock(QueryService.class, - "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockQueryServiceTwo"); + Cache mockCacheTwo = + mock(Cache.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockCacheTwo"); final AtomicReference queryServiceReference = new AtomicReference(null); doAnswer(new Answer() { - @Override public Boolean answer(final InvocationOnMock invocation) throws Throwable { + @Override public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable { return (queryServiceReference.get() != null); } }).when(mockBeanFactory).containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); @@ -961,8 +1658,7 @@ public class IndexFactoryBeanTest { }).when(mockBeanFactory).registerSingleton(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), any(QueryService.class)); - when(mockCacheOne.getQueryService()).thenReturn(mockQueryServiceOne); - when(mockCacheTwo.getQueryService()).thenReturn(mockQueryServiceTwo); + when(mockCacheOne.getQueryService()).thenReturn(mockQueryService); IndexFactoryBean indexFactoryBeanOne = new IndexFactoryBean(); @@ -987,31 +1683,36 @@ public class IndexFactoryBeanTest { indexFactoryBeanTwo.setType("HASH"); indexFactoryBeanTwo.afterPropertiesSet(); - verify(mockBeanFactory, times(2)).containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); - verify(mockBeanFactory, times(1)).getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), - eq(QueryService.class)); - verify(mockBeanFactory, times(1)).registerSingleton(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), - same(mockQueryServiceOne)); + verify(mockBeanFactory, times(2)) + .containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)); + + verify(mockBeanFactory, times(1)) + .getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), eq(QueryService.class)); + + verify(mockBeanFactory, times(1)) + .registerSingleton(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), + same(mockQueryService)); + verify(mockCacheOne, times(1)).getQueryService(); verify(mockCacheTwo, never()).getQueryService(); - verify(mockQueryServiceOne, times(1)).defineKeyIndex(eq("PersonIdIndex"), eq("id"), eq("/People")); - verify(mockQueryServiceOne, times(1)).defineHashIndex(eq("PurchaseDateIndex"), eq("purchaseDate"), eq("/Orders"), - eq("org.example.Order")); - verify(mockQueryServiceTwo, never()).defineHashIndex(anyString(), anyString(), anyString()); - verify(mockQueryServiceTwo, never()).defineHashIndex(anyString(), anyString(), anyString()); - verify(mockQueryServiceTwo, never()).defineIndex(anyString(), anyString(), anyString()); - verify(mockQueryServiceTwo, never()).defineIndex(anyString(), anyString(), anyString(), anyString()); - verify(mockQueryServiceTwo, never()).defineKeyIndex(anyString(), anyString(), anyString()); + + verify(mockQueryService, times(1)) + .defineKeyIndex(eq("PersonIdIndex"), eq("id"), eq("/People")); + + verify(mockQueryService, times(1)) + .defineHashIndex(eq("PurchaseDateIndex"), eq("purchaseDate"), eq("/Orders"), + eq("org.example.Order")); } @Test @SuppressWarnings("deprecation") public void indexWrapperDelegation() { - Index mockIndex = mock(Index.class, "indexWrapperDelegation.MockIndex"); - IndexStatistics mockIndexStats = mock(IndexStatistics.class, "indexWrapperDelegation.MockIndexStats"); + Index mockIndex = mockIndex("testIndexWrapperDelegation.MockIndex"); - QueryService mockQueryService = mock(QueryService.class, "indexWrapperDelegation.MockQueryService"); + IndexStatistics mockIndexStats = mock(IndexStatistics.class, "testIndexWrapperDelegation.MockIndexStats"); + + QueryService mockQueryService = mock(QueryService.class, "testIndexWrapperDelegation.MockQueryService"); when(mockIndex.getCanonicalizedFromClause()).thenReturn("/Example"); when(mockIndex.getCanonicalizedIndexedExpression()).thenReturn("ID"); @@ -1024,28 +1725,30 @@ public class IndexFactoryBeanTest { when(mockIndex.getType()).thenReturn(com.gemstone.gemfire.cache.query.IndexType.HASH); when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex)); - IndexFactoryBean.IndexWrapper indexWrapper = new IndexFactoryBean.IndexWrapper(mockQueryService, "MockIndex"); + IndexFactoryBean.IndexWrapper indexWrapper = + new IndexFactoryBean.IndexWrapper(mockQueryService, "MockIndex"); - assertNotNull(indexWrapper); - assertEquals("MockIndex", indexWrapper.getIndexName()); - assertSame(mockQueryService, indexWrapper.getQueryService()); + assertThat(indexWrapper).isNotNull(); + assertThat(indexWrapper.getIndexName()).isEqualTo("MockIndex"); + assertThat(indexWrapper.getQueryService()).isEqualTo(mockQueryService); - Index actualIndex = indexWrapper.getIndex(); + Index actualIndex = indexWrapper.resolveIndex(); - assertSame(mockIndex, actualIndex); - assertEquals("/Example", indexWrapper.getCanonicalizedFromClause()); - assertEquals("ID", indexWrapper.getCanonicalizedIndexedExpression()); - assertEquals("identifier", indexWrapper.getCanonicalizedProjectionAttributes()); - assertEquals("Example", indexWrapper.getFromClause()); - assertEquals("id", indexWrapper.getIndexedExpression()); - assertEquals("MockIndex", indexWrapper.getName()); - assertEquals("id", indexWrapper.getProjectionAttributes()); - assertSame(mockIndexStats, indexWrapper.getStatistics()); - assertEquals(com.gemstone.gemfire.cache.query.IndexType.HASH, indexWrapper.getType()); + assertThat(actualIndex).isSameAs(mockIndex); - Index sameIndex = indexWrapper.getIndex(); + assertThat(indexWrapper.getCanonicalizedFromClause()).isEqualTo("/Example"); + assertThat(indexWrapper.getCanonicalizedIndexedExpression()).isEqualTo("ID"); + assertThat(indexWrapper.getCanonicalizedProjectionAttributes()).isEqualTo("identifier"); + assertThat(indexWrapper.getFromClause()).isEqualTo("Example"); + assertThat(indexWrapper.getIndexedExpression()).isEqualTo("id"); + assertThat(indexWrapper.getName()).isEqualTo("MockIndex"); + assertThat(indexWrapper.getProjectionAttributes()).isEqualTo("id"); + assertThat(indexWrapper.getStatistics()).isSameAs(mockIndexStats); + assertThat(indexWrapper.getType()).isEqualTo(com.gemstone.gemfire.cache.query.IndexType.HASH); - assertSame(actualIndex, sameIndex); + Index sameIndex = indexWrapper.resolveIndex(); + + assertThat(sameIndex).isSameAs(actualIndex); } @Test(expected = IllegalArgumentException.class) @@ -1054,42 +1757,46 @@ public class IndexFactoryBeanTest { new IndexFactoryBean.IndexWrapper(null, "TestIndex"); } catch (IllegalArgumentException expected) { - assertEquals("QueryService must not be null", expected.getMessage()); + assertThat(expected).hasMessage("QueryService is required"); + assertThat(expected).hasNoCause(); + throw expected; } } @Test(expected = IllegalArgumentException.class) - public void createIndexWrapperWithUnspecifiedIndexName() { + public void createIndexWrapperWithNoIndexName() { try { - new IndexFactoryBean.IndexWrapper(mock(QueryService.class), " "); + new IndexFactoryBean.IndexWrapper(mockQueryService, " "); } catch (IllegalArgumentException expected) { - assertEquals("The name of the Index must be specified!", expected.getMessage()); + assertThat(expected).hasMessage("Name of Index is required"); + assertThat(expected).hasNoCause(); + throw expected; } } @Test(expected = GemfireIndexException.class) - public void indexWrapperGetIndexWhenIndexNotFound() { - QueryService mockQueryService = mock(QueryService.class, "indexWrapperGetIndexWhenIndexNotFound.MockQueryService"); + public void indexWrapperGetIndexThrowsExceptionWhenIndexNotFound() { when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); - IndexFactoryBean.IndexWrapper indexWrapper = new IndexFactoryBean.IndexWrapper(mockQueryService, "NonExistingIndex"); + IndexFactoryBean.IndexWrapper indexWrapper = + new IndexFactoryBean.IndexWrapper(mockQueryService, "NonExistingIndex"); - assertNotNull(indexWrapper); - assertEquals("NonExistingIndex", indexWrapper.getIndexName()); - assertSame(mockQueryService, indexWrapper.getQueryService()); + assertThat(indexWrapper).isNotNull(); + assertThat(indexWrapper.getIndexName()).isEqualTo("NonExistingIndex"); + assertThat(indexWrapper.getQueryService()).isSameAs(mockQueryService); try { - indexWrapper.getIndex(); + indexWrapper.resolveIndex(); } catch (GemfireIndexException expected) { - assertThat(expected.getMessage(), startsWith("index with name (NonExistingIndex) was not found")); - assertTrue(expected.getCause() instanceof IndexInvalidException); + assertThat(expected).hasMessage("Index with name [NonExistingIndex] was not found"); + assertThat(expected).hasNoCause(); + throw expected; } } - } 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 index b1d156dc..1fbfe4ed 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexesConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexesConfigurationUnitTests.java @@ -20,17 +20,21 @@ 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.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Map; +import java.util.Set; import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; import com.gemstone.gemfire.cache.RegionAttributes; import com.gemstone.gemfire.cache.RegionFactory; import com.gemstone.gemfire.cache.RegionShortcut; import com.gemstone.gemfire.cache.query.Index; import com.gemstone.gemfire.cache.query.QueryService; +import com.gemstone.gemfire.internal.concurrent.ConcurrentHashSet; import org.junit.After; import org.junit.Test; @@ -42,6 +46,7 @@ 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; @@ -62,8 +67,22 @@ import org.springframework.data.gemfire.config.annotation.test.entities.Replicat */ public class EnableIndexesConfigurationUnitTests { + private static final Set indexes = new ConcurrentHashSet(); + private ConfigurableApplicationContext applicationContext; + /* (non-Javadoc) */ + private static Index findIndexByName(String indexName) { + + for (Index index : indexes) { + if (index.getName().equalsIgnoreCase(indexName)) { + return index; + } + } + + return null; + } + @After public void tearDown() { if (applicationContext != null) { @@ -115,22 +134,46 @@ public class EnableIndexesConfigurationUnitTests { assertThat(indexes.isEmpty()).isTrue(); } + @Test + public void indexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinition() { + + applicationContext = newApplicationContext( + IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinitionConfiguration.class); + + Index firstNameIndex = applicationContext.getBean("LoyalCustomersFirstNameFunctionalIdx", Index.class); + + assertIndex(firstNameIndex, "LoyalCustomersFirstNameFunctionalIdx", + "first_name", "/LoyalCustomers", IndexType.FUNCTIONAL); + + assertThat(findIndexByName("CustomersFirstNameFunctionalIdx")).isNull(); + } + + @Test + public void indexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameName() { + + applicationContext = newApplicationContext( + IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameNameConfiguration.class); + + Index lastNameIndex = applicationContext.getBean("LastNameIdx", Index.class); + + assertIndex(lastNameIndex, "LastNameIdx", "last_name", "/People", IndexType.HASH); + } + @Configuration @SuppressWarnings("unused") - static class CacheConfiguration { + static class GemFireConfiguration { @Bean @SuppressWarnings("unchecked") Cache gemfireCache() throws Exception { - Cache mockCache = mock(Cache.class); + return mockQueryService(mockRegionFactory(mock(Cache.class, "MockGemFireCache"))); + } + + Cache mockQueryService(Cache mockCache) throws Exception { + 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()); @@ -141,6 +184,34 @@ public class EnableIndexesConfigurationUnitTests { when(mockQueryService.createKeyIndex(anyString(), anyString(), anyString())) .thenAnswer(new KeyIndexAnswer()); + when(mockQueryService.getIndexes()).thenReturn(indexes); + + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Index indexToRemove = invocation.getArgumentAt(0, Index.class); + + indexes.remove(findIndexByName(indexToRemove.getName())); + + return null; + } + + }).when(mockQueryService).removeIndex(any(Index.class)); + + 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; } } @@ -196,7 +267,7 @@ public class EnableIndexesConfigurationUnitTests { excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, LocalRegionEntity.class, ReplicateRegionEntity.class })) - static class IndexedPersistentEntityConfiguration extends CacheConfiguration { + static class IndexedPersistentEntityConfiguration extends GemFireConfiguration { } @@ -204,7 +275,55 @@ public class EnableIndexesConfigurationUnitTests { excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class, LocalRegionEntity.class, ReplicateRegionEntity.class })) - static class NoIndexesCreatedForIndexedPersistentEntityConfiguration extends CacheConfiguration { + static class NoIndexesCreatedForIndexedPersistentEntityConfiguration extends GemFireConfiguration { } + + @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 IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinitionConfiguration + extends GemFireConfiguration { + + @Bean + @SuppressWarnings("unused") + IndexFactoryBean firstNameIndex(GemFireCache gemfireCache) { + + IndexFactoryBean firstNameIndex = new IndexFactoryBean(); + + firstNameIndex.setCache(gemfireCache); + firstNameIndex.setName("LoyalCustomersFirstNameFunctionalIdx"); + firstNameIndex.setExpression("first_name"); + firstNameIndex.setFrom("/LoyalCustomers"); + firstNameIndex.setType(IndexType.FUNCTIONAL); + + return firstNameIndex; + } + } + + @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 IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameNameConfiguration + extends GemFireConfiguration { + + @Bean + @SuppressWarnings("unused") + IndexFactoryBean lastNameIndex(GemFireCache gemfireCache) { + + IndexFactoryBean lastNameIndex = new IndexFactoryBean(); + + lastNameIndex.setCache(gemfireCache); + lastNameIndex.setName("LastNameIdx"); + lastNameIndex.setExpression("last_name"); + lastNameIndex.setFrom("/People"); + lastNameIndex.setType(IndexType.HASH); + + return lastNameIndex; + } + } } diff --git a/src/test/java/org/springframework/data/gemfire/config/xml/IndexNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/xml/IndexNamespaceTest.java index 2d0be669..0bdf3a81 100644 --- a/src/test/java/org/springframework/data/gemfire/config/xml/IndexNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/xml/IndexNamespaceTest.java @@ -16,6 +16,7 @@ package org.springframework.data.gemfire.config.xml; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import javax.annotation.Resource; @@ -25,13 +26,16 @@ import com.gemstone.gemfire.cache.query.Index; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.IndexFactoryBean; import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** - * The IndexNamespaceTest is a test suite of test cases testing the functionality of GemFire Index creation using - * the Spring Data GemFire XML namespace (XSD). + * Integration tests with test cases testing the functionality of GemFire Index creation using + * the Spring Data GemFire XML namespace (XSD) and the {@link IndexParser}. * * @author Costin Leau * @author David Turanski @@ -43,30 +47,42 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner * @since 1.1.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(locations="index-ns.xml", initializers=GemfireTestApplicationContextInitializer.class) @SuppressWarnings({ "deprecation", "unused" }) public class IndexNamespaceTest { private static final String TEST_REGION_NAME = "IndexedRegion"; - @Resource(name = "simple") - private Index simple; + @Autowired + private ApplicationContext applicationContext; + + @Resource(name = "basic") + private Index basic; @Resource(name = "complex") private Index complex; @Test - public void testBasicIndex() throws Exception { - assertEquals("simple", simple.getName()); - assertEquals("status", simple.getIndexedExpression()); - assertEquals(Region.SEPARATOR + TEST_REGION_NAME, simple.getFromClause()); - assertEquals(TEST_REGION_NAME, simple.getRegion().getName()); - assertEquals(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL, simple.getType()); + public void basicIndexIsCorrect() throws Exception { + assertEquals("basic", basic.getName()); + assertEquals("status", basic.getIndexedExpression()); + assertEquals(Region.SEPARATOR + TEST_REGION_NAME, basic.getFromClause()); + assertEquals(TEST_REGION_NAME, basic.getRegion().getName()); + assertEquals(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL, basic.getType()); } @Test - public void testComplexIndex() throws Exception { + public void basicIndexFactoryBeanIsCorrect() { + + IndexFactoryBean basicIndexFactoryBean = applicationContext.getBean("&basic", IndexFactoryBean.class); + + assertThat(basicIndexFactoryBean.isIgnoreIfExists()).isFalse(); + assertThat(basicIndexFactoryBean.isOverride()).isFalse(); + } + + @Test + public void complexIndexIsCorrect() throws Exception { assertEquals("complex-index", complex.getName()); assertEquals("tsi.name", complex.getIndexedExpression()); assertEquals(Region.SEPARATOR + TEST_REGION_NAME + " tsi", complex.getFromClause()); @@ -74,4 +90,13 @@ public class IndexNamespaceTest { assertEquals(com.gemstone.gemfire.cache.query.IndexType.HASH, complex.getType()); } + @Test + public void indexWithIgnoreAndOverrideIsCorrect() { + + IndexFactoryBean indexFactoryBean = + applicationContext.getBean("&index-with-ignore-and-override", IndexFactoryBean.class); + + assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue(); + assertThat(indexFactoryBean.isOverride()).isTrue(); + } } diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java new file mode 100644 index 00000000..0f086965 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java @@ -0,0 +1,208 @@ +/* + * Copyright 2017 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.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.commons.logging.Log; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; + +/** + * Unit tests for {@link AbstractFactoryBeanSupport}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.Spy + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class AbstractFactoryBeanSupportUnitTests { + + @Mock + private Log mockLog; + + @Spy + private TestFactoryBeanSupport factoryBeanSupport; + + @Before + public void setup() { + when(factoryBeanSupport.getLog()).thenReturn(mockLog); + } + + @Test + public void setAndGetBeanClassLoader() { + assertThat(factoryBeanSupport.getBeanClassLoader()).isNull(); + + ClassLoader mockClassLoader = mock(ClassLoader.class); + + factoryBeanSupport.setBeanClassLoader(mockClassLoader); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(mockClassLoader); + + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + + factoryBeanSupport.setBeanClassLoader(systemClassLoader); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(systemClassLoader); + + factoryBeanSupport.setBeanClassLoader(null); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isNull(); + } + + @Test + public void setAndGetBeanFactory() { + assertThat(factoryBeanSupport.getBeanFactory()).isNull(); + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + factoryBeanSupport.setBeanFactory(mockBeanFactory); + + assertThat(factoryBeanSupport.getBeanFactory()).isSameAs(mockBeanFactory); + + factoryBeanSupport.setBeanFactory(null); + + assertThat(factoryBeanSupport.getBeanFactory()).isNull(); + } + + @Test + public void setAndGetBeanName() { + assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty(); + + factoryBeanSupport.setBeanName("test"); + + assertThat(factoryBeanSupport.getBeanName()).isEqualTo("test"); + + factoryBeanSupport.setBeanName(null); + + assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty(); + } + + @Test + public void isSingletonDefaultsToTrue() { + assertThat(factoryBeanSupport.isSingleton()).isTrue(); + } + + @Test + public void logsDebugWhenDebugIsEnabled() { + when(mockLog.isDebugEnabled()).thenReturn(true); + + factoryBeanSupport.logDebug("%s log test", "debug"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, times(1)).debug(eq("debug log test")); + } + + @Test + public void logsInfoWhenInfoIsEnabled() { + when(mockLog.isInfoEnabled()).thenReturn(true); + + factoryBeanSupport.logInfo("%s log test", "info"); + + verify(mockLog, times(1)).isInfoEnabled(); + verify(mockLog, times(1)).info(eq("info log test")); + } + + @Test + public void logsWarningWhenWarnIsEnabled() { + when(mockLog.isWarnEnabled()).thenReturn(true); + + factoryBeanSupport.logWarning("%s log test", "warn"); + + verify(mockLog, times(1)).isWarnEnabled(); + verify(mockLog, times(1)).warn(eq("warn log test")); + } + + @Test + public void logsWarningWhenErrorIsEnabled() { + when(mockLog.isErrorEnabled()).thenReturn(true); + + factoryBeanSupport.logError("%s log test", "error"); + + verify(mockLog, times(1)).isErrorEnabled(); + verify(mockLog, times(1)).error(eq("error log test")); + } + + @Test + public void suppressesDebugLoggingWhenDebugIsDisabled() { + when(mockLog.isDebugEnabled()).thenReturn(false); + + factoryBeanSupport.logDebug("test"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, never()).debug(any()); + } + + @Test + public void suppressesInfoLoggingWhenInfoIsDisabled() { + when(mockLog.isInfoEnabled()).thenReturn(false); + + factoryBeanSupport.logInfo("test"); + + verify(mockLog, times(1)).isInfoEnabled(); + verify(mockLog, never()).info(any()); + } + + @Test + public void suppressesWarnLoggingWhenWarnIsDisabled() { + when(mockLog.isWarnEnabled()).thenReturn(false); + + factoryBeanSupport.logWarning("test"); + + verify(mockLog, times(1)).isWarnEnabled(); + verify(mockLog, never()).warn(any()); + } + + @Test + public void suppressesErrorLoggingWhenInfoIsDisabled() { + when(mockLog.isErrorEnabled()).thenReturn(false); + + factoryBeanSupport.logError("test"); + + verify(mockLog, times(1)).isErrorEnabled(); + verify(mockLog, never()).error(any()); + } + + private static class TestFactoryBeanSupport extends AbstractFactoryBeanSupport { + + @Override + public T getObject() throws Exception { + return null; + } + + @Override + public Class getObjectType() { + return Object.class; + } + } +} diff --git a/src/test/resources/org/springframework/data/gemfire/config/xml/index-ns.xml b/src/test/resources/org/springframework/data/gemfire/config/xml/index-ns.xml index 0835a84c..9181829c 100644 --- a/src/test/resources/org/springframework/data/gemfire/config/xml/index-ns.xml +++ b/src/test/resources/org/springframework/data/gemfire/config/xml/index-ns.xml @@ -30,9 +30,12 @@ - + + +