SGF-637 - Improve IndexFactoryBean's resilience and options for handling GemFire IndexExistsExceptions and IndexNameConflictExceptions.

(cherry picked from commit d3b8e58358)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2017-06-22 16:21:11 -07:00
parent 693179629e
commit 69bf7ac446
16 changed files with 2882 additions and 648 deletions

View File

@@ -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]
----
<gfe:index id="myIndex" expression="someField" from="/SomeRegion" type="HASH"/>
----
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 `&lt;gfe:cache&gt;`. 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, Long> {
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]
----
<gfe:index id="myIndex" name="CustomersLastNameIndex" expression="lastName" from="/Customers" type="HASH"/>
----
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]
----
<gfe:index id="myDefinedIndex" expression="someField" from="/SomeRegion" define="true"/>
----
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 `&lt;gfe:index&gt;` 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 `&lt;gfe:index ignore-if-exists="true"&gt;`),
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 `&lt;gfe:index ignore-if-exists="true"&gt;`),
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 `&lt;gfe:index override="true"&gt;`), 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 `&lt;gfe:index override="true"&gt;`),
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_*!

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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<Index>, BeanNameAware {
public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> 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<Index>, 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<Index>, 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<Index> 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<Index>, 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<Index>, 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}

View File

@@ -23,12 +23,13 @@ import org.springframework.data.gemfire.IndexFactoryBean;
import org.w3c.dom.Element;
/**
* Namespace parser for &lt;index;gt; bean definitions.
*
* Namespace parser for &lt;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);
}
}

View File

@@ -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<T> implements FactoryBean<T>,
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<String> 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<String> 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<String> 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<String> message) {
Log log = getLog();
if (log.isErrorEnabled()) {
log.error(message.get());
}
}
protected interface Supplier<T> {
T get();
}
protected static class StringFormatSupplier implements Supplier<String> {
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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> dependsOnList = new ArrayList<String>();
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> 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));
}
}

View File

@@ -2324,10 +2324,10 @@ The name of the index bean definition. If property 'name' is not set, it will be
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:attribute name="cache-ref" type="xsd:string" use="optional" default="gemfireCache">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of index: FUNCTIONAL, HASH, PRIMARY_KEY.
The name of the bean defining the GemFire cache (by default 'gemfireCache').
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -2346,18 +2346,27 @@ Corresponds to the regionPath parameter in createIndex methods.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="imports" type="xsd:string" use="optional" />
<xsd:attribute name="override" type="xsd:string" use="optional" default="true">
<xsd:attribute name="ignore-if-exists" type="xsd:string" use="optional" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Indicates whether the index is created even if there is an index with the same name (default) or not.
Indicates whether this Index is ignored when there exists an Index with the same name, but possibly
different definition.
You should use this setting with care. See Javadoc for more details.
Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cache-ref" type="xsd:string" use="optional" default="gemfireCache">
<xsd:attribute name="imports" type="xsd:string" use="optional" />
<xsd:attribute name="override" type="xsd:string" use="optional" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the bean defining the GemFire cache (by default 'gemfireCache').
Indicates whether the Index is created even if there exists an Index with the same definition, different name.
You should use this setting with care. See Javadoc for more details.
Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -2368,6 +2377,13 @@ The name of the pool used by the index. Used usually in client scenarios.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of index: FUNCTIONAL, HASH, PRIMARY_KEY.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<!-- -->

View File

@@ -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 <a href="https://jira.spring.io/browse/SGF-432">IndexFactoryBean traps IndexExistsException instead of IndexNameConflictException</a>
* @see <a href="https://jira.spring.io/browse/SGF-637">Improve IndexFactoryBean's resilience and options for handling GemFire IndexExistsExceptions and IndexNameConflictExceptions</a>
* @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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<T> extends AbstractFactoryBeanSupport<T> {
@Override
public T getObject() throws Exception {
return null;
}
@Override
public Class<?> getObjectType() {
return Object.class;
}
}
}

View File

@@ -30,9 +30,12 @@
<gfe:replicated-region id="IndexedRegion" persistent="false"/>
<gfe:index id="simple" expression="status" from="/IndexedRegion" type="functional"/>
<gfe:index id="basic" expression="status" from="/IndexedRegion" type="functional"/>
<gfe:index id="complex" name="complex-index" expression="tsi.name" from="/IndexedRegion tsi"
imports="import java.util" type="${index.complex.type}"/>
<gfe:index id="index-with-ignore-and-override" expression="id" from="/IndexedRegion"
ignore-if-exists="true" override="true" type="PRIMARY_KEY"/>
</beans>