diff --git a/src/main/asciidoc/reference/indexing.adoc b/src/main/asciidoc/reference/indexing.adoc
index 7206ab97..9476782c 100644
--- a/src/main/asciidoc/reference/indexing.adoc
+++ b/src/main/asciidoc/reference/indexing.adoc
@@ -1,25 +1,238 @@
[[bootstrap:indexing]]
= Configuring an Index
-Pivotal GemFire allows Indexes (or Indices) to be created to improve the performance of OQL queries.
+Pivotal GemFire allows Indexes (or Indices) to be created on Region data to improve the performance of OQL queries.
-In _Spring Data GemFire_, Indexes are declared with the `index` element:
+In _Spring Data GemFire_ (SDG), Indexes are declared with the `index` element:
[source,xml]
----
-
+
----
-Before creating the `Index`, _Spring Data GemFire_ will verify whether an `Index` with the same name already exists.
-By default, SDG overrides an existing `Index` if an `Index` with the same name already exists. The existing Index
-is overridden by removing the old `Index` first followed by creating a new `Index` with the same name defined by
-the new bean definition, regardless if the old `Index` definition was the same or not.
+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.
-To prevent the named `Index` definition change, especially when the old and new `Index` definitions differ
-in a significant way, then set the `override` attribute to *false*, which effectively returns the existing
-`Index` definition given the same name.
+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.
-`Index` declarations are not bound to a Region but rather are top-level elements (just like `cache`).
-This allows one to declare any number of Indices on any Region whether they are just created or already exist
-- an improvement over GemFire's native `cache.xml`. By default, the `Index` relies on the default cache declaration
-but one can customize it accordingly or use a Pool (if need be) - see the XML schema for a full set of options.
+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 e3d36a80..029ba012 100644
--- a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java
+++ b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java
@@ -37,6 +37,14 @@ import org.springframework.dao.DataIntegrityViolationException;
@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);
}
@@ -76,5 +84,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 17cb7fbb..35245f4d 100644
--- a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
+++ b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
@@ -20,19 +20,20 @@ import static java.util.stream.StreamSupport.stream;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
+import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
+import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionService;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexExistsException;
-import org.apache.geode.cache.query.IndexInvalidException;
import org.apache.geode.cache.query.IndexNameConflictException;
import org.apache.geode.cache.query.IndexStatistics;
import org.apache.geode.cache.query.QueryService;
@@ -42,7 +43,6 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.data.gemfire.config.annotation.IndexConfigurer;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
-import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -60,6 +60,7 @@ import org.springframework.util.StringUtils;
* @see org.apache.geode.cache.query.QueryService
* @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.config.annotation.IndexConfigurer
* @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport
@@ -67,8 +68,14 @@ import org.springframework.util.StringUtils;
*/
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 boolean ignoreIfExists = false;
+ private boolean override = false;
private Index index;
@@ -77,7 +84,7 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
//@Autowired(required = false)
private List indexConfigurers = Collections.emptyList();
- private IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() {
+ private final IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() {
@Override
public void configure(String beanName, IndexFactoryBean bean) {
@@ -101,25 +108,18 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
@Override
public void afterPropertiesSet() throws Exception {
- this.indexName = Optional.ofNullable(this.name).filter(StringUtils::hasText).orElse(getBeanName());
-
- Assert.hasText(this.indexName, "Index name is required");
+ this.indexName = resolveIndexName();
applyIndexConfigurers(this.indexName);
- Assert.notNull(cache, "Cache is required");
+ this.cache = resolveCache();
+ this.queryService = resolveQueryService();
- this.queryService = lookupQueryService();
+ assertIndexDefinitionConfiguration();
- 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");
+ this.index = createIndex(this.queryService, this.indexName);
- if (IndexType.isKey(indexType)) {
- Assert.isNull(imports, "imports are not supported with a KEY Index");
- }
-
- this.index = createIndex(queryService, indexName);
+ registerAlias(getBeanName(), this.indexName);
}
/* (non-Javadoc) */
@@ -155,47 +155,93 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
.forEach(indexConfigurer -> indexConfigurer.configure(indexName, this));
}
+ /* (non-Javadoc) */
+ 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() {
+
+ return Optional.ofNullable(this.cache)
+ .orElseGet(() -> Optional.ofNullable(GemfireUtils.resolveGemFireCache())
+ .orElseThrow(() -> newIllegalStateException("Cache is required")));
+ }
+
+ /* (non-Javadoc) */
+ String resolveIndexName() {
+
+ return Optional.ofNullable(this.name).filter(StringUtils::hasText)
+ .orElseGet(() -> Optional.ofNullable(getBeanName()).filter(StringUtils::hasText)
+ .orElseThrow(() -> newIllegalArgumentException("Index name is required")));
+ }
+
+ /* (non-Javadoc) */
+ QueryService resolveQueryService() {
+
+ return Optional.ofNullable(this.queryService)
+ .orElseGet(() -> Optional.ofNullable(lookupQueryService())
+ .orElseThrow(() -> newIllegalStateException("QueryService is required to create an Index")));
+ }
+
+ /* (non-Javadoc) */
+ QueryService lookupQueryService() {
+
+ String queryServiceBeanName = GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE;
+
+ return Optional.ofNullable(getBeanFactory())
+ .filter(beanFactory -> beanFactory.containsBean(queryServiceBeanName))
+ .map(beanFactory -> beanFactory.getBean(queryServiceBeanName, QueryService.class))
+ .orElseGet(() -> registerQueryServiceBean(queryServiceBeanName, doLookupQueryService()));
+ }
+
/* (non-Javadoc) */
QueryService doLookupQueryService() {
+
return Optional.ofNullable(this.queryService).orElseGet(() ->
(this.cache instanceof ClientCache ? ((ClientCache) this.cache).getLocalQueryService()
: this.cache.getQueryService()));
}
- /* (non-Javadoc) */
- QueryService lookupQueryService() {
- if (getBeanFactory().containsBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)) {
- return getBeanFactory().getBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE,
- QueryService.class);
- }
- else {
- return registerQueryServiceBean(doLookupQueryService());
- }
- }
+
/* (non-Javadoc) */
- QueryService registerQueryServiceBean(QueryService queryService) {
+ QueryService registerQueryServiceBean(String beanName, QueryService queryService) {
+
if (isDefine()) {
- ((ConfigurableBeanFactory) getBeanFactory()).registerSingleton(
- GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE, queryService);
+ ((ConfigurableBeanFactory) getBeanFactory()).registerSingleton(beanName, queryService);
}
return queryService;
}
+ /* (non-Javadoc) */
+ void registerAlias(String beanName, String indexName) {
+
+ Optional.ofNullable(getBeanFactory()).filter(it -> it instanceof ConfigurableBeanFactory)
+ .filter(it -> (beanName != null && !beanName.equals(indexName)))
+ .ifPresent(it -> ((ConfigurableBeanFactory) it).registerAlias(beanName, indexName));
+ }
+
/* (non-Javadoc) */
Index createIndex(QueryService queryService, String indexName) throws Exception {
+ return createIndex(queryService, indexName, false);
+ }
- Index existingIndex = getExistingIndex(queryService, indexName);
+ /* (non-Javadoc) */
+ private Index createIndex(QueryService queryService, String indexName, boolean retryAttempted) throws Exception {
- if (existingIndex != null) {
- if (override) {
- queryService.removeIndex(existingIndex);
- }
- else {
- return existingIndex;
- }
- }
+ IndexType indexType = this.indexType;
+
+ String expression = this.expression;
+ String from = this.from;
+ String imports = this.imports;
try {
if (IndexType.isKey(indexType)) {
@@ -208,32 +254,177 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
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
- throw e;
+ Optional existingIndexByDefinition =
+ tryToFindExistingIndexByDefinition(queryService, expression, from, indexType);
+
+ return existingIndexByDefinition.filter(existingIndex -> isIgnoreIfExists())
+ .map(existingIndex -> {
+
+ 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(), existingIndex.getName());
+
+ return handleIgnore(existingIndex);
+
+ }).orElseGet(() ->
+
+ existingIndexByDefinition.filter(it -> !retryAttempted && isOverride())
+ .map(existingIndex -> {
+
+ // 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]",
+ existingIndex.getName(), toBasicIndexDefinition(), indexName);
+
+ return handleOverride(existingIndex, queryService, indexName);
+
+ }).orElseThrow(() -> {
+
+ String existingIndexName = existingIndexByDefinition.map(Index::getName)
+ .orElse("unknown");
+
+ return 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
+
+ Optional existingIndexByName = tryToFindExistingIndexByName(queryService, indexName);
+
+ return existingIndexByName.filter(existingIndex -> isIgnoreIfExists())
+ .map(existingIndex ->
+
+ handleIgnore(warnOnIndexDefinitionMismatch(existingIndex, indexName, "Returning"))
+
+ ).orElseGet(() ->
+
+ existingIndexByName.filter(it -> !retryAttempted && isOverride())
+ .map(existingIndex ->
+
+ handleSmartOverride(warnOnIndexDefinitionMismatch(existingIndex, indexName,
+ "Overriding"), queryService, indexName)
+
+ ).orElseThrow(() -> {
+
+ String existingIndexDefinition = existingIndexByName
+ .map(it -> String.format(DETAILED_INDEX_DEFINITION, it.getName(),
+ it.getIndexedExpression(), it.getFromClause(), "unknown", it.getType()))
+ .orElse("unknown");
+
+ return 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) */
+ @SuppressWarnings("all")
+ private boolean isIndexDefinitionMatch(Index index) {
+
+ return Optional.ofNullable(index)
+ .map(it -> {
+
+ IndexType thisIndexType = Optional.ofNullable(this.indexType).orElse(IndexType.FUNCTIONAL);
+
+ boolean result = ObjectUtils.nullSafeEquals(it.getIndexedExpression(), this.expression)
+ && ObjectUtils.nullSafeEquals(it.getFromClause(), this.from)
+ && ObjectUtils.nullSafeEquals(IndexType.valueOf(it.getType()), thisIndexType);
+
+ return result;
+ })
+ .orElse(false);
+ }
+
+ /* (non-Javadoc) */
+ private boolean isNotIndexDefinitionMatch(Index index) {
+ return !isIndexDefinitionMatch(index);
+ }
+
+ /* (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 Optional.of(existingIndex)
+ .filter(it -> it.getName().equalsIgnoreCase(indexName))
+ .filter(it -> isIndexDefinitionMatch(existingIndex))
+ .orElseGet(() -> 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) */
@@ -249,8 +440,8 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
}
/* (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);
@@ -301,15 +492,31 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
}
/* (non-Javadoc) */
- Index getExistingIndex(QueryService queryService, String indexName) {
+ Optional tryToFindExistingIndexByDefinition(QueryService queryService,
+ String expression, String fromClause, IndexType indexType) {
for (Index index : nullSafeCollection(queryService.getIndexes())) {
- if (index.getName().equalsIgnoreCase(indexName)) {
- return index;
+ if (index.getIndexedExpression().equalsIgnoreCase(expression)
+ && index.getFromClause().equalsIgnoreCase(fromClause)
+ && indexType.equals(IndexType.valueOf(index.getType()))) {
+
+ return Optional.of(index);
}
}
- return null;
+ return Optional.empty();
+ }
+
+ /* (non-Javadoc) */
+ Optional tryToFindExistingIndexByName(QueryService queryService, String indexName) {
+
+ for (Index index : nullSafeCollection(queryService.getIndexes())) {
+ if (index.getName().equalsIgnoreCase(indexName)) {
+ return Optional.of(index);
+ }
+ }
+
+ return Optional.empty();
}
/**
@@ -323,13 +530,23 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
return this.compositeIndexConfigurer;
}
+ /**
+ * 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 org.apache.geode.cache.query.Index
+ */
+ public Index getIndex() {
+ return this.index;
+ }
+
/**
* @inheritDoc
*/
@Override
public Index getObject() {
- return Optional.ofNullable(this.index)
- .orElseGet(() -> this.index = getExistingIndex(queryService, indexName));
+ return Optional.ofNullable(getIndex()).orElseGet(() ->
+ this.index = tryToFindExistingIndexByName(resolveQueryService(), resolveIndexName()).orElse(null));
}
/**
@@ -338,74 +555,151 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
@Override
@SuppressWarnings("unchecked")
public Class> getObjectType() {
- return Optional.ofNullable(this.index).map(Index::getClass).orElse((Class) Index.class);
+ return Optional.ofNullable(getIndex()).map(Index::getClass).orElse((Class) Index.class);
}
/**
- * 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 org.apache.geode.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;
- }
-
- /**
- * @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 org.apache.geode.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;
}
+ /**
+ * 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;
+ }
+
/**
* Null-safe operation to set an array of {@link IndexConfigurer IndexConfigurers} used to apply
* additional configuration to this {@link IndexFactoryBean} when using Annotation-based configuration.
@@ -432,28 +726,88 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
}
/**
- * @param override the override to set
+ * 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) */
@@ -466,96 +820,109 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
private final String indexName;
protected IndexWrapper(QueryService queryService, String indexName) {
- Assert.notNull(queryService, "QueryService must not be null");
- Assert.hasText(indexName, "The name of the Index must be specified!");
+
+ Assert.notNull(queryService, "QueryService is required");
+ Assert.hasText(indexName, "Name of Index is required");
+
this.queryService = queryService;
this.indexName = indexName;
-
}
- protected synchronized Index getIndex() {
- if (this.index == null) {
- String localIndexName = getIndexName();
+ /* (non-Javadoc) */
+ protected synchronized Index resolveIndex() {
- for (Index localIndex : getQueryService().getIndexes()) {
- if (localIndex.getName().equals(localIndexName)) {
- this.index = localIndex;
- break;
- }
- }
+ String indexName = getIndexName();
- if (this.index == null) {
- throw new GemfireIndexException(new IndexInvalidException(String.format(
- "index with name (%1$s) was not found", localIndexName)));
- }
- }
+ return Optional.ofNullable(this.index)
+ .orElseGet(() -> {
- return index;
+ AtomicReference searchResult = new AtomicReference<>();
+
+ nullSafeCollection(getQueryService().getIndexes()).forEach(index -> {
+ if (index.getName().equalsIgnoreCase(indexName)) {
+ searchResult.set(index);
+ }
+ });
+
+ return Optional.of(searchResult).map(it -> {
+ this.index = it.get();
+ return this.index;
+ }).orElseThrow(() -> new GemfireIndexException(
+ String.format("Index with name [%s] was not found", indexName), (Exception) null));
+ });
}
+ /* (non-Javadoc) */
+ protected Index getIndex() {
+ return this.index;
+ }
+
+ /* (non-Javadoc) */
protected String getIndexName() {
- Assert.state(StringUtils.hasText(indexName), "The Index 'name' was not properly initialized!");
- return indexName;
+ return Optional.ofNullable(this.indexName).filter(StringUtils::hasText).orElseThrow(() ->
+ newIllegalStateException("Index name is required"));
}
+ /* (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 org.apache.geode.cache.query.IndexType getType() {
- return getIndex().getType();
+ return resolveIndex().getType();
}
@Override
public boolean equals(Object obj) {
- if (obj == this) {
+
+ if (this == obj) {
return true;
}
@@ -567,20 +934,25 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport implemen
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 Optional.ofNullable(getIndex()).map(String::valueOf)
+ .orElseGet(this::getIndexName);
}
}
}
diff --git a/src/main/java/org/springframework/data/gemfire/IndexType.java b/src/main/java/org/springframework/data/gemfire/IndexType.java
index 904c35d9..ec51d6af 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(org.apache.geode.cache.query.IndexType.FUNCTIONAL),
HASH(org.apache.geode.cache.query.IndexType.HASH),
PRIMARY_KEY(org.apache.geode.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 org.apache.geode.cache.query.IndexType
*/
- public static IndexType valueOf(final org.apache.geode.cache.query.IndexType gemfireIndexType) {
+ public static IndexType valueOf(org.apache.geode.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 3fe29742..4f0f38a4 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
@@ -136,6 +136,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
getAnnotationAttributes(importingClassMetadata, getEnableIndexingAnnotationTypeName());
localPersistentEntity.doWithProperties((PropertyHandler) persistentProperty -> {
+
persistentProperty.findAnnotation(Id.class).ifPresent(idAnnotation ->
registerIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity, persistentProperty,
IndexType.KEY, idAnnotation, registry));
@@ -147,6 +148,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
persistentProperty.findAnnotation(LuceneIndexed.class).ifPresent( luceneIndexAnnotation ->
registerLuceneIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity,
persistentProperty, luceneIndexAnnotation, registry));
+
});
}
@@ -195,12 +197,13 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
indexFactoryBeanBuilder.addPropertyValue("from",
resolveFrom(persistentEntity, persistentProperty, indexedAttributes));
+ indexFactoryBeanBuilder.addPropertyValue("ignoreIfExists", Boolean.TRUE);
+
indexFactoryBeanBuilder.addPropertyValue("indexConfigurers", resolveIndexConfigurers());
indexFactoryBeanBuilder.addPropertyValue("name", indexName);
- indexFactoryBeanBuilder.addPropertyValue("override",
- resolveOverride(persistentEntity, persistentProperty, indexedAttributes));
+ indexFactoryBeanBuilder.addPropertyValue("override", Boolean.FALSE);
indexFactoryBeanBuilder.addPropertyValue("type",
resolveType(persistentEntity, persistentProperty, indexedAttributes, indexType).toString());
@@ -325,15 +328,6 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
StringUtils.capitalize(indexType.name().toLowerCase()));
}
- /* (non-Javadoc) */
- @SuppressWarnings("unused")
- private boolean resolveOverride(GemfirePersistentEntity persistentEntity,
- GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes) {
-
- return (indexedAttributes.containsKey("override")
- && indexedAttributes.getBoolean("override"));
- }
-
/* (non-Javadoc) */
@SuppressWarnings("unused")
private IndexType resolveType(GemfirePersistentEntity> persistentEntity,
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/mapping/annotation/Indexed.java b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java
index 1d5a7184..043c7b21 100644
--- a/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java
+++ b/src/main/java/org/springframework/data/gemfire/mapping/annotation/Indexed.java
@@ -70,13 +70,6 @@ public @interface Indexed {
*/
String from() default "";
- /**
- * Determines whether this given {@link Index} definition should override any existing {@link Index} definition.
- *
- * Defaults to {@literal false}.
- */
- boolean override() default false;
-
/**
* Type of Index to create.
*
diff --git a/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java
index 0a1ca91f..d1b444f9 100644
--- a/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java
+++ b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java
@@ -203,4 +203,54 @@ public abstract class AbstractFactoryBeanSupport implements FactoryBean,
.filter(Log::isInfoEnabled)
.ifPresent(log -> 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(() -> String.format(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) {
+ Optional.ofNullable(getLog())
+ .filter(Log::isWarnEnabled)
+ .ifPresent(log -> 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(() -> String.format(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) {
+ Optional.ofNullable(getLog())
+ .filter(Log::isErrorEnabled)
+ .ifPresent(log -> log.error(message.get()));
+ }
}
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 adda07e7..73393781 100644
--- a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
+++ b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
@@ -126,14 +126,14 @@ public abstract class CacheUtils extends DistributedSystemUtils {
try {
return ClientCacheFactory.getAnyInstance();
}
- catch (CacheClosedException ignore) {
+ catch (CacheClosedException | IllegalStateException ignore) {
return null;
}
}
/* (non-Javadoc) */
public static GemFireCache resolveGemFireCache() {
- return Optional.ofNullable(getCache()).orElseGet(CacheUtils::getClientCache);
+ return Optional.ofNullable(getClientCache()).orElseGet(CacheUtils::getCache);
}
/* (non-Javadoc) */
diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-2.0.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-2.0.xsd
index 45e5f990..f909cbcc 100644
--- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-2.0.xsd
+++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-2.0.xsd
@@ -2256,11 +2256,27 @@ Corresponds to the regionPath parameter in createIndex methods.
]]>
-
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-geode-1.1.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-geode-1.1.xsd
index 4f987903..46656697 100644
--- a/src/main/resources/org/springframework/data/gemfire/config/spring-geode-1.1.xsd
+++ b/src/main/resources/org/springframework/data/gemfire/config/spring-geode-1.1.xsd
@@ -2358,11 +2358,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 1602db3e..46c17190 100644
--- a/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java
+++ b/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java
@@ -16,25 +16,21 @@
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 org.apache.geode.cache.Cache;
+import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexExistsException;
+import org.apache.geode.cache.query.IndexNameConflictException;
+import org.apache.geode.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;
@@ -43,26 +39,49 @@ import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
/**
- * 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.apache.geode.cache.GemFireCache
+ * @see org.apache.geode.cache.query.Index
+ * @see org.springframework.context.ConfigurableApplicationContext
+ * @see org.springframework.context.annotation.Bean
+ * @see org.springframework.context.annotation.Configuration
+ * @see org.springframework.context.annotation.Import
+ * @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());
@@ -71,185 +90,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();
@@ -257,7 +351,6 @@ public class IndexConflictsIntegrationTest {
indexFactoryBean.setExpression("lastName");
indexFactoryBean.setFrom("/Customers");
indexFactoryBean.setName(INDEX_NAME);
- indexFactoryBean.setOverride(override);
indexFactoryBean.setType(IndexType.HASH);
return indexFactoryBean;
@@ -265,20 +358,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 1140f97b..9fe52273 100644
--- a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java
+++ b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java
@@ -16,90 +16,84 @@
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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.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.apache.commons.logging.Log;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexExistsException;
-import org.apache.geode.cache.query.IndexInvalidException;
import org.apache.geode.cache.query.IndexNameConflictException;
import org.apache.geode.cache.query.IndexStatistics;
import org.apache.geode.cache.query.QueryService;
import org.junit.After;
-import org.junit.Rule;
+import org.junit.Before;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
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;
/**
* 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.junit.MockitoJUnitRunner
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.query.Index
+ * @see org.apache.geode.cache.query.QueryService
* @see org.springframework.data.gemfire.IndexFactoryBean
* @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
@@ -107,26 +101,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");
@@ -134,47 +169,73 @@ 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 {
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
indexFactoryBean.setName("TestIndex");
indexFactoryBean.afterPropertiesSet();
}
- catch (IllegalArgumentException expected) {
- assertEquals("Cache is required", 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)
@@ -185,88 +246,134 @@ public class IndexFactoryBeanTest {
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.setName("TestIndex");
- 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();
@@ -274,399 +381,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());
+
+ 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"), eq(null));
+
+ 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());
+
+ 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"), eq(null));
+
+ 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());
+
+ 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"), eq(null));
+
+ 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());
+
+ 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"), eq(null));
+
+ 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());
+
+ 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"), eq(null));
+
+ 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);
@@ -674,298 +1206,464 @@ 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).orElse(null)).isSameAs(mockIndexTwo);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "purchaseDate", "/Orders", IndexType.FUNCTIONAL).orElse(null)).isSameAs(mockIndexThree);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.HASH).orElse(null)).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).orElse(null)).isNull();
- Index actualIndex = indexFactoryBean.getObject();
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/Persons", IndexType.HASH).orElse(null)).isNull();
- assertNotNull(actualIndex);
- assertEquals("MockIndex1", actualIndex.getName());
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.KEY).orElse(null)).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).orElse(null)).isNull();
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByDefinitionWithQueryServiceReturningNullIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(null);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.KEY).orElse(null)).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")
+ .orElse(null)).isSameAs(mockIndexOne);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "SecondaryIndex")
+ .orElse(null)).isSameAs(mockIndexTwo);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "ternaryindex")
+ .orElse(null)).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)
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, " ")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Primary Index")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Secondary_Index")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "QuadIndex")
+ .orElse(null)).isNull();
+
+ verify(mockQueryService, times(6)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameWithQueryServiceHavingNoIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList());
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")
+ .orElse(null)).isNull();
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameWithQueryServiceReturningNullIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(null);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")
+ .orElse(null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")
+ .orElse(null)).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");
+ Cache mockCacheTwo =
+ mock(Cache.class, "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockCacheTwo");
- QueryService mockQueryServiceTwo = mock(QueryService.class,
- "testDefineMultipleIndexesWithSeparateIndexFactoryBeansSameSpringContext.MockQueryServiceTwo");
+ AtomicReference queryServiceReference = new AtomicReference<>(null);
- final AtomicReference queryServiceReference = new AtomicReference(null);
+ doAnswer(invocation -> (queryServiceReference.get() != null)).when(mockBeanFactory)
+ .containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE));
- doAnswer(new Answer() {
- @Override public Boolean answer(final InvocationOnMock invocation) throws Throwable {
- return (queryServiceReference.get() != null);
- }
- }).when(mockBeanFactory).containsBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE));
+ doAnswer(invocation -> queryServiceReference.get()).when(mockBeanFactory)
+ .getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE), eq(QueryService.class));
- doAnswer(new Answer() {
- @Override public QueryService answer(final InvocationOnMock invocation) throws Throwable {
- return queryServiceReference.get();
- }
- }).when(mockBeanFactory).getBean(eq(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE),
- eq(QueryService.class));
+ doAnswer(invocation -> {
- doAnswer(new Answer() {
- @Override public Void answer(final InvocationOnMock invocation) throws Throwable {
- assertEquals(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE,
- invocation.getArgument(0));
- queryServiceReference.compareAndSet(null, invocation.getArgument(1));
- return null;
- }
+ assertEquals(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE,
+ invocation.getArgument(0));
+
+ queryServiceReference.compareAndSet(null, invocation.getArgument(1));
+
+ return null;
}).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();
@@ -990,31 +1688,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");
@@ -1027,28 +1730,30 @@ public class IndexFactoryBeanTest {
when(mockIndex.getType()).thenReturn(org.apache.geode.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(org.apache.geode.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(org.apache.geode.cache.query.IndexType.HASH);
- assertSame(actualIndex, sameIndex);
+ Index sameIndex = indexWrapper.resolveIndex();
+
+ assertThat(sameIndex).isSameAs(actualIndex);
}
@Test(expected = IllegalArgumentException.class)
@@ -1057,40 +1762,45 @@ 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());
+ 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/EnableIndexingConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexingConfigurationUnitTests.java
index 2a5e90a0..866e1159 100644
--- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexingConfigurationUnitTests.java
+++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableIndexingConfigurationUnitTests.java
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -38,7 +37,10 @@ import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.apache.geode.cache.lucene.LuceneService;
import org.apache.geode.cache.query.Index;
+import org.apache.geode.cache.query.IndexExistsException;
+import org.apache.geode.cache.query.IndexNameConflictException;
import org.apache.geode.cache.query.QueryService;
+import org.apache.geode.internal.concurrent.ConcurrentHashSet;
import org.junit.After;
import org.junit.Test;
import org.mockito.Matchers;
@@ -71,7 +73,7 @@ import org.springframework.data.gemfire.config.annotation.test.entities.Replicat
*/
public class EnableIndexingConfigurationUnitTests {
- private static final Set indexes = new HashSet<>();
+ private static final Set indexes = new ConcurrentHashSet<>();
private ConfigurableApplicationContext applicationContext;
@@ -82,16 +84,19 @@ public class EnableIndexingConfigurationUnitTests {
}
/* (non-Javadoc) */
- protected void assertIndex(Index index, String name, String expression, String from, IndexType indexType) {
- assertThat(index).isNotNull();
- assertThat(index.getName()).isEqualTo(name);
- assertThat(index.getIndexedExpression()).isEqualTo(expression);
- assertThat(index.getFromClause()).isEqualTo(from);
- assertThat(index.getType()).isEqualTo(indexType.getGemfireIndexType());
+ private static Index findIndexByName(String indexName) {
+
+ for (Index index : indexes) {
+ if (index.getName().equalsIgnoreCase(indexName)) {
+ return index;
+ }
+ }
+
+ return null;
}
/* (non-Javadoc) */
- protected void assertLuceneIndex(LuceneIndex index, String name, String regionPath, String... fields) {
+ private void assertLuceneIndex(LuceneIndex index, String name, String regionPath, String... fields) {
assertThat(index).isNotNull();
assertThat(index.getName()).isEqualTo(name);
assertThat(index.getRegionPath()).isEqualTo(regionPath);
@@ -100,68 +105,87 @@ public class EnableIndexingConfigurationUnitTests {
}
/* (non-Javadoc) */
- protected ConfigurableApplicationContext newApplicationContext(Class>... annotatedClasses) {
+ private void assertOqlIndex(Index index, String name, String expression, String from, IndexType indexType) {
+ assertThat(index).isNotNull();
+ assertThat(index.getName()).isEqualTo(name);
+ assertThat(index.getIndexedExpression()).isEqualTo(expression);
+ assertThat(index.getFromClause()).isEqualTo(from);
+ assertThat(index.getType()).isEqualTo(indexType.getGemfireIndexType());
+ }
+
+ /* (non-Javadoc) */
+ private ConfigurableApplicationContext newApplicationContext(Class>... annotatedClasses) {
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses);
applicationContext.registerShutdownHook();
return applicationContext;
}
@Test
- public void persistentEntityIndexesCreatedSuccessfully() {
- applicationContext = newApplicationContext(IndexedPersistentEntityConfiguration.class);
+ public void persistentEntityIndexesAreCreated() {
+
+ applicationContext = newApplicationContext(IndexingEnabledWithIndexedPersistentEntityConfiguration.class);
assertLuceneIndexes(applicationContext);
assertOqlIndexes(applicationContext);
}
private void assertLuceneIndexes(ConfigurableApplicationContext applicationContext) {
+
LuceneIndex luceneIndex = applicationContext.getBean("TitleLuceneIdx", LuceneIndex.class);
assertLuceneIndex(luceneIndex, "TitleLuceneIdx", "Customers", "title");
}
private void assertOqlIndexes(ConfigurableApplicationContext applicationContext) {
- Index customersIdIdx = applicationContext.getBean("CustomersIdKeyIdx", Index.class);
- assertIndex(customersIdIdx, "CustomersIdKeyIdx", "id", "Customers", IndexType.KEY);
+ Index customersIdIndex = applicationContext.getBean("CustomersIdKeyIdx", Index.class);
- Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
+ assertOqlIndex(customersIdIndex, "CustomersIdKeyIdx", "id", "Customers", IndexType.KEY);
- assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx", "first_name",
+ Index customersFirstNameIndex = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
+
+ assertOqlIndex(customersFirstNameIndex, "CustomersFirstNameFunctionalIdx", "first_name",
"/LoyalCustomers", IndexType.FUNCTIONAL);
- Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class);
+ Index lastNameIndex = applicationContext.getBean("LastNameIdx", Index.class);
- assertIndex(lastNameIdx, "LastNameIdx", "surname", "Customers", IndexType.HASH);
+ assertOqlIndex(lastNameIndex, "LastNameIdx", "surname", "Customers", IndexType.HASH);
}
@Test
- public void persistentEntityIndexesWillNotBeCreated() {
- applicationContext = newApplicationContext(NoIndexesCreatedForIndexedPersistentEntityConfiguration.class);
+ public void persistentEntityIndexesAreNotCreated() {
+
+ applicationContext = newApplicationContext(IndexingNotEnabledWithIndexedPersistentEntityConfiguration.class);
Map indexes = applicationContext.getBeansOfType(Index.class);
assertThat(indexes).isNotNull();
- assertThat(indexes.isEmpty()).isTrue();
+ assertThat(indexes).isEmpty();
}
@Test
- public void indexAnnotatedEntityPropertyDoesNotOverrideIndexBeanDefinition() {
- applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration.class);
+ public void indexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinition() {
- Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class);
+ applicationContext = newApplicationContext(
+ IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinitionConfiguration.class);
- assertIndex(lastNameIdx, "LastNameIdx", "last_name", "/People", IndexType.HASH);
- }
+ Index firstNameIndex = applicationContext.getBean("LoyalCustomersFirstNameFunctionalIdx", Index.class);
- @Test
- public void indexAnnotatedEntityPropertyOverridesIndexBeanDefinition() {
- applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration.class);
-
- Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
-
- assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx",
+ assertOqlIndex(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);
+
+ assertOqlIndex(lastNameIndex, "LastNameIdx", "last_name", "/People", IndexType.HASH);
}
@Configuration
@@ -175,10 +199,10 @@ public class EnableIndexingConfigurationUnitTests {
}
Cache mockQueryService(Cache mockCache) throws Exception {
+
QueryService mockQueryService = mock(QueryService.class);
when(mockCache.getQueryService()).thenReturn(mockQueryService);
- when(mockQueryService.getIndexes()).thenReturn(indexes);
when(mockQueryService.createHashIndex(anyString(), anyString(), anyString()))
.thenAnswer(new HashIndexAnswer());
@@ -189,11 +213,24 @@ public class EnableIndexingConfigurationUnitTests {
when(mockQueryService.createKeyIndex(anyString(), anyString(), anyString()))
.thenAnswer(new KeyIndexAnswer());
+ when(mockQueryService.getIndexes()).thenReturn(indexes);
+
+ doAnswer(invocation -> {
+
+ Index indexToRemove = invocation.getArgument(0);
+
+ 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);
@@ -206,9 +243,11 @@ public class EnableIndexingConfigurationUnitTests {
@Bean
LuceneService luceneService() {
+
LuceneService mockLuceneService = mock(LuceneService.class);
doAnswer(invocation -> {
+
LuceneIndex mockLuceneIndex = mock(LuceneIndex.class);
String indexName = invocation.getArgument(0);
@@ -220,6 +259,7 @@ public class EnableIndexingConfigurationUnitTests {
when(mockLuceneService.getIndex(eq(indexName), eq(regionPath))).thenReturn(mockLuceneIndex);
return mockLuceneIndex;
+
}).when(mockLuceneService).createIndex(anyString(), anyString(), Matchers.anyVararg());
return mockLuceneService;
@@ -237,16 +277,22 @@ public class EnableIndexingConfigurationUnitTests {
@Override
public Index answer(InvocationOnMock invocation) throws Throwable {
+
+ IndexType indexType = getType();
+
String name = invocation.getArgument(0);
String expression = invocation.getArgument(1);
String from = invocation.getArgument(2);
+ validateIndexDefinition(name, expression, from, indexType);
+ validateIndexName(name);
+
Index mockIndex = mock(Index.class, name);
when(mockIndex.getName()).thenReturn(name);
when(mockIndex.getIndexedExpression()).thenReturn(expression);
when(mockIndex.getFromClause()).thenReturn(from);
- when(mockIndex.getType()).thenReturn(getType().getGemfireIndexType());
+ when(mockIndex.getType()).thenReturn(indexType.getGemfireIndexType());
indexes.add(mockIndex);
@@ -255,6 +301,30 @@ public class EnableIndexingConfigurationUnitTests {
abstract IndexType getType();
+ private void validateIndexDefinition(String name, String expression, String fromClause, IndexType type)
+ throws IndexExistsException {
+
+ for (Index index : indexes) {
+ if (index.getIndexedExpression().equalsIgnoreCase(expression)
+ && index.getFromClause().equalsIgnoreCase(fromClause)
+ && index.getType().equals(type.getGemfireIndexType())) {
+
+ throw new IndexExistsException(String.format(
+ "Index [%1$s] has the same definition as existing Index [%2$s]",
+ name, index.getName()));
+
+ }
+ }
+ }
+
+ private void validateIndexName(String name) throws IndexNameConflictException {
+
+ for (Index index : indexes) {
+ if (index.getName().equalsIgnoreCase(name)) {
+ throw new IndexNameConflictException(String.format("Index with name [%s] already exists", name));
+ }
+ }
+ }
}
static class FunctionalIndexAnswer extends AbstractIndexAnswer {
@@ -286,7 +356,7 @@ public class EnableIndexingConfigurationUnitTests {
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
LocalRegionEntity.class, ReplicateRegionEntity.class }))
- static class IndexedPersistentEntityConfiguration extends GemFireConfiguration {
+ private static class IndexingEnabledWithIndexedPersistentEntityConfiguration extends GemFireConfiguration {
}
@@ -294,7 +364,7 @@ public class EnableIndexingConfigurationUnitTests {
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
LocalRegionEntity.class, ReplicateRegionEntity.class }))
- static class NoIndexesCreatedForIndexedPersistentEntityConfiguration extends GemFireConfiguration {
+ private static class IndexingNotEnabledWithIndexedPersistentEntityConfiguration extends GemFireConfiguration {
}
@@ -303,42 +373,46 @@ public class EnableIndexingConfigurationUnitTests {
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
LocalRegionEntity.class, ReplicateRegionEntity.class }))
- static class IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration extends GemFireConfiguration {
+ 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;
+ }
+ }
+
+ @EnableIndexing
+ @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
+ excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
+ ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
+ LocalRegionEntity.class, ReplicateRegionEntity.class }))
+ static class 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.setName("LastNameIdx");
lastNameIndex.setType(IndexType.HASH);
return lastNameIndex;
}
}
-
- @EnableIndexing
- @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
- excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
- ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
- LocalRegionEntity.class, ReplicateRegionEntity.class }))
- static class IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration extends GemFireConfiguration {
-
- @Bean
- @SuppressWarnings("unused")
- IndexFactoryBean firstNameIndex(GemFireCache gemfireCache) {
- IndexFactoryBean firstNameIndex = new IndexFactoryBean();
-
- firstNameIndex.setCache(gemfireCache);
- firstNameIndex.setExpression("given_name");
- firstNameIndex.setFrom("/ProspectiveCustomers");
- firstNameIndex.setName("CustomersFirstNameFunctionalIdx");
- firstNameIndex.setType(IndexType.HASH);
-
- return firstNameIndex;
- }
- }
}
diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java
index a88dc4ec..dce72e06 100644
--- a/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java
+++ b/src/test/java/org/springframework/data/gemfire/config/annotation/test/entities/PartitionRegionEntity.java
@@ -42,7 +42,7 @@ public class PartitionRegionEntity {
@Id
private Long id;
- @Indexed(expression = "first_name", from = "/LoyalCustomers", override = true, type = IndexType.FUNCTIONAL)
+ @Indexed(expression = "first_name", from = "/LoyalCustomers", type = IndexType.FUNCTIONAL)
private String firstName;
@Indexed(name = "LastNameIdx", expression = "surname")
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 0ccf759b..a492c04b 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;
@@ -24,13 +25,16 @@ import org.apache.geode.cache.Region;
import org.apache.geode.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
@@ -42,30 +46,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(org.apache.geode.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(org.apache.geode.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());
@@ -73,4 +89,13 @@ public class IndexNamespaceTest {
assertEquals(org.apache.geode.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/search/lucene/LuceneOperationsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsIntegrationTests.java
index 43b85b3c..7632a6ca 100644
--- a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsIntegrationTests.java
+++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsIntegrationTests.java
@@ -129,6 +129,7 @@ public class LuceneOperationsIntegrationTests {
@Test
public void findsDoctorDoesAsTypePersonSuccessfully() {
+
Collection doctorDoes = template.queryForValues("title: Doctor*", "title");
assertThat(doctorDoes).isNotNull();
@@ -139,6 +140,7 @@ public class LuceneOperationsIntegrationTests {
@Test
@SuppressWarnings("all")
public void findsMasterDoesAsTypeUserSuccessfully() {
+
List masterDoes = template.query("title: Master*", "title", User.class);
assertThat(masterDoes).isNotNull();
diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java
index 1e600e74..648da8b9 100644
--- a/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java
@@ -133,6 +133,26 @@ public class AbstractFactoryBeanSupportUnitTests {
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);
@@ -153,6 +173,26 @@ public class AbstractFactoryBeanSupportUnitTests {
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
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 @@
-
+
+
+