diff --git a/src/main/asciidoc/reference/indexing.adoc b/src/main/asciidoc/reference/indexing.adoc
new file mode 100644
index 00000000..9476782c
--- /dev/null
+++ b/src/main/asciidoc/reference/indexing.adoc
@@ -0,0 +1,238 @@
+[[bootstrap:indexing]]
+= Configuring an Index
+
+Pivotal GemFire allows Indexes (or Indices) to be created on Region data to improve the performance of OQL queries.
+
+In _Spring Data GemFire_ (SDG), Indexes are declared with the `index` element:
+
+[source,xml]
+----
+
+----
+
+In _Spring Data GemFire's_ XML schema (a.k.a. SDG namespace), `Index` bean declarations are not bound to a _Region_,
+unlike GemFire's native `cache.xml`. Rather, they are top-level elements just like `<gfe:cache>`. This allows
+a developer to declare any number of Indexes on any _Region_ whether they were just created or already exist,
+a significant improvement over GemFire's native `cache.xml` format.
+
+An `Index` must have a name. A developer may give the `Index` an explicit name using the `name` attribute,
+otherwise the _bean name_ (i.e. value of the `id` attribute) of the `Index` bean definition is used as
+the `Index` name.
+
+The `expression` and `from` clause form the main components of an `Index`, identifying the data to index
+(i.e. the _Region_ identified in the `from` clause) along with what criteria (i.e. `expression`) is used
+to index the data. The `expression` should be based on what application domain object fields are used
+in the predicate of application-defined OQL queries used to query and lookup the objects stored
+in the _Region_.
+
+For example, if I have a `Customer` that has a `lastName` property...
+
+[source,java]
+----
+@Region("Customers")
+class Customer {
+
+ @Id
+ Long id;
+
+ String lastName;
+ String firstName;
+
+ ...
+}
+----
+
+And, I also have an application defined SD[G] _Repository_ to query for `Customers`...
+
+[source,java]
+----
+interface CustomerRepository extends GemfireRepository {
+
+ Customer findByLastName(String lastName);
+
+ ...
+}
+----
+
+Then, the SD[G] _Repository_ finder/query method would result in the following OQL statement being executed...
+
+[source,java]
+----
+SELECT * FROM /Customers c WHERE c.lastName = '$1'
+----
+
+Therefore, I might want to create an `Index` like so...
+
+[source,xml]
+----
+
+----
+
+The `from` clause must refer to a valid, existing _Region_ and is how an `Index` gets applied to a _Region_.
+This is *not* _Sprig Data GemFire_ specific; this is a feature of Pivotal GemFire.
+
+The `Index` `type` maybe 1 of 3 enumerated values defined by _Spring Data GemFire's_
+http://docs.spring.io/spring-data-gemfire/docs/current/api/org/springframework/data/gemfire/IndexType.html[IndexType]
+enumeration: `FUNCTIONAL`, `HASH` and `PRIMARY_KEY`.
+
+Each of the enumerated values correspond to one of the http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html[QueryService]
+`create[|Key|Hash]Index` methods invoked when the actual `Index` is to be created (or "defined"; more on "defining"
+Indexes below). For instance, if the `IndexType` is `PRIMARY_KEY`, then the
+http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#createKeyIndex-java.lang.String-java.lang.String-java.lang.String-[QueryService.createKeyIndex(..)]
+is invoked to create a `KEY` `Index`.
+
+The default is `FUNCTIONAL` and results in one of the `QueryService.createIndex(..)` methods
+being invoked.
+
+See the _Spring Data GemFire_ XML schema for a full set of options.
+
+For more information on Indexing in Pivotal GemFire, see http://gemfire90.docs.pivotal.io/geode/developing/query_index/query_index.html[Working with Indexes]
+in Pivotal GemFire's User Guide.
+
+== Defining Indexes
+
+In addition to creating Indexes upfront as `Index` bean definitions are processed by _Spring Data GemFire_
+on _Spring_ container initialization, you may also *define* all of your application Indexes prior to creating
+them by using the `define` attribute, like so...
+
+[source,xml]
+----
+
+----
+
+When `define` is set to `true` (defaults to `false`), this will not actually create the `Index` right then and there.
+All "defined" Indexes are created all at once, when the _Spring_ `ApplicationContext` is "refreshed", or, that is,
+when a `ContextRefreshedEvent` is published by the _Spring_ container. _Spring Data GemFire_ registers itself as
+an `ApplicationListener` listening for the `ContextRefreshedEvent`. When fired, _Spring Data GemFire_ will call
+http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#createDefinedIndexes--[QueryService.createDefinedIndexes()].
+
+Defining Indexes and creating them all at once helps promote speed and efficiency when creating Indexes.
+
+See http://gemfire90.docs.pivotal.io/geode/developing/query_index/create_multiple_indexes.html[Creating Multiple Indexes at Once]
+for more details.
+
+== `IgnoreIfExists` and `Override`
+
+Two _Spring Data GemFire_ `Index` configuration options warrant special mention here: `ignoreIfExists` and `override`.
+
+These options correspond to the `ignore-if-exists` and `override` attributes on the `<gfe:index>` element
+in _Spring Data GemFire's_ XML schema, respectively.
+
+WARNING: Make sure you absolutely understand what you are doing before using either of these options. These options can
+affect the performance and/or resources (e.g. memory) consumed by your application at runtime. As such, both of
+these options are disabled (i.e. set to `false`) in SDG by default.
+
+NOTE: These options are only available in _Spring Data GemFire_ and exist to workaround known limitations
+with Pivotal GemFire; there are no equivalent options or functionality available in GemFire itself.
+
+Each option significantly differs in behavior and entirely depends on the type of GemFire `Index` _Exception_ thrown.
+This also means that neither option has any effect if a GemFire Index-type _Exception_ is *not* thrown. These options
+are meant to specifically handle GemFire `IndexExistsExceptions` and `IndexNameConflictExceptions`, which can occur
+for various, sometimes obscure reasons. But, in general...
+
+* An http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/IndexExistsException.html[IndexExistsException]
+is thrown when there exists another `Index` with the same definition but different name when attempting to
+create an `Index`.
+
+* An http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/IndexNameConflictException.html[IndexNameConflictException]
+is thrown when there exists another `Index` with the same name but possibly different definition when attempting to
+create an `Index`.
+
+_Spring Data GemFire's_ default behavior is to *_fail-fast_*, always! So, neither `Index` _Exception_ will be "handled"
+by default; these `Index` _Exceptions_ are simply wrapped in a SDG `GemfireIndexException` and rethrown. If you wish
+for _Spring Data GemFire_ to handle them for you, then you can set either of these `Index` bean definition options.
+
+`IgnoreIfExists` always takes *precedence* over `Override`, primarily because it uses less resources given it returns
+the "existing" `Index` in both exceptional cases.
+
+=== `IgnoreIfExists` Behavior
+
+When an `IndexExistsException` is thrown and `ignoreIfExists` is set to `true` (or `<gfe:index ignore-if-exists="true">`),
+then the `Index` that would have been created by this `Index` bean definition / declaration will be "*ignored*",
+and the "existing" `Index` will be returned.
+
+There is very little consequence in returning the "existing" `Index` since the `Index` "definition" is the same,
+as deemed by GemFire itself, *not* SDG.
+
+However, this also means that *no* `Index` with the "`name`" specified in your `Index` bean definition / declaration
+will "actually" exist from GemFire's perspective either (i.e. with
+http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes--[QueryService.getIndexes()]).
+Therefore, you should be careful when writing OQL query statements that use _Query Hints_, especially _Hints_ that refer
+to the application `Index` being "*ignored*". Those _Query Hints_ will need to be changed.
+
+Now, when an `IndexNameConflictException` is thrown and `ignoreIfExists` is set to `true` (or `<gfe:index ignore-if-exists="true">`),
+then the `Index` that would have been created by this `Index` bean definition / declaration will also be "*ignored*",
+and the "existing" Index will be returned, just like when an `IndexExistsException` is thrown.
+
+However, there is more risk in returning the "existing" `Index` and "*ignoring*" the application's definition
+of the `Index` when an `IndexNameConflictException` is thrown since, for a `IndexNameConflictException`, while the "names"
+of the conflicting Indexes are the same, the "definitions" could very well be different! This obviously could have
+implications for OQL queries specific to the application, where you would presume the Indexes were defined specifically
+with the application data access patterns and queries in mind. However, if like named Indexes differ in definition,
+this might not be the case. So, make sure you verify.
+
+NOTE: SDG makes a best effort to inform the user when the `Index` being ignored is significantly different
+in its definition from the "existing" `Index`. However, in order for SDG to accomplish this, it must be able to "find"
+the existing `Index`, which is looked up using the GemFire API (the only means available).
+
+
+=== `Override` Behavior
+
+When an `IndexExistsException` is thrown and `override` is set to `true` (or `<gfe:index override="true">`), then
+the `Index` is effectively "_renamed_". Remember, `IndexExistsExceptions` are thrown when multiple Indexes exist,
+all having the same "definition" but different "names".
+
+_Spring Data GemFire_ can only accomplish this using GemFire's API, by first "_removing_" the "existing" `Index`
+and then "_recreating_" the `Index` with the *new* name. It is possible that either the remove or subsequent
+create invocation could fail. There is no way to execute both actions atomically and rollback this joint operation
+if either fails.
+
+However, if it succeeds, then you have the same problem as before with the "_ignoreIfExists_" option. Any existing OQL
+query statement using "_Query Hints_" referring to the old `Index` by name must be changed.
+
+Now, when an `IndexNameConflictException` is thrown and `override` is set to `true` (or `<gfe:index override="true">`),
+then potentially the "existing" `Index` will be "_re-defined_". I say "potentially", because it is possible for the
+"like-named", "existing" `Index` to have exactly the same definition and name when an `IndexNameConflictException`
+is thrown.
+
+If so, SDG is *smart* and will just return the "existing" Index as is, even on `override`. There is no harm in this
+since both the "name" and the "definition" are exactly the same. Of course, SDG can only accomplish this when
+SDG is able to "find" the "existing" `Index`, which is dependent on GemFire's APIs. If it cannot find it,
+nothing happens and a SDG `GemfireIndexException` is thrown wrapping the `IndexNameConflictException`.
+
+However, when the "definition" of the "existing" `Index` is different, then SDG will attempt to "_recreate_" the `Index`
+using the `Index` definition specified in the `Index` bean definition /declaration. Make sure this is what you want
+and make sure the `Index` definition matches your expectations and application requirements.
+
+=== How does `IndexNameConflictExceptions` actually happen?
+
+It is probably not all that uncommon for `IndexExistsExceptions` to be thrown, especially when
+multiple configuration sources are used to configure GemFire (e.g. _Spring Data GemFire_, GemFire _Cluster Config_,
+maybe GemFire native `cache.xml`, the API, etc, etc). You should definitely prefer 1 configuration method here
+and stick with it.
+
+_However, when does an `IndexNameConflictException` get thrown?_
+
+One particular case is an `Index` defined on a `PARTITION` _Region_ (PR). When an `Index` is defined on
+a `PARTITION` _Region_ (e.g. "X"), GemFire distributes the `Index` definition (and name) to other peer members
+in the cluster that also host the same `PARTITION` _Region_ (i.e. "X"). The distribution of this `Index` definition
+to and subsequent creation of this `Index` by peer members on a "need-to-know" basis (i.e. those hosting the same PR)
+is performed asynchronously.
+
+During this window of time, it is possible that these "pending" PR `Indexes` will not be identifiable by GemFire,
+such as with a call to http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes--[QueryService.getIndexes()]
+or with http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndexes-org.apache.geode.cache.Region-[QueryService.getIndexes(:Region)],
+or even with http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/query/QueryService.html#getIndex-org.apache.geode.cache.Region-java.lang.String-[QueryService.getIndex(:Region, indexName:String)].
+
+As such, the only way for SDG or other GemFire cache client applications (not involving _Spring_) to know for sure,
+is to just attempt to create the `Index`. If it fails with either an `IndexNameConflictException`,
+or even an `IndexExistsException`, then you will know. This is because the `QueryService` `Index` creation waits on
+"pending" `Index` definitions, where as the other GemFire API calls do not.
+
+In any case, SDG makes a best effort and attempts to inform the user what has or is happening along with
+the corrective action. Given all GemFire `QueryService.createIndex(..)` methods are synchronous, "blocking" operations,
+then the state of GemFire should be consistent and accessible after either of these Index-type _Exceptions_ are thrown,
+in which case, SDG can inspect the state of the system and respond/act accordingly, based on the user's
+desired configuration.
+
+In all other cases, SDG will simply *_fail-fast_*!
diff --git a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java
index b275452d..bf477a0b 100644
--- a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java
+++ b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java
@@ -38,6 +38,14 @@ import com.gemstone.gemfire.cache.query.IndexNameConflictException;
@SuppressWarnings({ "serial", "unused" })
public class GemfireIndexException extends DataIntegrityViolationException {
+ public GemfireIndexException(Exception cause) {
+ this(cause.getMessage(), cause);
+ }
+
+ public GemfireIndexException(String message, Exception cause) {
+ super(message, cause);
+ }
+
public GemfireIndexException(IndexCreationException cause) {
this(cause.getMessage(), cause);
}
@@ -77,5 +85,4 @@ public class GemfireIndexException extends DataIntegrityViolationException {
public GemfireIndexException(String message, IndexNameConflictException cause) {
super(message, cause);
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/GemfireUtils.java b/src/main/java/org/springframework/data/gemfire/GemfireUtils.java
index e7e87f0a..e63cf40d 100644
--- a/src/main/java/org/springframework/data/gemfire/GemfireUtils.java
+++ b/src/main/java/org/springframework/data/gemfire/GemfireUtils.java
@@ -18,12 +18,11 @@ package org.springframework.data.gemfire;
import java.util.concurrent.ConcurrentMap;
-import org.springframework.data.gemfire.util.DistributedSystemUtils;
-import org.springframework.util.ClassUtils;
-
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
-import com.gemstone.gemfire.cache.client.ClientCacheFactory;
+
+import org.springframework.data.gemfire.util.CacheUtils;
+import org.springframework.util.ClassUtils;
/**
* GemfireUtils is an abstract utility class encapsulating common functionality to access features and capabilities
@@ -34,30 +33,10 @@ import com.gemstone.gemfire.cache.client.ClientCacheFactory;
* @since 1.3.3
*/
@SuppressWarnings("unused")
-public abstract class GemfireUtils extends DistributedSystemUtils {
+public abstract class GemfireUtils extends CacheUtils {
public final static String GEMFIRE_VERSION = CacheFactory.getVersion();
- public static boolean closeCache() {
- try {
- CacheFactory.getAnyInstance().close();
- return true;
- }
- catch (Exception ignore) {
- return false;
- }
- }
-
- public static boolean closeClientCache() {
- try {
- ClientCacheFactory.getAnyInstance().close();
- return true;
- }
- catch (Exception ignore) {
- return false;
- }
- }
-
public static boolean isGemfireVersionGreaterThanEqual(double expectedVersion) {
double actualVersion = Double.parseDouble(GEMFIRE_VERSION.substring(0, 3));
return actualVersion >= expectedVersion;
@@ -103,5 +82,4 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
//System.out.printf("Is GemFire Version 6.5 of Above? %1$s%n", isGemfireVersion65OrAbove());
//System.out.printf("Is GemFire Version 7.0 of Above? %1$s%n", isGemfireVersion7OrAbove());
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
index b94d2899..d66abe34 100644
--- a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
+++ b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
@@ -16,14 +16,8 @@
package org.springframework.data.gemfire;
-import java.util.Collection;
-
-import org.springframework.beans.factory.BeanNameAware;
-import org.springframework.beans.factory.FactoryBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.data.gemfire.util.CollectionUtils;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
+import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
+import static org.springframework.data.gemfire.util.SpringUtils.defaultIfNull;
import com.gemstone.gemfire.cache.RegionService;
import com.gemstone.gemfire.cache.client.ClientCache;
@@ -32,23 +26,40 @@ import com.gemstone.gemfire.cache.query.IndexExistsException;
import com.gemstone.gemfire.cache.query.IndexNameConflictException;
import com.gemstone.gemfire.cache.query.QueryService;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
/**
- * Spring FactoryBean for easy declarative creation of GemFire Indexes.
- *
+ * Spring {@link FactoryBean} for simple and easy declarative creation of GemFire Indexes.
+ *
* @author Costin Leau
* @author David Turanski
* @author John Blum
- * @see org.springframework.beans.factory.InitializingBean
- * @see org.springframework.beans.factory.BeanNameAware
* @see org.springframework.beans.factory.FactoryBean
+ * @see org.springframework.beans.factory.InitializingBean
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory
+ * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport
+ * @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.RegionService
* @see com.gemstone.gemfire.cache.query.Index
* @see com.gemstone.gemfire.cache.query.QueryService
* @since 1.0.0
*/
-public class IndexFactoryBean implements InitializingBean, FactoryBean, BeanNameAware {
+public class IndexFactoryBean extends AbstractFactoryBeanSupport implements InitializingBean {
- private boolean override = true;
+ 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 ignoreIfExists = false;
+ private boolean override = false;
private Index index;
@@ -58,53 +69,105 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
private RegionService cache;
- private String beanName;
private String expression;
private String from;
private String imports;
private String name;
+ @Override
public void afterPropertiesSet() throws Exception {
- Assert.notNull(cache, "The GemFire Cache reference must not be null!");
- queryService = lookupQueryService();
+ this.cache = resolveCache();
- 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");
+ String indexName = resolveIndexName();
- if (IndexType.isKey(indexType)) {
- Assert.isNull(imports, "The 'imports' property is not supported for a Key Index.");
- }
+ this.queryService = resolveQueryService();
- String indexName = (StringUtils.hasText(name) ? name : beanName);
+ assertIndexDefinitionConfiguration();
- Assert.hasText(indexName, "The Index bean id or name is required!");
+ this.index = createIndex(this.queryService, indexName);
- index = createIndex(queryService, indexName);
+ registerAlias(getBeanName(), indexName);
}
+ /* (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() {
+
+ RegionService resolvedCache = (this.cache != null ? this.cache : GemfireUtils.resolveGemFireCache());
+
+ Assert.state(resolvedCache != null, "Cache is required");
+
+ return resolvedCache;
+ }
+
+ /* (non-Javadoc) */
+ String resolveIndexName() {
+
+ String resolvedIndexName = (StringUtils.hasText(this.name) ? this.name : getBeanName());
+
+ Assert.hasText(resolvedIndexName, "Index name is required");
+
+ return resolvedIndexName;
+ }
+
+ /* (non-Javadoc) */
+ QueryService resolveQueryService() {
+
+ QueryService resolvedQueryService = (this.queryService != null ? this.queryService : lookupQueryService());
+
+ Assert.state(resolvedQueryService != null, "QueryService is required to create an Index");
+
+ return resolvedQueryService;
+ }
+
+ /* (non-Javadoc) */
QueryService lookupQueryService() {
+
return (queryService != null ? queryService
: (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService()
: cache.getQueryService()));
}
- Index createIndex(QueryService queryService, String indexName) throws Exception {
- Index existingIndex = getExistingIndex(queryService, indexName);
+ /* (non-Javadoc) */
+ void registerAlias(String beanName, String indexName) {
- if (existingIndex != null) {
- if (override) {
- queryService.removeIndex(existingIndex);
- }
- else {
- return existingIndex;
+ BeanFactory beanFactory = getBeanFactory();
+
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ if (beanName != null && !beanName.equals(indexName)) {
+ ((ConfigurableBeanFactory) beanFactory).registerAlias(beanName, indexName);
}
}
+ }
+
+ /* (non-Javadoc) */
+ Index createIndex(QueryService queryService, String indexName) throws Exception {
+ return createIndex(queryService, indexName, false);
+ }
+
+ /* (non-Javadoc) */
+ private Index createIndex(QueryService queryService, String indexName, boolean retryAttempted) throws Exception {
+
+ IndexType indexType = this.indexType;
+
+ String expression = this.expression;
+ String from = this.from;
+ String imports = this.imports;
try {
if (IndexType.isKey(indexType)) {
- return queryService.createKeyIndex(indexName, expression, from);
+ return createKeyIndex(queryService, indexName, expression, from);
}
else if (IndexType.isHash(indexType)) {
return createHashIndex(queryService, indexName, expression, from, imports);
@@ -113,36 +176,199 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
return createFunctionalIndex(queryService, indexName, expression, from, imports);
}
}
- catch (IndexExistsException e) {
- throw new GemfireIndexException(String.format(
- "An Index with a different name having the same definition as this Index (%1$s) already exists",
- indexName), e);
- }
- catch (IndexNameConflictException e) {
- // NOTE technically, the only way for an IndexNameConflictException to be thrown is if
- // queryService.remove(existingIndex) above silently fails, since otherwise, when override is 'false',
- // the existingIndex is already being returned. Given this state of affairs, an Index with the provided
- // name is unresolvable based on what the user intended to happen, so just rethrow an Exception.
- throw new GemfireIndexException(String.format(
- "Failed to remove the existing Index%1$sbefore re-creating Index with name (%2$s)",
- (override ? " on override " : " "), indexName), e);
- }
- catch (Exception e) {
- if (existingIndex != null) {
- Collection indexes = queryService.getIndexes();
+ catch (IndexExistsException cause) {
- if (CollectionUtils.isEmpty(indexes) || !indexes.contains(existingIndex)) {
- queryService.getIndexes().add(existingIndex);
- return existingIndex;
- }
+ // Same definition, different name
+
+ Index existingIndexByDefinition =
+ tryToFindExistingIndexByDefinition(queryService, expression, from, indexType);
+
+ if (isIgnorable(existingIndexByDefinition)) {
+
+ logWarning("WARNING! You are choosing to ignore this Index [%1$s] and return the existing"
+ + " Index having the same basic definition [%2$s] but with a different name [%3$s];"
+ + " Make sure no OQL Query Hints refer to this Index by name [%1$s]",
+ indexName, toBasicIndexDefinition(), existingIndexByDefinition.getName());
+
+ return handleIgnore(existingIndexByDefinition);
}
+ else if (isOverridable(existingIndexByDefinition, retryAttempted)) {
- throw e;
+ // Log an informational warning to caution the user about using the override
+ logWarning("WARNING! You are attempting to 'override' an existing Index [%1$s]"
+ + " having the same basic definition [%2$s] as the Index that will be created"
+ + " by this IndexFactoryBean [%3$s]; 'Override' effectively 'renames' the existing"
+ + " Index [%1$s] by removing it then recreating it under the new name [%3$s] with"
+ + " the same definition; You should be careful to update any existing OQL Query"
+ + " Hints referring to the old Index name [%1$s] to now use the new name [%3$s]",
+ existingIndexByDefinition.getName(), toBasicIndexDefinition(), indexName);
+
+ return handleOverride(existingIndexByDefinition, queryService, indexName);
+ }
+ else {
+
+ String existingIndexName = (existingIndexByDefinition != null
+ ? existingIndexByDefinition.getName() : "unknown");
+
+ throw new GemfireIndexException(String.format(
+ "An Index with a different name [%1$s] having the same definition [%2$s] already exists;"
+ + " You may attempt to override the existing Index [%1$s] with the new name [%3$s]"
+ + " by setting the 'override' property to 'true'",
+ existingIndexName, toBasicIndexDefinition(), indexName), cause);
+ }
+ }
+ catch (IndexNameConflictException cause) {
+
+ // Same name; possibly different definition
+
+ Index existingIndexByName = tryToFindExistingIndexByName(queryService, indexName);
+
+ if (isIgnorable(existingIndexByName)) {
+ return handleIgnore(warnOnIndexDefinitionMismatch(existingIndexByName, indexName, "Returning"));
+ }
+ else if (isOverridable(existingIndexByName, retryAttempted)) {
+ return handleSmartOverride(warnOnIndexDefinitionMismatch(existingIndexByName, indexName,
+ "Overriding"), queryService, indexName);
+ }
+ else {
+
+ String existingIndexDefinition = (existingIndexByName != null
+ ? String.format(DETAILED_INDEX_DEFINITION, existingIndexByName.getName(),
+ existingIndexByName.getIndexedExpression(), existingIndexByName.getFromClause(), "unknown",
+ existingIndexByName.getType())
+ : "unknown");
+
+ throw new GemfireIndexException(String.format(
+ "An Index with the same name [%1$s] having possibly a different definition already exists;"
+ + " you may choose to ignore this Index definition [%2$s] and use the existing Index"
+ + " definition [%3$s] by setting the 'ignoreIfExists' property to 'true'",
+ indexName, toDetailedIndexDefinition(), existingIndexDefinition), cause);
+ }
+ }
+ catch (Exception cause) {
+ throw new GemfireIndexException(String.format("Failed to create Index [%s]",
+ toDetailedIndexDefinition()), cause);
}
}
+ /* (non-Javadoc) */
+ private boolean isIgnorable(Index existingIndex) {
+ return (isIgnoreIfExists() && existingIndex != null);
+ }
+
+ /* (non-Javadoc) */
+ @SuppressWarnings("all")
+ private boolean isIndexDefinitionMatch(Index index) {
+
+ boolean result = false;
+
+ if (index != null) {
+
+ IndexType thisIndexType = defaultIfNull(this.indexType, IndexType.FUNCTIONAL);
+
+ result = ObjectUtils.nullSafeEquals(index.getIndexedExpression(), this.expression)
+ && ObjectUtils.nullSafeEquals(index.getFromClause(), this.from)
+ && ObjectUtils.nullSafeEquals(IndexType.valueOf(index.getType()), thisIndexType);
+
+ }
+
+ return result;
+ }
+
+ /* (non-Javadoc) */
+ private boolean isNotIndexDefinitionMatch(Index index) {
+ return !isIndexDefinitionMatch(index);
+ }
+
+ /* (non-Javadoc) */
+ private boolean isOverridable(Index existingIndex, boolean retryAttempted) {
+ return (isOverride() && existingIndex != null && !retryAttempted);
+ }
+
+ /* (non-Javadoc) */
+ private Index warnOnIndexDefinitionMismatch(Index existingIndex, String indexName, String action) {
+
+ if (isNotIndexDefinitionMatch(existingIndex)) {
+
+ String existingIndexDefinition = String.format(BASIC_INDEX_DEFINITION, existingIndex.getIndexedExpression(),
+ existingIndex.getFromClause(), IndexType.valueOf(existingIndex.getType()));
+
+ logWarning("WARNING! %1$s existing Index [%2$s] having a definition [%3$s]"
+ + " that does not match the Index defined [%4$s] by this IndexFactoryBean [%5$s]",
+ action, existingIndex.getName(), existingIndexDefinition, toBasicIndexDefinition(), indexName);
+ }
+
+ return existingIndex;
+ }
+
+ /* (non-Javadoc) */
+ private Index handleIgnore(Index existingIndex) {
+
+ registerAlias(getBeanName(), existingIndex.getName());
+
+ return existingIndex;
+ }
+
+ /* (non-Javadoc) */
+ private Index handleOverride(Index existingIndex, QueryService queryService, String indexName) {
+ try {
+ // No way to tell whether the QueryService.remove(:Index) was successful or not! o.O
+ // Should return a boolean! Does it throw an RuntimeException? Javadoc is useless; #sigh
+ queryService.removeIndex(existingIndex);
+
+ return createIndex(queryService, indexName, true);
+ }
+ catch (Exception cause) {
+ throw new GemfireIndexException(String.format(
+ "Attempt to 'override' existing Index [%1$s] with the Index that would be created"
+ + " by this IndexFactoryBean [%2$s] failed; you should verify the state of"
+ + " your system and make sure the previously existing Index [%1$s] still exits",
+ existingIndex.getName(), indexName), cause);
+ }
+ }
+
+ /* (non-Javadoc) */
+ private Index handleSmartOverride(Index existingIndex, QueryService queryService, String indexName) {
+
+ return (existingIndex.getName().equalsIgnoreCase(indexName) && isIndexDefinitionMatch(existingIndex)
+ ? existingIndex
+ : handleOverride(existingIndex, queryService, indexName));
+ }
+
+ /* (non-Javadoc) */
+ String toBasicIndexDefinition() {
+ return String.format(BASIC_INDEX_DEFINITION, this.expression, this.from, this.indexType);
+ }
+
+ /* (non-Javadoc) */
+ String toDetailedIndexDefinition() {
+ return String.format(DETAILED_INDEX_DEFINITION,
+ this.name, this.expression, this.from, this.imports, this.indexType);
+ }
+
+ /* (non-Javadoc) */
+ Index createKeyIndex(QueryService queryService, String indexName, String expression, String from) throws Exception {
+ return queryService.createKeyIndex(indexName, expression, from);
+ }
+
+ /* (non-Javadoc) */
+ Index createHashIndex(QueryService queryService, String indexName, String expression, String from, String imports)
+ throws Exception {
+
+ boolean hasImports = StringUtils.hasText(imports);
+
+ if (hasImports) {
+ return queryService.createHashIndex(indexName, expression, from, imports);
+ }
+ else {
+ return queryService.createHashIndex(indexName, expression, from);
+ }
+ }
+
+ /* (non-Javadoc) */
Index createFunctionalIndex(QueryService queryService, String indexName, String expression, String from,
String imports) throws Exception {
+
if (StringUtils.hasText(imports)) {
return queryService.createIndex(indexName, expression, from, imports);
}
@@ -151,18 +377,26 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
}
}
- Index createHashIndex(QueryService queryService, String indexName, String expression, String from,
- String imports) throws Exception {
- if (StringUtils.hasText(imports)) {
- return queryService.createHashIndex(indexName, expression, from, imports);
- }
- else {
- return queryService.createHashIndex(indexName, expression, from);
+ /* (non-Javadoc) */
+ Index tryToFindExistingIndexByDefinition(QueryService queryService,
+ String expression, String fromClause, IndexType indexType) {
+
+ for (Index index : nullSafeCollection(queryService.getIndexes())) {
+ if (index.getIndexedExpression().equalsIgnoreCase(expression)
+ && index.getFromClause().equalsIgnoreCase(fromClause)
+ && indexType.equals(IndexType.valueOf(index.getType()))) {
+
+ return index;
+ }
}
+
+ return null;
}
- Index getExistingIndex(QueryService queryService, String indexName) {
- for (Index index : CollectionUtils.nullSafeCollection(queryService.getIndexes())) {
+ /* (non-Javadoc) */
+ Index tryToFindExistingIndexByName(QueryService queryService, String indexName) {
+
+ for (Index index : nullSafeCollection(queryService.getIndexes())) {
if (index.getName().equalsIgnoreCase(indexName)) {
return index;
}
@@ -171,91 +405,227 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
return null;
}
- public Index getObject() {
- return index;
+ /**
+ * Returns a reference to the {@link Index} created by this {@link IndexFactoryBean}.
+ *
+ * @return a reference to the {@link Index} created by this {@link IndexFactoryBean}.
+ * @see com.gemstone.gemfire.cache.query.Index
+ */
+ public Index getIndex() {
+ return this.index;
}
+ /* (non-Javadoc) */
+ @Override
+ public Index getObject() {
+
+ this.index = (this.index != null ? this.index
+ : tryToFindExistingIndexByName(resolveQueryService(), resolveIndexName()));
+
+ return this.index;
+ }
+
+ /* (non-Javadoc) */
+ @Override
public Class> getObjectType() {
+ Index index = getIndex();
return (index != null ? index.getClass() : Index.class);
}
- public boolean isSingleton() {
- return true;
- }
-
/**
- * Sets the underlying cache used for creating indexes.
- *
- * @param cache cache used for creating indexes.
+ * Sets a reference to the {@link RegionService}.
+ *
+ * @param cache reference to the {@link RegionService}.
+ * @see com.gemstone.gemfire.cache.RegionService
*/
public void setCache(RegionService cache) {
this.cache = cache;
}
/**
- * Sets the query service used for creating indexes.
- *
- * @param service query service used for creating indexes.
- */
- public void setQueryService(QueryService service) {
- this.queryService = service;
- }
-
- public void setBeanName(String name) {
- this.beanName = name;
- }
-
- /**
- * @param name the name to set
+ * Sets the name of the {@link Index}.
+ *
+ * @param name {@link String} containing the name given to the {@link Index}.
*/
public void setName(String name) {
this.name = name;
}
/**
- * @param expression the expression to set
+ * Sets the {@link QueryService} used to create the {@link Index}.
+ *
+ * @param service {@link QueryService} used to create the {@link Index}.
+ * @see com.gemstone.gemfire.cache.query.QueryService
+ */
+ public void setQueryService(QueryService service) {
+ this.queryService = service;
+ }
+
+ /**
+ * @param expression Index expression to set
*/
public void setExpression(String expression) {
this.expression = expression;
}
/**
- * @param from the from to set
+ * @param from Index from clause to set
*/
public void setFrom(String from) {
this.from = from;
}
/**
- * @param imports the imports to set
+ * @param imports Index imports to set
*/
public void setImports(String imports) {
this.imports = imports;
}
/**
- * @param override the override to set
+ * Configures whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}
+ * when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown.
+ *
+ * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
+ * but with another name.
+ *
+ * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
+ * but possibly a different definition.
+ *
+ * When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown,
+ * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation
+ * and the name of the existing {@link Index} is added as an alias for this bean.
+ *
+ * When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown,
+ * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation.
+ * A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index}
+ * are different.
+ *
+ * {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}.
+ *
+ * Defaults to {@literal false}.
+ *
+ * @param ignore boolean value indicating whether to ignore the {@link Index} defined by
+ * this {@link IndexFactoryBean}. Default is {@literal false}.
+ * @see #setOverride(boolean)
+ */
+ public void setIgnoreIfExists(boolean ignore) {
+ this.ignoreIfExists = ignore;
+ }
+
+ /**
+ * Determines whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}
+ * when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown.
+ *
+ * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
+ * but with another name.
+ *
+ * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
+ * but possibly a different definition.
+ *
+ * When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown,
+ * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation
+ * and the name of the existing {@link Index} is added as an alias for this bean.
+ *
+ * When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown,
+ * then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation.
+ * A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index}
+ * are different.
+ *
+ * {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}.
+ *
+ * Defaults to {@literal false}.
+ *
+ * @return a boolean value indicating whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}.
+ * Default is {@literal false}.
+ * @see #setIgnoreIfExists(boolean)
+ */
+ public boolean isIgnoreIfExists() {
+ return this.ignoreIfExists;
+ }
+
+ /**
+ * Configures whether to override an existing {@link Index} having the same definition but different name
+ * as the {@link Index} that would be created by this {@link IndexFactoryBean}.
+ *
+ * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
+ * but with another name.
+ *
+ * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
+ * but possibly a different definition.
+ *
+ * With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override
+ * is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index}
+ * will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean}
+ * under the new {@link #resolveIndexName() name} having the same definition.
+ *
+ * With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown,
+ * then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition.
+ * When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding
+ * effectively just rebuilds the {@link Index}.
+ *
+ * {@literal ignoreIfExists} takes precedence over {@literal override}.
+ *
+ * Defaults to {@literal false}.
+ *
+ * @param override boolean value indicating whether an existing {@link Index} will be removed and recreated
+ * by this {@link IndexFactoryBean}. Default is {@literal false}.
+ * @see #setIgnoreIfExists(boolean)
*/
public void setOverride(boolean override) {
this.override = override;
}
/**
- * @param type the type to set
+ * Determines whether to override an existing {@link Index} having the same definition but different name
+ * as the {@link Index} that would be created by this {@link IndexFactoryBean}.
+ *
+ * An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
+ * but with another name.
+ *
+ * An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
+ * but possibly a different definition.
+ *
+ * With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override
+ * is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index}
+ * will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean}
+ * under the new {@link #resolveIndexName() name} having the same definition.
+ *
+ * With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown,
+ * then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition.
+ * When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding
+ * effectively just rebuilds the {@link Index}.
+ *
+ * {@literal ignoreIfExists} takes precedence over {@literal override}.
+ *
+ * Defaults to {@literal false}.
+ *
+ * @return a boolean value indicating whether an existing {@link Index} will be removed and recreated
+ * by this {@link IndexFactoryBean}. Default is {@literal false}.
+ * @see #setOverride(boolean)
+ */
+ public boolean isOverride() {
+ return this.override;
+ }
+
+ /**
+ * Set the {@link IndexType type} of the {@link Index} as a {@link String}.
+ *
+ * @param type {@link String} specifying the {@link IndexType type} of the {@link Index}.
+ * @see org.springframework.data.gemfire.IndexType#valueOf(String)
+ * @see #setType(IndexType)
*/
public void setType(String type) {
setType(IndexType.valueOfIgnoreCase(type));
}
/**
- * Sets the type of GemFire Index to create.
+ * Set the {@link IndexType type} of the {@link Index}.
*
- * @param indexType the IndexType enumerated value indicating the type of GemFire Index
- * that will be created by this Spring FactoryBean.
+ * @param type {@link IndexType} indicating the type of the {@link Index}.
* @see org.springframework.data.gemfire.IndexType
*/
- public void setType(IndexType indexType) {
- this.indexType = indexType;
+ public void setType(IndexType type) {
+ this.indexType = type;
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/IndexType.java b/src/main/java/org/springframework/data/gemfire/IndexType.java
index d1a936eb..9acd503e 100644
--- a/src/main/java/org/springframework/data/gemfire/IndexType.java
+++ b/src/main/java/org/springframework/data/gemfire/IndexType.java
@@ -25,6 +25,7 @@ package org.springframework.data.gemfire;
*/
@SuppressWarnings({ "deprecation", "unused" })
public enum IndexType {
+
FUNCTIONAL(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL),
HASH(com.gemstone.gemfire.cache.query.IndexType.HASH),
PRIMARY_KEY(com.gemstone.gemfire.cache.query.IndexType.PRIMARY_KEY),
@@ -49,7 +50,7 @@ public enum IndexType {
* @return a boolean value indicating whether the IndexType is a "FUNCTIONAL" Index.
* @see #isFunctional()
*/
- public static boolean isFunctional(final IndexType indexType) {
+ public static boolean isFunctional(IndexType indexType) {
return (indexType != null && indexType.isFunctional());
}
@@ -60,7 +61,7 @@ public enum IndexType {
* @return a boolean value indicating whether the IndexType is a "HASH" Index.
* @see #isHash()
*/
- public static boolean isHash(final IndexType indexType) {
+ public static boolean isHash(IndexType indexType) {
return (indexType != null && indexType.isHash());
}
@@ -71,7 +72,7 @@ public enum IndexType {
* @return a boolean value indicating whether the IndexType is a "KEY" Index.
* @see #isFunctional()
*/
- public static boolean isKey(final IndexType indexType) {
+ public static boolean isKey(IndexType indexType) {
return (indexType != null && indexType.isKey());
}
@@ -84,7 +85,8 @@ public enum IndexType {
* any IndexType in this enumeration.
* @see com.gemstone.gemfire.cache.query.IndexType
*/
- public static IndexType valueOf(final com.gemstone.gemfire.cache.query.IndexType gemfireIndexType) {
+ public static IndexType valueOf(com.gemstone.gemfire.cache.query.IndexType gemfireIndexType) {
+
for (IndexType indexType : values()) {
if (indexType.getGemfireIndexType().equals(gemfireIndexType)) {
return indexType;
@@ -101,7 +103,8 @@ public enum IndexType {
* @return an IndexType matching the given String.
* @see java.lang.String#equalsIgnoreCase(String)
*/
- public static IndexType valueOfIgnoreCase(final String value) {
+ public static IndexType valueOfIgnoreCase(String value) {
+
for (IndexType indexType : values()) {
if (indexType.name().equalsIgnoreCase(value)) {
return indexType;
@@ -147,5 +150,4 @@ public enum IndexType {
public boolean isKey() {
return (this.equals(KEY) || this.equals(PRIMARY_KEY));
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/config/GemfireConstants.java b/src/main/java/org/springframework/data/gemfire/config/GemfireConstants.java
index bcce9372..2b78219a 100644
--- a/src/main/java/org/springframework/data/gemfire/config/GemfireConstants.java
+++ b/src/main/java/org/springframework/data/gemfire/config/GemfireConstants.java
@@ -24,12 +24,12 @@ package org.springframework.data.gemfire.config;
@SuppressWarnings("unused")
public interface GemfireConstants {
- static final String DEFAULT_GEMFIRE_CACHE_NAME = "gemfireCache";
- static final String DEFAULT_GEMFIRE_FUNCTION_SERVICE_NAME = "gemfireFunctionService";
- static final String DEFAULT_GEMFIRE_POOL_NAME = "DEFAULT";
- static final String DEFAULT_GEMFIRE_TRANSACTION_MANAGER_NAME = "gemfireTransactionManager";
+ String DEFAULT_GEMFIRE_CACHE_NAME = "gemfireCache";
+ String DEFAULT_GEMFIRE_FUNCTION_SERVICE_NAME = "gemfireFunctionService";
+ String DEFAULT_GEMFIRE_POOL_NAME = "DEFAULT";
+ String DEFAULT_GEMFIRE_TRANSACTION_MANAGER_NAME = "gemfireTransactionManager";
@Deprecated
- static final String DEFAULT_GEMFIRE_TXMANAGER_NAME = DEFAULT_GEMFIRE_TRANSACTION_MANAGER_NAME;
+ String DEFAULT_GEMFIRE_TXMANAGER_NAME = DEFAULT_GEMFIRE_TRANSACTION_MANAGER_NAME;
}
diff --git a/src/main/java/org/springframework/data/gemfire/config/IndexParser.java b/src/main/java/org/springframework/data/gemfire/config/IndexParser.java
index 33e05afe..2dc138fa 100644
--- a/src/main/java/org/springframework/data/gemfire/config/IndexParser.java
+++ b/src/main/java/org/springframework/data/gemfire/config/IndexParser.java
@@ -23,12 +23,13 @@ import org.springframework.data.gemfire.IndexFactoryBean;
import org.w3c.dom.Element;
/**
- * Namespace parser for <index;gt; bean definitions.
- *
+ * Namespace parser for <gfe:index;gt; bean definitions.
+ *
* @author Costin Leau
* @author John Blum
* @see org.springframework.beans.factory.support.BeanDefinitionBuilder
* @see org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser
+ * @see org.springframework.beans.factory.xml.ParserContext
* @see org.springframework.data.gemfire.IndexFactoryBean
* @since 1.1.0
*/
@@ -48,5 +49,4 @@ class IndexParser extends AbstractSimpleBeanDefinitionParser {
ParsingUtils.setPropertyReference(element, builder, "cache-ref", "cache");
super.doParse(element, parserContext, builder);
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java
new file mode 100644
index 00000000..03da56b6
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.gemfire.support;
+
+import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.util.Assert;
+
+/**
+ * The {@link AbstractFactoryBeanSupport} class is an abstract Spring {@link FactoryBean} base class implementation
+ * encapsulating operations common to SDG's {@link FactoryBean} implementations.
+ *
+ * @author John Blum
+ * @see org.springframework.beans.factory.BeanClassLoaderAware
+ * @see org.springframework.beans.factory.BeanFactory
+ * @see org.springframework.beans.factory.BeanFactoryAware
+ * @see org.springframework.beans.factory.BeanNameAware
+ * @see org.springframework.beans.factory.FactoryBean
+ * @since 1.0.0
+ */
+public abstract class AbstractFactoryBeanSupport implements FactoryBean,
+ BeanClassLoaderAware, BeanFactoryAware, BeanNameAware {
+
+ protected static final boolean DEFAULT_SINGLETON = true;
+
+ private ClassLoader beanClassLoader;
+
+ private BeanFactory beanFactory;
+
+ private final Log log = newLog();
+
+ private String beanName;
+
+ /**
+ * Constructs a new instance of {@link Log} to log statements printed by Spring Data GemFire/Geode.
+ *
+ * @return a new instance of {@link Log}.
+ * @see org.apache.commons.logging.LogFactory#getLog(Class)
+ * @see org.apache.commons.logging.Log
+ */
+ protected Log newLog() {
+ return LogFactory.getLog(getClass());
+ }
+
+ /**
+ * Sets a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes.
+ *
+ * @param classLoader {@link ClassLoader} used by the Spring container to load and create bean classes.
+ * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader)
+ * @see java.lang.ClassLoader
+ */
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.beanClassLoader = classLoader;
+ }
+
+ /**
+ * Returns a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes.
+ *
+ * @return the {@link ClassLoader} used by the Spring container to load and create bean classes.
+ * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader)
+ * @see java.lang.ClassLoader
+ */
+ public ClassLoader getBeanClassLoader() {
+ return this.beanClassLoader;
+ }
+
+ /**
+ * Sets a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared.
+ *
+ * @param beanFactory reference to the declaring Spring {@link BeanFactory}.
+ * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory)
+ * @see org.springframework.beans.factory.BeanFactory
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Returns a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared.
+ *
+ * @return a reference to the declaring Spring {@link BeanFactory}.
+ * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory)
+ * @see org.springframework.beans.factory.BeanFactory
+ */
+ public BeanFactory getBeanFactory() {
+ return this.beanFactory;
+ }
+
+ /**
+ * Sets the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container.
+ *
+ * @param name {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container.
+ * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String)
+ * @see java.lang.String
+ */
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ /**
+ * Returns the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container.
+ *
+ * @return the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container.
+ * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String)
+ * @see java.lang.String
+ */
+ public String getBeanName() {
+ return this.beanName;
+ }
+
+ /**
+ * Returns a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}.
+ *
+ * @return a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}.
+ * @see org.apache.commons.logging.Log
+ */
+ protected Log getLog() {
+ return this.log;
+ }
+
+ /**
+ * Indicates that this {@link FactoryBean} produces a single bean instance.
+ *
+ * @return {@literal true} by default.
+ * @see org.springframework.beans.factory.FactoryBean#isSingleton()
+ */
+ @Override
+ public boolean isSingleton() {
+ return DEFAULT_SINGLETON;
+ }
+
+ /**
+ * Logs the {@link String message} formatted with the array of {@link Object arguments} at debug level.
+ *
+ * @param message {@link String} containing the message to log.
+ * @param args array of {@link Object arguments} used to format the {@code message}.
+ * @see #logDebug(Supplier)
+ */
+ protected void logDebug(String message, Object... args) {
+ logDebug(StringFormatSupplier.of(message, args));
+ }
+
+ /**
+ * Logs the {@link String message} supplied by the given {@link Supplier} at debug level.
+ *
+ * @param message {@link Supplier} containing the {@link String message} and arguments to log.
+ * @see org.apache.commons.logging.Log#isDebugEnabled()
+ * @see org.apache.commons.logging.Log#debug(Object)
+ * @see #getLog()
+ */
+ protected void logDebug(Supplier message) {
+
+ Log log = getLog();
+
+ if (log.isDebugEnabled()) {
+ log.debug(message.get());
+ }
+ }
+
+ /**
+ * Logs the {@link String message} formatted with the array of {@link Object arguments} at info level.
+ *
+ * @param message {@link String} containing the message to log.
+ * @param args array of {@link Object arguments} used to format the {@code message}.
+ * @see #logInfo(Supplier)
+ */
+ protected void logInfo(String message, Object... args) {
+ logInfo(StringFormatSupplier.of(message, args));
+ }
+
+ /**
+ * Logs the {@link String message} supplied by the given {@link Supplier} at info level.
+ *
+ * @param message {@link Supplier} containing the {@link String message} and arguments to log.
+ * @see org.apache.commons.logging.Log#isInfoEnabled()
+ * @see org.apache.commons.logging.Log#info(Object)
+ * @see #getLog()
+ */
+ protected void logInfo(Supplier message) {
+
+ Log log = getLog();
+
+ if (log.isInfoEnabled()) {
+ log.info(message.get());
+ }
+ }
+
+ /**
+ * Logs the {@link String message} formatted with the array of {@link Object arguments} at warn level.
+ *
+ * @param message {@link String} containing the message to log.
+ * @param args array of {@link Object arguments} used to format the {@code message}.
+ * @see #logWarning(Supplier)
+ */
+ protected void logWarning(String message, Object... args) {
+ logWarning(StringFormatSupplier.of(message, args));
+ }
+
+ /**
+ * Logs the {@link String message} supplied by the given {@link Supplier} at warn level.
+ *
+ * @param message {@link Supplier} containing the {@link String message} and arguments to log.
+ * @see org.apache.commons.logging.Log#isWarnEnabled()
+ * @see org.apache.commons.logging.Log#warn(Object)
+ * @see #getLog()
+ */
+ protected void logWarning(Supplier message) {
+
+ Log log = getLog();
+
+ if (log.isWarnEnabled()) {
+ log.warn(message.get());
+ }
+ }
+
+ /**
+ * Logs the {@link String message} formatted with the array of {@link Object arguments} at error level.
+ *
+ * @param message {@link String} containing the message to log.
+ * @param args array of {@link Object arguments} used to format the {@code message}.
+ * @see #logError(Supplier)
+ */
+ protected void logError(String message, Object... args) {
+ logError(StringFormatSupplier.of(message, args));
+ }
+
+ /**
+ * Logs the {@link String message} supplied by the given {@link Supplier} at error level.
+ *
+ * @param message {@link Supplier} containing the {@link String message} and arguments to log.
+ * @see org.apache.commons.logging.Log#isErrorEnabled()
+ * @see org.apache.commons.logging.Log#error(Object)
+ * @see #getLog()
+ */
+ protected void logError(Supplier message) {
+
+ Log log = getLog();
+
+ if (log.isErrorEnabled()) {
+ log.error(message.get());
+ }
+ }
+
+ protected interface Supplier {
+
+ T get();
+
+ }
+
+ protected static class StringFormatSupplier implements Supplier {
+
+ private final Object[] args;
+
+ private final String message;
+
+ protected static StringFormatSupplier of(String message, Object... args) {
+ return new StringFormatSupplier(message, nullSafeArray(args));
+ }
+
+ private StringFormatSupplier(String message, Object... args) {
+
+ Assert.hasText(message, "Message is required");
+
+ this.message = message;
+ this.args = args;
+ }
+
+ @Override
+ public String get() {
+ return String.format(this.message, this.args);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
new file mode 100644
index 00000000..7dc617cd
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.springframework.data.gemfire.util;
+
+import com.gemstone.gemfire.cache.Cache;
+import com.gemstone.gemfire.cache.CacheClosedException;
+import com.gemstone.gemfire.cache.CacheFactory;
+import com.gemstone.gemfire.cache.GemFireCache;
+import com.gemstone.gemfire.cache.Region;
+import com.gemstone.gemfire.cache.client.ClientCache;
+import com.gemstone.gemfire.cache.client.ClientCacheFactory;
+import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
+
+/**
+ * {@link CacheUtils} is an abstract utility class encapsulating common operations for working with
+ * GemFire {@link Cache} and {@link ClientCache} instances.
+ *
+ * @author John Blum
+ * @see org.springframework.data.gemfire.util.DistributedSystemUtils
+ * @see com.gemstone.gemfire.cache.Cache
+ * @see com.gemstone.gemfire.cache.CacheFactory
+ * @see com.gemstone.gemfire.cache.GemFireCache
+ * @see com.gemstone.gemfire.cache.Region
+ * @see com.gemstone.gemfire.cache.client.ClientCache
+ * @see com.gemstone.gemfire.cache.client.ClientCacheFactory
+ * @see com.gemstone.gemfire.distributed.DistributedSystem
+ * @see com.gemstone.gemfire.internal.cache.GemFireCacheImpl
+ * @since 1.8.0
+ */
+@SuppressWarnings("unused")
+public abstract class CacheUtils extends DistributedSystemUtils {
+
+ public static final String DEFAULT_POOL_NAME = "DEFAULT";
+
+ /* (non-Javadoc) */
+ @SuppressWarnings("all")
+ public static boolean isClient(GemFireCache cache) {
+
+ boolean client = (cache instanceof ClientCache);
+
+ if (cache instanceof GemFireCacheImpl) {
+ client &= ((GemFireCacheImpl) cache).isClient();
+ }
+
+ return client;
+ }
+
+ /* (non-Javadoc) */
+ @SuppressWarnings("all")
+ public static boolean isPeer(GemFireCache cache) {
+
+ boolean peer = (cache instanceof Cache);
+
+ if (cache instanceof GemFireCacheImpl) {
+ peer &= !((GemFireCacheImpl) cache).isClient();
+ }
+
+ return peer;
+ }
+
+ /* (non-Javadoc) */
+ public static boolean closeCache() {
+ try {
+ CacheFactory.getAnyInstance().close();
+ return true;
+ }
+ catch (Exception ignore) {
+ return false;
+ }
+ }
+
+ /* (non-Javadoc) */
+ public static boolean closeClientCache() {
+ try {
+ ClientCacheFactory.getAnyInstance().close();
+ return true;
+ }
+ catch (Exception ignore) {
+ return false;
+ }
+ }
+
+ /* (non-Javadoc) */
+ public static Cache getCache() {
+ try {
+ return CacheFactory.getAnyInstance();
+ }
+ catch (CacheClosedException ignore) {
+ return null;
+ }
+ }
+
+ /* (non-Javadoc) */
+ public static ClientCache getClientCache() {
+ try {
+ return ClientCacheFactory.getAnyInstance();
+ }
+ catch (CacheClosedException ignore) {
+ return null;
+ }
+ catch (IllegalStateException ignore) {
+ return null;
+ }
+ }
+
+ /* (non-Javadoc) */
+ public static GemFireCache resolveGemFireCache() {
+
+ GemFireCache resolvedGemFireCache = getClientCache();
+
+ return (resolvedGemFireCache != null ? resolvedGemFireCache : getCache());
+ }
+
+ /* (non-Javadoc) */
+ public static String toRegionPath(String regionName) {
+ return String.format("%1$s%2$s", Region.SEPARATOR, regionName);
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java
new file mode 100644
index 00000000..055db4ac
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.springframework.data.gemfire.util;
+
+import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.util.StringUtils;
+
+/**
+ * SpringUtils is a utility class encapsulating common functionality on objects and other class types.
+ *
+ * @author John Blum
+ * @since 1.8.0
+ */
+@SuppressWarnings("unused")
+public abstract class SpringUtils {
+
+ /* (non-Javadoc) */
+ public static BeanDefinition addDependsOn(BeanDefinition bean, String... beanNames) {
+
+ List dependsOnList = new ArrayList();
+
+ Collections.addAll(dependsOnList, nullSafeArray(bean.getDependsOn()));
+ dependsOnList.addAll(Arrays.asList(nullSafeArray(beanNames)));
+ bean.setDependsOn(dependsOnList.toArray(new String[dependsOnList.size()]));
+
+ return bean;
+ }
+
+ /* (non-Javadoc) */
+ public static String defaultIfEmpty(String value, String defaultValue) {
+ return (StringUtils.hasText(value) ? value : defaultValue);
+ }
+
+ /* (non-Javadoc) */
+ public static T defaultIfNull(T value, T defaultValue) {
+ return (value != null ? value : defaultValue);
+ }
+
+ /* (non-Javadoc) */
+ public static String dereferenceBean(String beanName) {
+ return String.format("%1$s%2$s", BeanFactory.FACTORY_BEAN_PREFIX, beanName);
+ }
+
+ /* (non-Javadoc) */
+ public static boolean equalsIgnoreNull(Object obj1, Object obj2) {
+ return (obj1 == null ? obj2 == null : obj1.equals(obj2));
+ }
+
+ /* (non-Javadoc) */
+ public static boolean nullOrEquals(Object obj1, Object obj2) {
+ return (obj1 == null || obj1.equals(obj2));
+ }
+
+ /* (non-Javadoc) */
+ public static boolean nullSafeEquals(Object obj1, Object obj2) {
+ return (obj1 != null && obj1.equals(obj2));
+ }
+}
diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.6.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.6.xsd
index 4047f523..13df269c 100644
--- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.6.xsd
+++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.6.xsd
@@ -2324,10 +2324,10 @@ The name of the index bean definition. If property 'name' is not set, it will be
]]>
-
+
@@ -2346,18 +2346,27 @@ Corresponds to the regionPath parameter in createIndex methods.
]]>
-
-
+
-
+
+
@@ -2368,6 +2377,13 @@ The name of the pool used by the index. Used usually in client scenarios.
]]>
+
+
+
+
+
diff --git a/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java
index a9b9e2ce..0be8c08c 100644
--- a/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java
+++ b/src/test/java/org/springframework/data/gemfire/IndexConflictsIntegrationTest.java
@@ -16,22 +16,22 @@
package org.springframework.data.gemfire;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.junit.Assert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+import com.gemstone.gemfire.cache.GemFireCache;
+import com.gemstone.gemfire.cache.query.Index;
+import com.gemstone.gemfire.cache.query.IndexExistsException;
+import com.gemstone.gemfire.cache.query.IndexNameConflictException;
+import com.gemstone.gemfire.cache.query.QueryService;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -39,31 +39,50 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
-import com.gemstone.gemfire.cache.Cache;
-import com.gemstone.gemfire.cache.query.Index;
-import com.gemstone.gemfire.cache.query.IndexExistsException;
-
/**
- * The IndexConflictsIntegrationTest class...
+ * Integration tests with test cases testing the numerous conflicting {@link Index} configurations.
+ *
+ * An {@link IndexExistsException} is thrown when 2 or more {@link Index Indexes} share the same definition
+ * but have different names.
+ *
+ * An {@link IndexNameConflictException} is thrown when 2 or more {@link Index Indexes} share the same name
+ * but have potentially different definitions.
*
* @author John Blum
* @see org.junit.Test
- * @link https://jira.spring.io/browse/SGF-432
+ * @see org.springframework.context.ConfigurableApplicationContext
+ * @see org.springframework.context.annotation.Bean
+ * @see org.springframework.context.annotation.Configuration
+ * @see org.springframework.context.annotation.Import
+ * @see com.gemstone.gemfire.cache.GemFireCache
+ * @see com.gemstone.gemfire.cache.query.Index
+ * @see IndexFactoryBean traps IndexExistsException instead of IndexNameConflictException
+ * @see Improve IndexFactoryBean's resilience and options for handling GemFire IndexExistsExceptions and IndexNameConflictExceptions
* @since 1.6.3
*/
public class IndexConflictsIntegrationTest {
- protected void assertIndex(Index index, String expectedName, String expectedExpression, String expectedFromClause,
+ private static final AtomicBoolean IGNORE = new AtomicBoolean(false);
+ private static final AtomicBoolean OVERRIDE = new AtomicBoolean(false);
+
+ private ConfigurableApplicationContext applicationContext;
+
+ private void assertIndex(Index index, String expectedName, String expectedExpression, String expectedFromClause,
IndexType expectedType) {
- assertThat(index, is(notNullValue()));
- assertThat(index.getName(), is(equalTo(expectedName)));
- assertThat(index.getIndexedExpression(), is(equalTo(expectedExpression)));
- assertThat(index.getFromClause(), is(equalTo(expectedFromClause)));
- assertThat(IndexType.valueOf(index.getType()), is(equalTo(expectedType)));
+ assertThat(index).isNotNull();
+ assertThat(index.getName()).isEqualTo(expectedName);
+ assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression);
+ assertThat(index.getFromClause()).isEqualTo(expectedFromClause);
+ assertThat(IndexType.valueOf(index.getType())).isEqualTo(expectedType);
}
- protected boolean close(ConfigurableApplicationContext applicationContext) {
+ private void assertIndexCount(int count) {
+ assertThat(getIndexCount()).isEqualTo(count);
+ }
+
+ private boolean close(ConfigurableApplicationContext applicationContext) {
+
if (applicationContext != null) {
applicationContext.close();
return !(applicationContext.isActive() || applicationContext.isRunning());
@@ -72,185 +91,260 @@ public class IndexConflictsIntegrationTest {
return true;
}
+ private Index getIndex(String indexName) {
+
+ for (Index index : nullSafeCollection(getQueryService().getIndexes())) {
+ if (index.getName().equalsIgnoreCase(indexName)) {
+ return index;
+ }
+ }
+
+ return null;
+ }
+
+ private int getIndexCount() {
+ return nullSafeCollection(getQueryService().getIndexes()).size();
+ }
+
+ private QueryService getQueryService() {
+ return this.applicationContext.getBean("gemfireCache", GemFireCache.class).getQueryService();
+ }
+
+ private boolean hasIndex(String indexName) {
+ return (getIndex(indexName) != null);
+ }
+
+ private ConfigurableApplicationContext newApplicationContext(Class>... annotatedClasses) {
+ return new AnnotationConfigApplicationContext(annotatedClasses);
+ }
+
@Before
public void setup() {
- System.getProperties().remove("gemfire.cache.region.index.override");
-
- assertThat(System.getProperties().containsKey("gemfire.cache.region.index.override"), is(false));
+ assertThat(IGNORE.get()).isFalse();
+ assertThat(OVERRIDE.get()).isFalse();
}
- @Test(expected = BeanCreationException.class)
- public void indexDefinitionConflictThrowsException() {
- ConfigurableApplicationContext applicationContext = null;
+ @After
+ public void tearDown() {
+ OVERRIDE.set(false);
+ IGNORE.set(false);
+
+ assertThat(close(this.applicationContext)).isTrue();
+ }
+
+ @Test
+ public void indexDefinitionConflictIgnoresIndex() {
+
+ assertThat(IGNORE.compareAndSet(false, true)).isTrue();
+
+ this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
+
+ assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue();
+ assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue();
+ assertIndexCount(1);
+ assertThat(hasIndex("customerIdIndex")).isTrue();
+ assertThat(hasIndex("customerIdentifierIndex")).isFalse();
+
+ Index customersIdIndex = getIndex("customerIdIndex");
+
+ assertIndex(customersIdIndex, "customerIdIndex",
+ "id", "/Customers", IndexType.PRIMARY_KEY);
+ }
+
+ @Test
+ public void indexDefinitionConflictOverridesIndex() {
+
+ assertThat(OVERRIDE.compareAndSet(false, true)).isTrue();
+
+ this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
+
+ assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue();
+ assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue();
+ assertIndexCount(1);
+ assertThat(hasIndex("customerIdIndex")).isFalse();
+ assertThat(hasIndex("customerIdentifierIndex")).isTrue();
+
+ Index customersIdentifierIndex = getIndex("customerIdentifierIndex");
+
+ assertIndex(customersIdentifierIndex, "customerIdentifierIndex",
+ "id", "/Customers", IndexType.PRIMARY_KEY);
+ }
+
+ @Test(expected = IndexExistsException.class)
+ public void indexDefinitionConflictThrowsIndexExistsException() throws Throwable {
try {
- applicationContext = new AnnotationConfigApplicationContext(
- IndexDefinitionConflictGemFireConfiguration.class);
+ this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
}
catch (BeanCreationException expected) {
- assertThat(expected.getMessage(), containsString("Error creating bean with name 'customerIdentityIndex'"
- + " defined in class org.springframework.data.gemfire.IndexConflictsIntegrationTest$IndexDefinitionConflictGemFireConfiguration:"
- + " Invocation of init method failed"));
- assertThat(expected.getCause(), is(instanceOf(GemfireIndexException.class)));
- assertThat(expected.getCause().getMessage(),
- containsString("An Index with a different name having the same definition"
- + " as this Index (customerIdentityIndex) already exists"));
- assertThat(expected.getCause().getCause(), is(instanceOf(IndexExistsException.class)));
- assertThat(expected.getCause().getCause().getMessage(), is(equalTo("Similar Index Exists")));
- throw expected;
- }
- finally {
- assertThat(close(applicationContext), is(true));
+ assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerIdentifierIndex'");
+
+ assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class);
+
+ String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION,
+ "id", "/Customers", IndexType.PRIMARY_KEY);
+
+ assertThat(expected.getCause()).hasMessageStartingWith(String.format(
+ "An Index with a different name [customerIdIndex] having the same definition [%s] already exists",
+ existingIndexDefinition));
+
+ assertThat(expected.getCause()).hasCauseInstanceOf(IndexExistsException.class);
+
+ assertThat(expected.getCause().getCause()).hasMessage("Similar Index Exists");
+
+ assertThat(expected.getCause().getCause()).hasNoCause();
+
+ throw expected.getCause().getCause();
}
}
@Test
- public void indexNameConflictOverridesExistingIndex() {
- ConfigurableApplicationContext applicationContext = null;
+ public void indexNameConflictIgnoresIndex() {
- try {
- applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class);
+ assertThat(IGNORE.compareAndSet(false, true)).isTrue();
- assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2)));
+ this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
- Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class);
+ assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue();
+ assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue();
+ assertIndexCount(1);
+ assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue();
- assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1)));
+ Index customerLastNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME);
- Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class);
-
- assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
- "lastName", "/Customers", IndexType.HASH);
-
- Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class);
-
- assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
- "firstName", "/Customers", IndexType.FUNCTIONAL);
-
- assertThat(customerFirstNameIndex, is(not(sameInstance(customerLastNameIndex))));
- assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(),
- is(sameInstance(customerFirstNameIndex)));
- }
- finally {
- assertThat(close(applicationContext), is(true));
- }
+ assertIndex(customerLastNameIndex, IndexNameConflictConfiguration.INDEX_NAME,
+ "lastName", "/Customers", IndexType.HASH);
}
@Test
- public void indexNameConflictReturnsExistingIndex() {
- ConfigurableApplicationContext applicationContext = null;
+ public void indexNameConflictOverridesIndex() {
+ assertThat(OVERRIDE.compareAndSet(false, true)).isTrue();
+
+ this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
+
+ assertThat(this.applicationContext.getBeansOfType(Index.class)).hasSize(2);
+ assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue();
+ assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue();
+ assertIndexCount(1);
+ assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue();
+
+ Index customerFirstNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME);
+
+ assertIndex(customerFirstNameIndex, IndexNameConflictConfiguration.INDEX_NAME,
+ "firstName", "/Customers", IndexType.FUNCTIONAL);
+ }
+
+ @Test(expected = IndexNameConflictException.class)
+ public void indexNameConflictThrowsIndexNameConflictException() throws Throwable {
try {
- System.setProperty("gemfire.cache.region.index.override", Boolean.FALSE.toString());
-
- assertThat(System.getProperty("gemfire.cache.region.index.override", "true"),
- is(equalTo(Boolean.FALSE.toString())));
-
- applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class);
-
- assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2)));
-
- Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class);
-
- assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1)));
-
- Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class);
-
- assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
- "lastName", "/Customers", IndexType.HASH);
-
- Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class);
-
- assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
- "lastName", "/Customers", IndexType.HASH);
-
- assertThat(customerFirstNameIndex, is(sameInstance(customerLastNameIndex)));
- assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(),
- is(sameInstance(customerLastNameIndex)));
+ this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
}
- finally {
- System.getProperties().remove("gemfire.cache.region.index.override");
+ catch (BeanCreationException expected) {
- if (applicationContext != null) {
- applicationContext.close();
- }
+ assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerFirstNameIndex'");
+
+ assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class);
+
+ assertThat(expected.getCause()).hasMessageStartingWith(String.format(
+ "An Index with the same name [%s] having possibly a different definition already exists;",
+ IndexNameConflictConfiguration.INDEX_NAME));
+
+ assertThat(expected.getCause()).hasCauseInstanceOf(IndexNameConflictException.class);
+
+ assertThat(expected.getCause().getCause()).hasMessage(String.format("Index named ' %s ' already exists.",
+ IndexNameConflictConfiguration.INDEX_NAME));
+
+ assertThat(expected.getCause().getCause()).hasNoCause();
+
+ throw expected.getCause().getCause();
}
}
@Configuration
@SuppressWarnings("unused")
- public static class BaseGemFireConfiguration {
+ public static class GemFireConfiguration {
- @Bean
public Properties gemfireProperties() {
+
Properties gemfireProperties = new Properties();
+
gemfireProperties.setProperty("name", IndexConflictsIntegrationTest.class.getSimpleName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "warning");
+
return gemfireProperties;
}
@Bean
public CacheFactoryBean gemfireCache() {
+
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
+
+ cacheFactoryBean.setClose(true);
cacheFactoryBean.setProperties(gemfireProperties());
- cacheFactoryBean.setUseBeanFactoryLocator(false);
+
return cacheFactoryBean;
}
@Bean(name = "Customers")
- public ReplicatedRegionFactoryBean customersRegion(Cache gemfireCache) {
+ public ReplicatedRegionFactoryBean customersRegion(GemFireCache gemfireCache) {
+
ReplicatedRegionFactoryBean customersRegionFactory = new ReplicatedRegionFactoryBean();
+
customersRegionFactory.setCache(gemfireCache);
- customersRegionFactory.setName("Customers");
+ customersRegionFactory.setClose(false);
customersRegionFactory.setPersistent(false);
+
return customersRegionFactory;
}
}
@Configuration
- @Import(BaseGemFireConfiguration.class)
+ @Import(GemFireConfiguration.class)
@SuppressWarnings("unused")
- public static class IndexDefinitionConflictGemFireConfiguration {
+ public static class IndexDefinitionConflictConfiguration {
@Bean
- public IndexFactoryBean customerIdIndex(Cache gemfireCache) {
+ public IndexFactoryBean customerIdIndex(GemFireCache gemfireCache) {
+
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+
indexFactoryBean.setCache(gemfireCache);
indexFactoryBean.setExpression("id");
indexFactoryBean.setFrom("/Customers");
indexFactoryBean.setType(IndexType.PRIMARY_KEY);
+
return indexFactoryBean;
}
@Bean
@DependsOn("customerIdIndex")
- public IndexFactoryBean customerIdentityIndex(Cache gemfireCache) {
+ public IndexFactoryBean customerIdentifierIndex(GemFireCache gemfireCache) {
+
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+
indexFactoryBean.setCache(gemfireCache);
indexFactoryBean.setExpression("id");
+ indexFactoryBean.setIgnoreIfExists(IGNORE.get());
indexFactoryBean.setFrom("/Customers");
+ indexFactoryBean.setOverride(OVERRIDE.get());
indexFactoryBean.setType(IndexType.PRIMARY_KEY);
+
return indexFactoryBean;
}
}
@Configuration
- @Import(BaseGemFireConfiguration.class)
+ @Import(GemFireConfiguration.class)
@SuppressWarnings("unused")
- public static class IndexNameConflictGemFireConfiguration {
+ public static class IndexNameConflictConfiguration {
protected static final String INDEX_NAME = "CustomerNameIdx";
@Bean
- public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
- return new PropertyPlaceholderConfigurer();
- }
-
- @Bean
- public IndexFactoryBean customerLastNameIndex(Cache gemfireCache,
- @Value("${gemfire.cache.region.index.override:true}") boolean override) {
+ public IndexFactoryBean customerLastNameIndex(GemFireCache gemfireCache) {
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
@@ -258,7 +352,6 @@ public class IndexConflictsIntegrationTest {
indexFactoryBean.setExpression("lastName");
indexFactoryBean.setFrom("/Customers");
indexFactoryBean.setName(INDEX_NAME);
- indexFactoryBean.setOverride(override);
indexFactoryBean.setType(IndexType.HASH);
return indexFactoryBean;
@@ -266,20 +359,19 @@ public class IndexConflictsIntegrationTest {
@Bean
@DependsOn("customerLastNameIndex")
- public IndexFactoryBean customerFirstNameIndex(Cache gemfireCache,
- @Value("${gemfire.cache.region.index.override:true}") boolean override) {
+ public IndexFactoryBean customerFirstNameIndex(GemFireCache gemfireCache) {
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
indexFactoryBean.setCache(gemfireCache);
indexFactoryBean.setExpression("firstName");
indexFactoryBean.setFrom("/Customers");
+ indexFactoryBean.setIgnoreIfExists(IGNORE.get());
indexFactoryBean.setName(INDEX_NAME);
- indexFactoryBean.setOverride(override);
+ indexFactoryBean.setOverride(OVERRIDE.get());
indexFactoryBean.setType(IndexType.FUNCTIONAL);
return indexFactoryBean;
}
}
-
}
diff --git a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java
index 60c941ad..67cbc1ce 100644
--- a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java
+++ b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java
@@ -16,38 +16,22 @@
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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-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.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.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 org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.client.ClientCache;
@@ -56,12 +40,27 @@ import com.gemstone.gemfire.cache.query.IndexExistsException;
import com.gemstone.gemfire.cache.query.IndexNameConflictException;
import com.gemstone.gemfire.cache.query.QueryService;
+import org.apache.commons.logging.Log;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+
/**
- * The IndexFactoryBeanTest class is a test suite of test cases testing the contract and functionality
- * of the IndexFactoryBean class.
+ * Unit tests for {@link IndexFactoryBean}.
*
* @author John Blum
* @see org.junit.Test
+ * @see org.junit.runner.RunWith
+ * @see org.mockito.Mock
+ * @see org.mockito.Mockito
+ * @see org.mockito.Spy
+ * @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.client.ClientCache
@@ -69,156 +68,205 @@ import com.gemstone.gemfire.cache.query.QueryService;
* @see com.gemstone.gemfire.cache.query.QueryService
* @since 1.5.2
*/
+@RunWith(MockitoJUnitRunner.class)
public class IndexFactoryBeanTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
+ @Mock
+ private BeanFactory mockBeanFactory;
- private Cache mockCache = mock(Cache.class, "IndexFactoryBeanTest.MockCache");
+ @Mock
+ private Cache mockCache;
- private QueryService mockQueryService = mock(QueryService.class, "IndexFactoryBeanTest.MockQueryService");
+ @Mock
+ private Index mockIndex;
- protected IndexFactoryBean newIndexFactoryBean() {
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean() {
- @Override QueryService lookupQueryService() {
- return mockQueryService;
+ @Spy
+ private IndexFactoryBean indexFactoryBean;
+
+ @Mock
+ private Log mockLog;
+
+ @Mock
+ private QueryService mockQueryService;
+
+ @Before
+ public void setup() {
+ when(mockCache.getQueryService()).thenReturn(mockQueryService);
+ }
+
+ @After
+ public void tearDown() {
+ indexFactoryBean.setBeanFactory(null);
+ indexFactoryBean.setCache(null);
+ indexFactoryBean.setExpression(null);
+ indexFactoryBean.setFrom(null);
+ indexFactoryBean.setImports(null);
+ indexFactoryBean.setQueryService(null);
+ indexFactoryBean.setType((IndexType) null);
+ }
+
+ private Index mockIndex(String name) {
+
+ Index mockIndex = mock(Index.class, name);
+
+ when(mockIndex.getName()).thenReturn(name);
+
+ return mockIndex;
+ }
+
+ private Index mockIndexWithDefinition(String name, String expression, String fromClause, IndexType type) {
+
+ 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
- public void testAfterPropertiesSet() throws Exception {
- QueryService mockQueryService = mock(QueryService.class, "testAfterPropertiesSet.MockQueryService");
+ @SuppressWarnings("all")
+ public void afterPropertiesSetIsSuccessful() throws Exception {
- Cache mockCache = mock(Cache.class, "testAfterPropertiesSet.MockCache");
+ ConfigurableBeanFactory mockConfigurableBeanFactory = mock(ConfigurableBeanFactory.class);
- when(mockCache.getQueryService()).thenReturn(mockQueryService);
+ when(mockQueryService.createKeyIndex(eq("TestKeyIndex"), eq("id"), eq("/Example")))
+ .thenReturn(mockIndex);
- final Index mockIndex = mock(Index.class, "testAfterPropertiesSet.MockIndex");
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean() {
- @Override Index createIndex(final QueryService queryService, final String indexName) throws Exception {
- return mockIndex;
- }
- };
-
- indexFactoryBean.setCache(mockCache);
- indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setBeanFactory(mockConfigurableBeanFactory);
+ indexFactoryBean.setBeanName("KeyIndexBean");
indexFactoryBean.setExpression("id");
indexFactoryBean.setFrom("/Example");
+ indexFactoryBean.setName("TestKeyIndex");
indexFactoryBean.setType("key");
indexFactoryBean.afterPropertiesSet();
- assertEquals(mockIndex, indexFactoryBean.getObject());
- assertEquals(mockIndex, indexFactoryBean.getObject()); // assert Index really is a 'Singleton'
- assertTrue(Index.class.isAssignableFrom(indexFactoryBean.getObjectType()));
- assertTrue(indexFactoryBean.isSingleton());
+ assertThat(indexFactoryBean.getIndex()).isEqualTo(mockIndex);
+
+ 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 testAfterPropertiesSetWithNullCache() throws Exception {
+ public void afterPropertiesSetWithNoIndexName() throws Exception {
+ try {
+ newIndexFactoryBean().afterPropertiesSet();
+ }
+ catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessage("Index name is required");
+ assertThat(expected).hasNoCause();
+
+ throw expected;
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void afterPropertiesSetWithNullCache() throws Exception {
try {
new IndexFactoryBean().afterPropertiesSet();
}
- catch (IllegalArgumentException expected) {
- assertEquals("The GemFire Cache reference must not be null!", expected.getMessage());
+ catch (IllegalStateException expected) {
+ assertThat(expected).hasMessage("Cache is required");
+ assertThat(expected).hasNoCause();
+
throw expected;
}
}
- @Test(expected = IllegalArgumentException.class)
- public void testAfterPropertiesSetWithNullQueryService() throws Exception {
+ @Test(expected = IllegalStateException.class)
+ public void afterPropertiesSetWithNullQueryService() throws Exception {
+
+ IndexFactoryBean indexFactoryBean = spy(new IndexFactoryBean());
+
+ doReturn(null).when(indexFactoryBean).lookupQueryService();
+
try {
- Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWithNullQueryService.MockCache");
-
- when(mockCache.getQueryService()).thenReturn(null);
-
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
-
indexFactoryBean.setCache(mockCache);
+ indexFactoryBean.setName("TestIndex");
indexFactoryBean.afterPropertiesSet();
}
- catch (IllegalArgumentException expected) {
- assertEquals("QueryService is required to create an Index", expected.getMessage());
+ catch (IllegalStateException expected) {
+ assertThat(expected).hasMessage("QueryService is required to create an Index");
+ assertThat(expected).hasNoCause();
+
throw expected;
}
+ finally {
+ verify(indexFactoryBean, times(1)).lookupQueryService();
+ }
}
@Test(expected = IllegalArgumentException.class)
public void testAfterPropertiesSetWithUnspecifiedExpression() throws Exception {
try {
- QueryService mockQueryService = mock(QueryService.class,
- "testAfterPropertiesSetWithUnspecifiedExpression.MockQueryService");
-
- Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWithUnspecifiedExpression.MockCache");
-
- when(mockCache.getQueryService()).thenReturn(mockQueryService);
-
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
-
- indexFactoryBean.setCache(mockCache);
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+ indexFactoryBean.setName("TestIndex");
indexFactoryBean.afterPropertiesSet();
}
catch (IllegalArgumentException expected) {
- assertEquals("Index 'expression' is required", expected.getMessage());
+ assertThat(expected).hasMessage("Index expression is required");
+ assertThat(expected).hasNoCause();
+
throw expected;
}
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void afterPropertiesSetWithUnspecifiedFromClause() throws Exception {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectCause(is(nullValue(Throwable.class)));
- expectedException.expectMessage("Index 'from clause' is required");
-
- IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
-
- indexFactoryBean.setExpression("id");
- indexFactoryBean.afterPropertiesSet();
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testAfterPropertiesSetWithUnspecifiedIndexName() throws Exception {
try {
- QueryService mockQueryService = mock(QueryService.class,
- "testAfterPropertiesSetWithUnspecifiedIndexName.MockQueryService");
-
- Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWithUnspecifiedIndexName.MockCache");
-
- when(mockCache.getQueryService()).thenReturn(mockQueryService);
-
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
-
- indexFactoryBean.setCache(mockCache);
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+ indexFactoryBean.setName("TestIndex");
indexFactoryBean.setExpression("id");
- indexFactoryBean.setFrom("/Example");
- indexFactoryBean.setImports("org.example.DomainType");
- indexFactoryBean.setType("hash");
indexFactoryBean.afterPropertiesSet();
}
- catch (IllegalArgumentException expected) {
- assertEquals("The Index bean id or name is required!", expected.getMessage());
+ catch (Exception expected) {
+ assertThat(expected).hasMessage("Index from clause is required");
+ assertThat(expected).hasNoCause();
+
throw expected;
}
}
@Test(expected = IllegalArgumentException.class)
- public void testAfterPropertiesSetWithInvalidTypeUsingImports() throws Exception {
+ public void afterPropertiesSetUsingImportsWithInvalidIndexKeyType() throws Exception {
try {
- QueryService mockQueryService = mock(QueryService.class,
- "testAfterPropertiesSetWithInvalidTypeUsingImports.MockQueryService");
-
- Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWithInvalidTypeUsingImports.MockCache");
-
- when(mockCache.getQueryService()).thenReturn(mockQueryService);
-
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
-
- indexFactoryBean.setCache(mockCache);
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+ indexFactoryBean.setName("TestIndex");
indexFactoryBean.setExpression("id");
indexFactoryBean.setFrom("/Example");
indexFactoryBean.setImports("org.example.DomainType");
@@ -226,350 +274,839 @@ public class IndexFactoryBeanTest {
indexFactoryBean.afterPropertiesSet();
}
catch (IllegalArgumentException expected) {
- assertEquals("The 'imports' property is not supported for a Key Index.", expected.getMessage());
+ assertThat(expected).hasMessage("Imports are not supported with a KEY Index");
+ assertThat(expected).hasNoCause();
+
throw expected;
}
}
@Test
- public void testLookupQueryService() {
- QueryService mockQueryServiceOne = mock(QueryService.class, "testLookupQueryService.MockQueryService.One");
- QueryService mockQueryServiceTwo = mock(QueryService.class, "testLookupQueryService.MockQueryService.Two");
+ public void resolveCacheFromCacheProperty() {
+ assertThat(newIndexFactoryBean().resolveCache()).isSameAs(mockCache);
+ }
- Cache mockCache = mock(Cache.class, "testLookupQueryService.MockCache");
+ @Test(expected = IllegalStateException.class)
+ public void resolveCacheThrowsExceptionForUnresolvableCache() {
+ try {
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+ indexFactoryBean.setCache(null);
+ indexFactoryBean.resolveCache();
+ }
+ catch (IllegalStateException expected) {
+ assertThat(expected).hasMessage("Cache is required");
+ assertThat(expected).hasNoCause();
- when(mockCache.getQueryService()).thenReturn(mockQueryServiceTwo);
+ throw expected;
+ }
+ }
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+ @Test
+ public void resolveIndexNameFromBeanNameProperty() {
- indexFactoryBean.setCache(mockCache);
- indexFactoryBean.setQueryService(mockQueryServiceOne);
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
- assertSame(mockQueryServiceOne, indexFactoryBean.lookupQueryService());
+ 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 resolveIndexNameThrowsExceptionForUnresolvableIndexName() {
+ try {
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+ indexFactoryBean.setBeanName(null);
+ indexFactoryBean.setName(null);
+ indexFactoryBean.resolveIndexName();
+ }
+ catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessage("Index name is required");
+ assertThat(expected).hasNoCause();
+
+ throw expected;
+ }
+ }
+
+ @Test
+ public void lookupQueryService() {
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ indexFactoryBean.setQueryService(mockQueryService);
+
+ assertThat(indexFactoryBean.lookupQueryService()).isSameAs(mockQueryService);
verify(mockCache, never()).getQueryService();
}
@Test
- public void testLookupQueryServiceOnClientCache() {
- QueryService mockQueryService = mock(QueryService.class, "testLookupQueryServiceOnClientCache.MockQueryService");
+ public void lookupQueryServiceOnClientCache() {
- ClientCache mockClientCache = mock(ClientCache.class, "testLookupQueryServiceOnClientCache.MockClientCache");
+ ClientCache mockClientCache = mock(ClientCache.class);
when(mockClientCache.getLocalQueryService()).thenReturn(mockQueryService);
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
indexFactoryBean.setCache(mockClientCache);
+ indexFactoryBean.setQueryService(null);
- assertSame(mockQueryService, indexFactoryBean.lookupQueryService());
+ assertThat(indexFactoryBean.lookupQueryService()).isSameAs(mockQueryService);
verify(mockClientCache, times(1)).getLocalQueryService();
}
@Test
- public void testLookupQueryServiceOnPeerCache() {
- QueryService mockQueryService = mock(QueryService.class, "testLookupQueryServiceOnPeerCache.MockQueryService");
+ public void lookupQueryServiceOnPeerCache() {
- Cache mockCache = mock(Cache.class, "testLookupQueryServiceOnPeerCache.MockCache");
-
- when(mockCache.getQueryService()).thenReturn(mockQueryService);
-
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
indexFactoryBean.setCache(mockCache);
indexFactoryBean.setQueryService(null);
- assertSame(mockQueryService, indexFactoryBean.lookupQueryService());
+ assertThat(indexFactoryBean.lookupQueryService()).isSameAs(mockQueryService);
verify(mockCache, times(1)).getQueryService();
}
@Test
- public void testCreateIndexReturnsNewKeyIndex() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateIndexReturnsNewKeyIndex.MockIndex");
+ public void resolveQueryServiceReturnsIndexFactoryBeanConfiguredQueryService() {
+ assertThat(newIndexFactoryBean().resolveQueryService()).isSameAs(mockQueryService);
+ }
- QueryService mockQueryService = mock(QueryService.class, "testCreateIndexReturnsNewKeyIndex.MockQueryService");
+ @Test
+ public void resolveQueryServiceReturnsQueryServiceFromLookup() {
- when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList());
- when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"))).thenReturn(mockIndex);
+ QueryService mockQueryService =
+ mockQueryService("testResolveQueryServiceReturnsQueryServiceFromLookup.MockQueryService");
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+ 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 registerAliasWhenBeanFactoryIsNull() {
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ indexFactoryBean.setBeanFactory(null);
+
+ 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"));
}
@Test
- public void testCreateIndexReturnsNewHashIndex() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateIndexReturnsNewHashIndex.MockIndex");
+ public void createIndexReturnsNewHashIndex() throws Exception {
- QueryService mockQueryService = mock(QueryService.class, "testCreateIndexReturnsNewHashIndex.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"));
}
@Test
- public void testCreateIndexReturnsNewFunctionalIndex() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateIndexReturnsNewFunctionalIndex.MockIndex");
+ public void createIndexReturnsNewFunctionalIndex() throws Exception {
- QueryService mockQueryService = mock(QueryService.class,
- "testCreateIndexReturnsNewFunctionalIndex.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"));
}
- @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, "testCreateIndexReturnsExistingIndex.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));
- }
-
- @Test(expected = RuntimeException.class)
- public void testCreateIndexThrowsException() throws Exception {
- Index mockExistingIndex = mock(Index.class, "testCreateIndexThrowsException.MockIndex.Existing");
-
- QueryService mockQueryService = mock(QueryService.class, "testCreateIndexThrowsException.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(indexFactoryBean, times(2))
+ .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example"));
+
+ verify(mockLog, times(1)).warn(
+ eq(String.format("WARNING! You are attempting to 'override' an existing Index [MockIndex]"
+ + " having the same basic definition [%s] as the Index that will be created by this"
+ + " IndexFactoryBean [TestIndex]; 'Override' effectively 'renames' the existing Index [MockIndex]"
+ + " by removing it then recreating it under the new name [TestIndex] with the same definition;"
+ + " You should be careful to update any existing OQL Query Hints referring to the old"
+ + " Index name [MockIndex] to now use the new name [TestIndex]",
+ indexFactoryBean.toBasicIndexDefinition())));
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+ }
+
+ @Test(expected = GemfireIndexException.class)
+ public void createIndexThrowsIndexNameConflictException() throws Exception {
+
+ Index mockIndex =
+ mockIndexWithDefinition("TestIndex", "purchaseDate", "/Orders", IndexType.HASH);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean)
+ .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class));
+
+ indexFactoryBean.setExpression("id");
+ indexFactoryBean.setFrom("/Example");
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setType(IndexType.FUNCTIONAL);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+ assertThat(indexFactoryBean.isOverride()).isFalse();
+
+ try {
+ indexFactoryBean.createIndex(mockQueryService, "TestIndex");
+ }
+ catch (GemfireIndexException expected) {
+
+ String existingIndexDefinition = String.format(IndexFactoryBean.DETAILED_INDEX_DEFINITION,
+ mockIndex.getName(), mockIndex.getIndexedExpression(), mockIndex.getFromClause(), "unknown",
+ IndexType.valueOf(mockIndex.getType()));
+
+ assertThat(expected).hasMessageStartingWith(String.format(
+ "An Index with the same name [TestIndex] having possibly a different definition already exists;"
+ + " you may choose to ignore this Index definition [%1$s] and use the existing Index"
+ + " definition [%2$s] by setting the 'ignoreIfExists' property to 'true'",
+ indexFactoryBean.toDetailedIndexDefinition(), existingIndexDefinition));
+
+ assertThat(expected).hasCauseInstanceOf(IndexNameConflictException.class);
+ assertThat(expected.getCause()).hasMessage("TEST");
+ assertThat(expected.getCause()).hasNoCause();
+
+ throw expected;
+ }
+ finally {
+ verify(indexFactoryBean, times(1))
+ .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"),
+ eq("id"), eq("/Example"), any(String.class));
+
+ verifyZeroInteractions(mockLog);
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+ }
+
+ @Test(expected = GemfireIndexException.class)
+ public void createIndexThrowsIndexNameConflictExceptionAndCannotFindExistingIndexByName() throws Exception {
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList());
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean)
+ .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class));
+
+ indexFactoryBean.setExpression("id");
+ indexFactoryBean.setFrom("/Example");
+ indexFactoryBean.setIgnoreIfExists(true);
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.FUNCTIONAL);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+
+ try {
+ indexFactoryBean.createIndex(mockQueryService, "TestIndex");
+ }
+ catch (GemfireIndexException expected) {
+
+ assertThat(expected).hasMessageStartingWith(String.format(
+ "An Index with the same name [TestIndex] having possibly a different definition already exists;"
+ + " you may choose to ignore this Index definition [%s] and use the existing Index"
+ + " definition [unknown] by setting the 'ignoreIfExists' property to 'true'",
+ indexFactoryBean.toDetailedIndexDefinition()));
+
+ assertThat(expected).hasCauseInstanceOf(IndexNameConflictException.class);
+ assertThat(expected.getCause()).hasMessage("TEST");
+ assertThat(expected.getCause()).hasNoCause();
+
+ throw expected;
+ }
+ finally {
+ verify(indexFactoryBean, times(1))
+ .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"),
+ eq("id"), eq("/Example"), any(String.class));
+
+ verifyZeroInteractions(mockLog);
+
+ verify(mockQueryService, times(1)).getIndexes();
}
}
@Test
- public void testCreateFunctionalIndex() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateFunctionalIndex.MockIndex");
+ public void createIndexThrowsIndexNameConflictExceptionAndIgnoresThisIndex() throws Exception {
- QueryService mockQueryService = mock(QueryService.class, "testCreateFunctionalIndex.MockQueryService");
+ Index mockIndex =
+ mockIndexWithDefinition("TestIndex", "price", "/Orders", IndexType.FUNCTIONAL);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean)
+ .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class));
+
+ indexFactoryBean.setExpression("price");
+ indexFactoryBean.setFrom("/Orders");
+ indexFactoryBean.setIgnoreIfExists(true);
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.FUNCTIONAL);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+ assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex);
+
+ verify(indexFactoryBean, times(1))
+ .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), eq("price"),
+ eq("/Orders"), any(String.class));
+
+ verifyZeroInteractions(mockLog);
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void createIndexThrowsIndexNameConflictExceptionAndIgnoresThisIndexWithWarning() throws Exception {
+
+ Index mockIndex =
+ mockIndexWithDefinition("TestIndex", "id", "/Orders", IndexType.PRIMARY_KEY);
+
+ when(mockLog.isWarnEnabled()).thenReturn(true);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).when(indexFactoryBean)
+ .createFunctionalIndex(eq(mockQueryService), anyString(), anyString(), anyString(), any(String.class));
+
+ indexFactoryBean.setExpression("price");
+ indexFactoryBean.setFrom("/Orders");
+ indexFactoryBean.setIgnoreIfExists(true);
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.FUNCTIONAL);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+ assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex);
+
+ String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION,
+ "id", "/Orders", IndexType.PRIMARY_KEY);
+
+ verify(indexFactoryBean, times(1))
+ .createFunctionalIndex(eq(mockQueryService), eq("TestIndex"), eq("price"),
+ eq("/Orders"), any(String.class));
+
+ verify(mockLog, times(1)).warn(String.format(
+ "WARNING! Returning existing Index [TestIndex] having a definition [%1$s] that does not match"
+ + " the Index defined [%2$s] by this IndexFactoryBean [TestIndex]",
+ existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition()));
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void createIndexThrowsIndexNameConflictExceptionAndOverridesExistingIndex() throws Exception {
+
+ Index mockIndex =
+ mockIndexWithDefinition("TestIndex", "id", "/Example", IndexType.PRIMARY_KEY);
+
+ Index testIndex =
+ mockIndexWithDefinition("TestIndex", "id", "/Example", IndexType.PRIMARY_KEY);
+
+ assertThat(mockIndex).isNotSameAs(testIndex);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).doReturn(testIndex).when(indexFactoryBean)
+ .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString());
+
+ indexFactoryBean.setExpression("id");
+ indexFactoryBean.setFrom("/Example");
+ indexFactoryBean.setIgnoreIfExists(false);
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.PRIMARY_KEY);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+
+ // NOTE: this test case is also testing the smart "override" handling logic, which just
+ // returns the existing Index if both the name and definition are the same on when an
+ // IndexNameConflictException is thrown.
+ assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(mockIndex);
+
+ verify(indexFactoryBean, times(1))
+ .createKeyIndex(eq(mockQueryService), eq("TestIndex"), eq("id"), eq("/Example"));
+
+ verifyZeroInteractions(mockLog);
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void createIndexThrowsIndexNameConflictExceptionAndOverridesExistingIndexWithWarning() throws Exception {
+
+ Index mockIndex =
+ mockIndexWithDefinition("TestIndex", "price", "/Orders", IndexType.FUNCTIONAL);
+
+ Index testIndex =
+ mockIndexWithDefinition("TestIndex", "purchaseDate", "/Orders", IndexType.HASH);
+
+ assertThat(mockIndex).isNotSameAs(testIndex);
+
+ when(mockLog.isWarnEnabled()).thenReturn(true);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).doReturn(testIndex).when(indexFactoryBean)
+ .createHashIndex(any(QueryService.class), anyString(), anyString(), anyString(), any(String.class));
+
+ indexFactoryBean.setExpression("purchaseDate");
+ indexFactoryBean.setFrom("/Orders");
+ indexFactoryBean.setIgnoreIfExists(false);
+ indexFactoryBean.setName("TestIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.HASH);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+ assertThat(indexFactoryBean.createIndex(mockQueryService, "TestIndex")).isEqualTo(testIndex);
+
+ String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION,
+ mockIndex.getIndexedExpression(), mockIndex.getFromClause(), IndexType.valueOf(mockIndex.getType()));
+
+ verify(indexFactoryBean, times(2)).createHashIndex(eq(mockQueryService),
+ eq("TestIndex"), eq("purchaseDate"), eq("/Orders"), any(String.class));
+
+ verify(mockLog, times(1)).warn(eq(String.format(
+ "WARNING! Overriding existing Index [TestIndex] having a definition [%1$s] that does not match"
+ + " the Index defined [%2$s] by this IndexFactoryBean [TestIndex]",
+ existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition())));
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test(expected = GemfireIndexException.class)
+ public void createIndexThrowsIndexNameConflictExceptionAndOverrideThrowsRuntimeException() throws Exception {
+
+ Index mockIndex =
+ mockIndexWithDefinition("MockIndex", "purchaseDate", "/Example",
+ IndexType.HASH);
+
+ when(mockLog.isWarnEnabled()).thenReturn(true);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex))
+ .thenReturn(Collections.emptyList());
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doThrow(new IndexNameConflictException("TEST")).doThrow(new RuntimeException("RETRY")).when(indexFactoryBean)
+ .createKeyIndex(any(QueryService.class), anyString(), anyString(), anyString());
+
+ indexFactoryBean.setExpression("id");
+ indexFactoryBean.setFrom("/Example");
+ indexFactoryBean.setIgnoreIfExists(false);
+ indexFactoryBean.setName("MockIndex");
+ indexFactoryBean.setOverride(true);
+ indexFactoryBean.setType(IndexType.PRIMARY_KEY);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+
+ try {
+ indexFactoryBean.createIndex(mockQueryService, "MockIndex");
+ }
+ catch (GemfireIndexException expected) {
+
+ assertThat(expected).hasMessageStartingWith(
+ "Attempt to 'override' existing Index [MockIndex] with the Index that would be created by this"
+ + " IndexFactoryBean [MockIndex] failed; you should verify the state of your system"
+ + " and make sure the previously existing Index [MockIndex] still exits");
+
+ assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class);
+ assertThat(expected.getCause()).hasMessageStartingWith(
+ String.format("Failed to create Index [%s]", indexFactoryBean.toDetailedIndexDefinition()));
+ assertThat(expected.getCause()).hasCauseInstanceOf(RuntimeException.class);
+ assertThat(expected.getCause().getCause()).hasMessageStartingWith("RETRY");
+ assertThat(expected.getCause().getCause()).hasNoCause();
+
+ throw expected;
+ }
+ finally {
+
+ String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION,
+ mockIndex.getIndexedExpression(), mockIndex.getFromClause(), IndexType.valueOf(mockIndex.getType()));
+
+ verify(indexFactoryBean, times(2))
+ .createKeyIndex(eq(mockQueryService), eq("MockIndex"), eq("id"), eq("/Example"));
+
+ verify(mockLog, times(1)).warn(eq(String.format(
+ "WARNING! Overriding existing Index [MockIndex] having a definition [%1$s] that does not match"
+ + " the Index defined [%2$s] by this IndexFactoryBean [MockIndex]",
+ existingIndexDefinition, indexFactoryBean.toBasicIndexDefinition())));
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+ }
+
+ @Test
+ public void createFunctionalIndex() throws Exception {
when(mockQueryService.createIndex(eq("FunctionalIndex"), eq("purchaseDate"), eq("/Orders")))
.thenReturn(mockIndex);
@@ -577,77 +1114,323 @@ public class IndexFactoryBeanTest {
Index actualIndex = new 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"));
}
@Test
- public void testCreateFunctionalIndexWithImports() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateFunctionalIndexWithImports.MockIndex");
+ public void createFunctionalIndexWithImports() throws Exception {
- 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 = new IndexFactoryBean().createFunctionalIndex(mockQueryService, "FunctionalIndexWithImports",
- "purchaseDate", "/Orders", "org.example.Order");
+ Index actualIndex = indexFactoryBean.createFunctionalIndex(mockQueryService, "FunctionalIndexWithImports",
+ "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"));
}
@Test
- public void testCreateHashIndex() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateHashIndex.MockIndex");
+ public void createHashIndex() throws Exception {
- 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 = new IndexFactoryBean().createHashIndex(mockQueryService, "HashIndex",
+ 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"));
}
@Test
- public void testCreateHashIndexWithImports() throws Exception {
- Index mockIndex = mock(Index.class, "testCreateHashIndexWithImports.MockIndex");
+ public void createHashIndexWithImports() throws Exception {
- 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 = new IndexFactoryBean().createHashIndex(mockQueryService, "HashIndexWithImports",
- "name", "/People", "org.example.Person");
+ Index actualIndex = indexFactoryBean.createHashIndex(mockQueryService, "HashIndexWithImports",
+ "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"));
}
@Test
- public void testGetExistingIndex() {
- 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 createKeyIndex() throws Exception {
- QueryService mockQueryService = mock(QueryService.class, "testGetExistingIndex.MockQueryService");
+ Index mockIndex = mockIndex("testCreateKeyIndex.MockKeyIndex");
- when(mockIndexOne.getName()).thenReturn("PrimaryIndex");
- when(mockIndexTwo.getName()).thenReturn("SecondaryIndex");
- when(mockIndexThree.getName()).thenReturn("TernaryIndex");
- when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree));
+ when(mockQueryService.createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example")))
+ .thenReturn(mockIndex);
- IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
+ Index actualIndex = indexFactoryBean.createKeyIndex(mockQueryService, "KeyIndex",
+ "id", "/Example");
- 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(actualIndex).isSameAs(mockIndex);
+
+ verify(mockQueryService, times(1))
+ .createKeyIndex(eq("KeyIndex"), eq("id"), eq("/Example"));
}
+ @Test
+ public void tryToFindExistingIndexByDefinitionReturnsIndex() {
+
+ 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(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree));
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.PRIMARY_KEY)).isSameAs(mockIndexTwo);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "purchaseDate", "/Orders", IndexType.FUNCTIONAL)).isSameAs(mockIndexThree);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.HASH)).isSameAs(mockIndexOne);
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByDefinitionReturnsNull() {
+
+ Index mockIndex =
+ mockIndexWithDefinition("PrimaryIndex", "id", "/People", IndexType.HASH);
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.singletonList(mockIndex));
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "key", "/People", IndexType.HASH)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/Persons", IndexType.HASH)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.KEY)).isNull();
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByDefinitionWithQueryServiceHavingNoIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList());
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.KEY)).isNull();
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByDefinitionWithQueryServiceReturningNullIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(null);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByDefinition(mockQueryService,
+ "id", "/People", IndexType.KEY)).isNull();
+
+ verify(mockQueryService, times(1)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameReturnsIndex() {
+
+ Index mockIndexOne = mockIndex("PrimaryIndex");
+ Index mockIndexTwo = mockIndex("SecondaryIndex");
+ Index mockIndexThree = mockIndex("TernaryIndex");
+
+ when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree));
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "PRIMARYINDEX"))
+ .isSameAs(mockIndexOne);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "SecondaryIndex"))
+ .isSameAs(mockIndexTwo);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "ternaryindex"))
+ .isSameAs(mockIndexThree);
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameReturnsNull() {
+
+ Index mockIndexOne = mockIndex("PrimaryIndex");
+ Index mockIndexTwo = mockIndex("SecondaryIndex");
+ Index mockIndexThree = mockIndex("TernaryIndex");
+
+ when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo, mockIndexThree));
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, null)).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, " ")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Primary Index")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "Secondary_Index")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "QuadIndex")).isNull();
+
+ verify(mockQueryService, times(6)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameWithQueryServiceHavingNoIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(Collections.emptyList());
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")).isNull();
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void tryToFindExistingIndexByNameWithQueryServiceReturningNullIndexesReturnsNull() {
+
+ when(mockQueryService.getIndexes()).thenReturn(null);
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "TestIndex")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "KeyIndex")).isNull();
+
+ assertThat(indexFactoryBean.tryToFindExistingIndexByName(mockQueryService, "IdIndex")).isNull();
+
+ verify(mockQueryService, times(3)).getIndexes();
+ }
+
+ @Test
+ public void getObjectReturnsFactoryIndex() throws Exception {
+
+ Index mockIndex = mockIndex("testGetObjectReturnsFactoryIndex.MockIndex");
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doReturn(mockIndex).when(indexFactoryBean).createIndex(eq(mockQueryService),
+ eq("testGetObjectReturnsFactoryIndex.MockIndex"));
+
+ indexFactoryBean.setName("testGetObjectReturnsFactoryIndex.MockIndex");
+ indexFactoryBean.setExpression("id");
+ indexFactoryBean.setFrom("/Mock");
+ indexFactoryBean.afterPropertiesSet();
+
+ assertThat(indexFactoryBean.getIndex()).isEqualTo(mockIndex);
+ assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex);
+ assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex);
+
+ verify(indexFactoryBean, times(1))
+ .createIndex(eq(mockQueryService), eq("testGetObjectReturnsFactoryIndex.MockIndex"));
+ }
+
+ @Test
+ public void getObjectReturnsLookedUpIndex() throws Exception {
+
+ Index mockIndexOne = mockIndex("MockIndexOne");
+ Index mockIndexTwo = mockIndex("MockIndexTwo");
+
+ when(mockQueryService.getIndexes()).thenReturn(Arrays.asList(mockIndexOne, mockIndexTwo));
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ indexFactoryBean.setName("MockIndexTwo");
+
+ assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndexTwo);
+ }
+
+ @Test
+ public void getObjectReturnsNull() {
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ indexFactoryBean.setName("TestIndex");
+
+ assertThat(indexFactoryBean.getObject()).isNull();
+ }
+
+ @Test
+ public void getObjectTypeReturnsIndexClassType() {
+ assertThat(newIndexFactoryBean().getObjectType()).isEqualTo(Index.class);
+ }
+
+ @Test
+ @SuppressWarnings("all")
+ public void getObjectTypeReturnsMockIndexClassType() throws Exception {
+
+ Index mockIndex = mockIndex("MockIndex");
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ doReturn(mockIndex).when(indexFactoryBean).getIndex();
+
+ assertThat(indexFactoryBean.getObjectType()).isEqualTo(mockIndex.getClass());
+ }
+
+ @Test
+ public void isSingletonReturnsTrue() {
+ assertThat(indexFactoryBean.isSingleton()).isTrue();
+ }
+
+ @Test
+ public void setAndIsIgnoreIfExists() {
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+
+ indexFactoryBean.setIgnoreIfExists(true);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
+
+ indexFactoryBean.setIgnoreIfExists(false);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isFalse();
+ }
+
+ @Test
+ public void setAndIsOverride() {
+
+ IndexFactoryBean indexFactoryBean = newIndexFactoryBean();
+
+ assertThat(indexFactoryBean.isOverride()).isFalse();
+
+ indexFactoryBean.setOverride(true);
+
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+
+ indexFactoryBean.setOverride(false);
+
+ assertThat(indexFactoryBean.isOverride()).isFalse();
+ }
}
diff --git a/src/test/java/org/springframework/data/gemfire/config/IndexNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/IndexNamespaceTest.java
index f3d9ecb6..72c1429e 100644
--- a/src/test/java/org/springframework/data/gemfire/config/IndexNamespaceTest.java
+++ b/src/test/java/org/springframework/data/gemfire/config/IndexNamespaceTest.java
@@ -16,22 +16,26 @@
package org.springframework.data.gemfire.config;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import javax.annotation.Resource;
+import com.gemstone.gemfire.cache.Region;
+import com.gemstone.gemfire.cache.query.Index;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.data.gemfire.IndexFactoryBean;
import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import com.gemstone.gemfire.cache.Region;
-import com.gemstone.gemfire.cache.query.Index;
-
/**
- * 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
@@ -50,23 +54,35 @@ public class IndexNamespaceTest {
private static final String TEST_REGION_NAME = "IndexedRegion";
- @Resource(name = "simple")
- private Index simple;
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Resource(name = "basic")
+ private Index basic;
@Resource(name = "complex")
private Index complex;
@Test
- public void testBasicIndex() throws Exception {
- assertEquals("simple", simple.getName());
- assertEquals("status", simple.getIndexedExpression());
- assertEquals(Region.SEPARATOR + TEST_REGION_NAME, simple.getFromClause());
- assertEquals(TEST_REGION_NAME, simple.getRegion().getName());
- assertEquals(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL, simple.getType());
+ public void basicIndexIsCorrect() throws Exception {
+ assertEquals("basic", basic.getName());
+ assertEquals("status", basic.getIndexedExpression());
+ assertEquals(Region.SEPARATOR + TEST_REGION_NAME, basic.getFromClause());
+ assertEquals(TEST_REGION_NAME, basic.getRegion().getName());
+ assertEquals(com.gemstone.gemfire.cache.query.IndexType.FUNCTIONAL, basic.getType());
}
@Test
- public void testComplexIndex() throws Exception {
+ public void basicIndexFactoryBeanIsCorrect() {
+
+ IndexFactoryBean basicIndexFactoryBean = applicationContext.getBean("&basic", IndexFactoryBean.class);
+
+ assertThat(basicIndexFactoryBean.isIgnoreIfExists()).isFalse();
+ assertThat(basicIndexFactoryBean.isOverride()).isFalse();
+ }
+
+ @Test
+ public void complexIndexIsCorrect() throws Exception {
assertEquals("complex-index", complex.getName());
assertEquals("tsi.name", complex.getIndexedExpression());
assertEquals(Region.SEPARATOR + TEST_REGION_NAME + " tsi", complex.getFromClause());
@@ -74,4 +90,13 @@ public class IndexNamespaceTest {
assertEquals(com.gemstone.gemfire.cache.query.IndexType.HASH, complex.getType());
}
+ @Test
+ public void indexWithIgnoreAndOverrideIsCorrect() {
+
+ IndexFactoryBean indexFactoryBean =
+ applicationContext.getBean("&index-with-ignore-and-override", IndexFactoryBean.class);
+
+ assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
+ assertThat(indexFactoryBean.isOverride()).isTrue();
+ }
}
diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java
new file mode 100644
index 00000000..0f086965
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.gemfire.support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.logging.Log;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.beans.factory.BeanFactory;
+
+/**
+ * Unit tests for {@link AbstractFactoryBeanSupport}.
+ *
+ * @author John Blum
+ * @see org.junit.Test
+ * @see org.mockito.Mock
+ * @see org.mockito.Mockito
+ * @see org.mockito.Spy
+ * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport
+ * @since 1.0.0
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AbstractFactoryBeanSupportUnitTests {
+
+ @Mock
+ private Log mockLog;
+
+ @Spy
+ private TestFactoryBeanSupport> factoryBeanSupport;
+
+ @Before
+ public void setup() {
+ when(factoryBeanSupport.getLog()).thenReturn(mockLog);
+ }
+
+ @Test
+ public void setAndGetBeanClassLoader() {
+ assertThat(factoryBeanSupport.getBeanClassLoader()).isNull();
+
+ ClassLoader mockClassLoader = mock(ClassLoader.class);
+
+ factoryBeanSupport.setBeanClassLoader(mockClassLoader);
+
+ assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(mockClassLoader);
+
+ ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+
+ factoryBeanSupport.setBeanClassLoader(systemClassLoader);
+
+ assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(systemClassLoader);
+
+ factoryBeanSupport.setBeanClassLoader(null);
+
+ assertThat(factoryBeanSupport.getBeanClassLoader()).isNull();
+ }
+
+ @Test
+ public void setAndGetBeanFactory() {
+ assertThat(factoryBeanSupport.getBeanFactory()).isNull();
+
+ BeanFactory mockBeanFactory = mock(BeanFactory.class);
+
+ factoryBeanSupport.setBeanFactory(mockBeanFactory);
+
+ assertThat(factoryBeanSupport.getBeanFactory()).isSameAs(mockBeanFactory);
+
+ factoryBeanSupport.setBeanFactory(null);
+
+ assertThat(factoryBeanSupport.getBeanFactory()).isNull();
+ }
+
+ @Test
+ public void setAndGetBeanName() {
+ assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty();
+
+ factoryBeanSupport.setBeanName("test");
+
+ assertThat(factoryBeanSupport.getBeanName()).isEqualTo("test");
+
+ factoryBeanSupport.setBeanName(null);
+
+ assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty();
+ }
+
+ @Test
+ public void isSingletonDefaultsToTrue() {
+ assertThat(factoryBeanSupport.isSingleton()).isTrue();
+ }
+
+ @Test
+ public void logsDebugWhenDebugIsEnabled() {
+ when(mockLog.isDebugEnabled()).thenReturn(true);
+
+ factoryBeanSupport.logDebug("%s log test", "debug");
+
+ verify(mockLog, times(1)).isDebugEnabled();
+ verify(mockLog, times(1)).debug(eq("debug log test"));
+ }
+
+ @Test
+ public void logsInfoWhenInfoIsEnabled() {
+ when(mockLog.isInfoEnabled()).thenReturn(true);
+
+ factoryBeanSupport.logInfo("%s log test", "info");
+
+ verify(mockLog, times(1)).isInfoEnabled();
+ verify(mockLog, times(1)).info(eq("info log test"));
+ }
+
+ @Test
+ public void logsWarningWhenWarnIsEnabled() {
+ when(mockLog.isWarnEnabled()).thenReturn(true);
+
+ factoryBeanSupport.logWarning("%s log test", "warn");
+
+ verify(mockLog, times(1)).isWarnEnabled();
+ verify(mockLog, times(1)).warn(eq("warn log test"));
+ }
+
+ @Test
+ public void logsWarningWhenErrorIsEnabled() {
+ when(mockLog.isErrorEnabled()).thenReturn(true);
+
+ factoryBeanSupport.logError("%s log test", "error");
+
+ verify(mockLog, times(1)).isErrorEnabled();
+ verify(mockLog, times(1)).error(eq("error log test"));
+ }
+
+ @Test
+ public void suppressesDebugLoggingWhenDebugIsDisabled() {
+ when(mockLog.isDebugEnabled()).thenReturn(false);
+
+ factoryBeanSupport.logDebug("test");
+
+ verify(mockLog, times(1)).isDebugEnabled();
+ verify(mockLog, never()).debug(any());
+ }
+
+ @Test
+ public void suppressesInfoLoggingWhenInfoIsDisabled() {
+ when(mockLog.isInfoEnabled()).thenReturn(false);
+
+ factoryBeanSupport.logInfo("test");
+
+ verify(mockLog, times(1)).isInfoEnabled();
+ verify(mockLog, never()).info(any());
+ }
+
+ @Test
+ public void suppressesWarnLoggingWhenWarnIsDisabled() {
+ when(mockLog.isWarnEnabled()).thenReturn(false);
+
+ factoryBeanSupport.logWarning("test");
+
+ verify(mockLog, times(1)).isWarnEnabled();
+ verify(mockLog, never()).warn(any());
+ }
+
+ @Test
+ public void suppressesErrorLoggingWhenInfoIsDisabled() {
+ when(mockLog.isErrorEnabled()).thenReturn(false);
+
+ factoryBeanSupport.logError("test");
+
+ verify(mockLog, times(1)).isErrorEnabled();
+ verify(mockLog, never()).error(any());
+ }
+
+ private static class TestFactoryBeanSupport extends AbstractFactoryBeanSupport {
+
+ @Override
+ public T getObject() throws Exception {
+ return null;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return Object.class;
+ }
+ }
+}
diff --git a/src/test/resources/org/springframework/data/gemfire/config/index-ns.xml b/src/test/resources/org/springframework/data/gemfire/config/index-ns.xml
index 0835a84c..9181829c 100644
--- a/src/test/resources/org/springframework/data/gemfire/config/index-ns.xml
+++ b/src/test/resources/org/springframework/data/gemfire/config/index-ns.xml
@@ -30,9 +30,12 @@
-
+
+
+