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:
238
src/main/asciidoc/reference/indexing.adoc
Normal file
238
src/main/asciidoc/reference/indexing.adoc
Normal 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 `<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, 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 `<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_*!
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
<!-- -->
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user