SGF-637 - Improve IndexFactoryBean's resilience and options for handling GemFire IndexExistsExceptions and IndexNameConflictExceptions.
Related JIRA: https://jira.spring.io/browse/DATAGEODE-14
This commit is contained in:
@@ -1,25 +1,238 @@
|
||||
[[bootstrap:indexing]]
|
||||
= Configuring an Index
|
||||
|
||||
Pivotal GemFire allows Indexes (or Indices) to be created to improve the performance of OQL queries.
|
||||
Pivotal GemFire allows Indexes (or Indices) to be created on Region data to improve the performance of OQL queries.
|
||||
|
||||
In _Spring Data GemFire_, Indexes are declared with the `index` element:
|
||||
In _Spring Data GemFire_ (SDG), Indexes are declared with the `index` element:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<gfe:index id="myIndex" expression="someField" from="/SomeRegion"/>
|
||||
<gfe:index id="myIndex" expression="someField" from="/SomeRegion" type="HASH"/>
|
||||
----
|
||||
|
||||
Before creating the `Index`, _Spring Data GemFire_ will verify whether an `Index` with the same name already exists.
|
||||
By default, SDG overrides an existing `Index` if an `Index` with the same name already exists. The existing Index
|
||||
is overridden by removing the old `Index` first followed by creating a new `Index` with the same name defined by
|
||||
the new bean definition, regardless if the old `Index` definition was the same or not.
|
||||
In _Spring Data GemFire's_ XML schema (a.k.a. SDG namespace), `Index` bean declarations are not bound to a _Region_,
|
||||
unlike GemFire's native `cache.xml`. Rather, they are top-level elements just like `<gfe:cache>`. This allows
|
||||
a developer to declare any number of Indexes on any _Region_ whether they were just created or already exist,
|
||||
a significant improvement over GemFire's native `cache.xml` format.
|
||||
|
||||
To prevent the named `Index` definition change, especially when the old and new `Index` definitions differ
|
||||
in a significant way, then set the `override` attribute to *false*, which effectively returns the existing
|
||||
`Index` definition given the same name.
|
||||
An `Index` must have a name. A developer may give the `Index` an explicit name using the `name` attribute,
|
||||
otherwise the _bean name_ (i.e. value of the `id` attribute) of the `Index` bean definition is used as
|
||||
the `Index` name.
|
||||
|
||||
`Index` declarations are not bound to a Region but rather are top-level elements (just like `cache`).
|
||||
This allows one to declare any number of Indices on any Region whether they are just created or already exist
|
||||
- an improvement over GemFire's native `cache.xml`. By default, the `Index` relies on the default cache declaration
|
||||
but one can customize it accordingly or use a Pool (if need be) - see the XML schema for a full set of options.
|
||||
The `expression` and `from` clause form the main components of an `Index`, identifying the data to index
|
||||
(i.e. the _Region_ identified in the `from` clause) along with what criteria (i.e. `expression`) is used
|
||||
to index the data. The `expression` should be based on what application domain object fields are used
|
||||
in the predicate of application-defined OQL queries used to query and lookup the objects stored
|
||||
in the _Region_.
|
||||
|
||||
For example, if I have a `Customer` that has a `lastName` property...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Region("Customers")
|
||||
class Customer {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
String lastName;
|
||||
String firstName;
|
||||
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
And, I also have an application defined SD[G] _Repository_ to query for `Customers`...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface CustomerRepository extends GemfireRepository<Customer, 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_*!
|
||||
|
||||
@@ -37,6 +37,14 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
@SuppressWarnings({ "serial", "unused" })
|
||||
public class GemfireIndexException extends DataIntegrityViolationException {
|
||||
|
||||
public GemfireIndexException(Exception cause) {
|
||||
this(cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
public GemfireIndexException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public GemfireIndexException(IndexCreationException cause) {
|
||||
this(cause.getMessage(), cause);
|
||||
}
|
||||
@@ -76,5 +84,4 @@ public class GemfireIndexException extends DataIntegrityViolationException {
|
||||
public GemfireIndexException(String message, IndexNameConflictException cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,19 +20,20 @@ import static java.util.stream.StreamSupport.stream;
|
||||
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.RegionService;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
import org.apache.geode.cache.query.Index;
|
||||
import org.apache.geode.cache.query.IndexExistsException;
|
||||
import org.apache.geode.cache.query.IndexInvalidException;
|
||||
import org.apache.geode.cache.query.IndexNameConflictException;
|
||||
import org.apache.geode.cache.query.IndexStatistics;
|
||||
import org.apache.geode.cache.query.QueryService;
|
||||
@@ -42,7 +43,6 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.data.gemfire.config.annotation.IndexConfigurer;
|
||||
import org.springframework.data.gemfire.config.xml.GemfireConstants;
|
||||
import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -60,6 +60,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see org.apache.geode.cache.query.QueryService
|
||||
* @see org.springframework.beans.factory.FactoryBean
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory
|
||||
* @see org.springframework.data.gemfire.IndexFactoryBean.IndexWrapper
|
||||
* @see org.springframework.data.gemfire.config.annotation.IndexConfigurer
|
||||
* @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport
|
||||
@@ -67,8 +68,14 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implements InitializingBean {
|
||||
|
||||
public static final String BASIC_INDEX_DEFINITION = "{ expression = '%1$s', from = '%2$s', type = %3$s }";
|
||||
|
||||
public static final String DETAILED_INDEX_DEFINITION =
|
||||
"{ name = '%1$s', expression = '%2$s', from = '%3$s', imports = '%4$s', type = %5$s }";
|
||||
|
||||
private boolean define = false;
|
||||
private boolean override = true;
|
||||
private boolean ignoreIfExists = false;
|
||||
private boolean override = false;
|
||||
|
||||
private Index index;
|
||||
|
||||
@@ -77,7 +84,7 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
//@Autowired(required = false)
|
||||
private List<IndexConfigurer> indexConfigurers = Collections.emptyList();
|
||||
|
||||
private IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() {
|
||||
private final IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() {
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, IndexFactoryBean bean) {
|
||||
@@ -101,25 +108,18 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
this.indexName = Optional.ofNullable(this.name).filter(StringUtils::hasText).orElse(getBeanName());
|
||||
|
||||
Assert.hasText(this.indexName, "Index name is required");
|
||||
this.indexName = resolveIndexName();
|
||||
|
||||
applyIndexConfigurers(this.indexName);
|
||||
|
||||
Assert.notNull(cache, "Cache is required");
|
||||
this.cache = resolveCache();
|
||||
this.queryService = resolveQueryService();
|
||||
|
||||
this.queryService = lookupQueryService();
|
||||
assertIndexDefinitionConfiguration();
|
||||
|
||||
Assert.notNull(queryService, "QueryService is required to create an Index");
|
||||
Assert.hasText(expression, "Index expression is required");
|
||||
Assert.hasText(from, "Index from clause is required");
|
||||
this.index = createIndex(this.queryService, this.indexName);
|
||||
|
||||
if (IndexType.isKey(indexType)) {
|
||||
Assert.isNull(imports, "imports are not supported with a KEY Index");
|
||||
}
|
||||
|
||||
this.index = createIndex(queryService, indexName);
|
||||
registerAlias(getBeanName(), this.indexName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -155,47 +155,93 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
.forEach(indexConfigurer -> indexConfigurer.configure(indexName, this));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private void assertIndexDefinitionConfiguration() {
|
||||
|
||||
Assert.hasText(this.expression, "Index expression is required");
|
||||
Assert.hasText(this.from, "Index from clause is required");
|
||||
|
||||
if (IndexType.isKey(this.indexType)) {
|
||||
Assert.isTrue(StringUtils.isEmpty(this.imports), "Imports are not supported with a KEY Index");
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
RegionService resolveCache() {
|
||||
|
||||
return Optional.ofNullable(this.cache)
|
||||
.orElseGet(() -> Optional.ofNullable(GemfireUtils.resolveGemFireCache())
|
||||
.orElseThrow(() -> newIllegalStateException("Cache is required")));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
String resolveIndexName() {
|
||||
|
||||
return Optional.ofNullable(this.name).filter(StringUtils::hasText)
|
||||
.orElseGet(() -> Optional.ofNullable(getBeanName()).filter(StringUtils::hasText)
|
||||
.orElseThrow(() -> newIllegalArgumentException("Index name is required")));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
QueryService resolveQueryService() {
|
||||
|
||||
return Optional.ofNullable(this.queryService)
|
||||
.orElseGet(() -> Optional.ofNullable(lookupQueryService())
|
||||
.orElseThrow(() -> newIllegalStateException("QueryService is required to create an Index")));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
QueryService lookupQueryService() {
|
||||
|
||||
String queryServiceBeanName = GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE;
|
||||
|
||||
return Optional.ofNullable(getBeanFactory())
|
||||
.filter(beanFactory -> beanFactory.containsBean(queryServiceBeanName))
|
||||
.map(beanFactory -> beanFactory.getBean(queryServiceBeanName, QueryService.class))
|
||||
.orElseGet(() -> registerQueryServiceBean(queryServiceBeanName, doLookupQueryService()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
QueryService doLookupQueryService() {
|
||||
|
||||
return Optional.ofNullable(this.queryService).orElseGet(() ->
|
||||
(this.cache instanceof ClientCache ? ((ClientCache) this.cache).getLocalQueryService()
|
||||
: this.cache.getQueryService()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
QueryService lookupQueryService() {
|
||||
if (getBeanFactory().containsBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE)) {
|
||||
return getBeanFactory().getBean(GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE,
|
||||
QueryService.class);
|
||||
}
|
||||
else {
|
||||
return registerQueryServiceBean(doLookupQueryService());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc) */
|
||||
QueryService registerQueryServiceBean(QueryService queryService) {
|
||||
QueryService registerQueryServiceBean(String beanName, QueryService queryService) {
|
||||
|
||||
if (isDefine()) {
|
||||
((ConfigurableBeanFactory) getBeanFactory()).registerSingleton(
|
||||
GemfireConstants.DEFAULT_GEMFIRE_INDEX_DEFINITION_QUERY_SERVICE, queryService);
|
||||
((ConfigurableBeanFactory) getBeanFactory()).registerSingleton(beanName, queryService);
|
||||
}
|
||||
|
||||
return queryService;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
void registerAlias(String beanName, String indexName) {
|
||||
|
||||
Optional.ofNullable(getBeanFactory()).filter(it -> it instanceof ConfigurableBeanFactory)
|
||||
.filter(it -> (beanName != null && !beanName.equals(indexName)))
|
||||
.ifPresent(it -> ((ConfigurableBeanFactory) it).registerAlias(beanName, indexName));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
Index createIndex(QueryService queryService, String indexName) throws Exception {
|
||||
return createIndex(queryService, indexName, false);
|
||||
}
|
||||
|
||||
Index existingIndex = getExistingIndex(queryService, indexName);
|
||||
/* (non-Javadoc) */
|
||||
private Index createIndex(QueryService queryService, String indexName, boolean retryAttempted) throws Exception {
|
||||
|
||||
if (existingIndex != null) {
|
||||
if (override) {
|
||||
queryService.removeIndex(existingIndex);
|
||||
}
|
||||
else {
|
||||
return existingIndex;
|
||||
}
|
||||
}
|
||||
IndexType indexType = this.indexType;
|
||||
|
||||
String expression = this.expression;
|
||||
String from = this.from;
|
||||
String imports = this.imports;
|
||||
|
||||
try {
|
||||
if (IndexType.isKey(indexType)) {
|
||||
@@ -208,32 +254,177 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
return createFunctionalIndex(queryService, indexName, expression, from, imports);
|
||||
}
|
||||
}
|
||||
catch (IndexExistsException e) {
|
||||
throw new GemfireIndexException(String.format(
|
||||
"An Index with a different name having the same definition as this Index (%1$s) already exists",
|
||||
indexName), e);
|
||||
}
|
||||
catch (IndexNameConflictException e) {
|
||||
// NOTE technically, the only way for an IndexNameConflictException to be thrown is if
|
||||
// queryService.remove(existingIndex) above silently fails, since otherwise, when override is 'false',
|
||||
// the existingIndex is already being returned. Given this state of affairs, an Index with the provided
|
||||
// name is unresolvable based on what the user intended to happen, so just rethrow an Exception.
|
||||
throw new GemfireIndexException(String.format(
|
||||
"Failed to remove the existing Index%1$sbefore re-creating Index with name (%2$s)",
|
||||
(override ? " on override " : " "), indexName), e);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (existingIndex != null) {
|
||||
Collection<Index> indexes = queryService.getIndexes();
|
||||
catch (IndexExistsException cause) {
|
||||
|
||||
if (CollectionUtils.isEmpty(indexes) || !indexes.contains(existingIndex)) {
|
||||
queryService.getIndexes().add(existingIndex);
|
||||
return existingIndex;
|
||||
}
|
||||
}
|
||||
// Same definition, different name
|
||||
|
||||
throw e;
|
||||
Optional<Index> existingIndexByDefinition =
|
||||
tryToFindExistingIndexByDefinition(queryService, expression, from, indexType);
|
||||
|
||||
return existingIndexByDefinition.filter(existingIndex -> isIgnoreIfExists())
|
||||
.map(existingIndex -> {
|
||||
|
||||
logWarning("WARNING! You are choosing to ignore this Index [%1$s] and return the existing"
|
||||
+ " Index having the same basic definition [%2$s] but with a different name [%3$s];"
|
||||
+ " Make sure no OQL Query Hints refer to this Index by name [%1$s]",
|
||||
indexName, toBasicIndexDefinition(), existingIndex.getName());
|
||||
|
||||
return handleIgnore(existingIndex);
|
||||
|
||||
}).orElseGet(() ->
|
||||
|
||||
existingIndexByDefinition.filter(it -> !retryAttempted && isOverride())
|
||||
.map(existingIndex -> {
|
||||
|
||||
// Log an informational warning to caution the user about using the override
|
||||
logWarning("WARNING! You are attempting to 'override' an existing Index [%1$s]"
|
||||
+ " having the same basic definition [%2$s] as the Index that will be created"
|
||||
+ " by this IndexFactoryBean [%3$s]; 'Override' effectively 'renames' the existing"
|
||||
+ " Index [%1$s] by removing it then recreating it under the new name [%3$s] with"
|
||||
+ " the same definition; You should be careful to update any existing OQL Query"
|
||||
+ " Hints referring to the old Index name [%1$s] to now use the new name [%3$s]",
|
||||
existingIndex.getName(), toBasicIndexDefinition(), indexName);
|
||||
|
||||
return handleOverride(existingIndex, queryService, indexName);
|
||||
|
||||
}).orElseThrow(() -> {
|
||||
|
||||
String existingIndexName = existingIndexByDefinition.map(Index::getName)
|
||||
.orElse("unknown");
|
||||
|
||||
return new GemfireIndexException(String.format(
|
||||
"An Index with a different name [%1$s] having the same definition [%2$s] already exists;"
|
||||
+ " You may attempt to override the existing Index [%1$s] with the new name [%3$s]"
|
||||
+ " by setting the 'override' property to 'true'",
|
||||
existingIndexName, toBasicIndexDefinition(), indexName), cause);
|
||||
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (IndexNameConflictException cause) {
|
||||
|
||||
// Same name; possibly different definition
|
||||
|
||||
Optional<Index> existingIndexByName = tryToFindExistingIndexByName(queryService, indexName);
|
||||
|
||||
return existingIndexByName.filter(existingIndex -> isIgnoreIfExists())
|
||||
.map(existingIndex ->
|
||||
|
||||
handleIgnore(warnOnIndexDefinitionMismatch(existingIndex, indexName, "Returning"))
|
||||
|
||||
).orElseGet(() ->
|
||||
|
||||
existingIndexByName.filter(it -> !retryAttempted && isOverride())
|
||||
.map(existingIndex ->
|
||||
|
||||
handleSmartOverride(warnOnIndexDefinitionMismatch(existingIndex, indexName,
|
||||
"Overriding"), queryService, indexName)
|
||||
|
||||
).orElseThrow(() -> {
|
||||
|
||||
String existingIndexDefinition = existingIndexByName
|
||||
.map(it -> String.format(DETAILED_INDEX_DEFINITION, it.getName(),
|
||||
it.getIndexedExpression(), it.getFromClause(), "unknown", it.getType()))
|
||||
.orElse("unknown");
|
||||
|
||||
return new GemfireIndexException(String.format(
|
||||
"An Index with the same name [%1$s] having possibly a different definition already exists;"
|
||||
+ " you may choose to ignore this Index definition [%2$s] and use the existing Index"
|
||||
+ " definition [%3$s] by setting the 'ignoreIfExists' property to 'true'",
|
||||
indexName, toDetailedIndexDefinition(), existingIndexDefinition), cause);
|
||||
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (Exception cause) {
|
||||
throw new GemfireIndexException(String.format("Failed to create Index [%s]",
|
||||
toDetailedIndexDefinition()), cause);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("all")
|
||||
private boolean isIndexDefinitionMatch(Index index) {
|
||||
|
||||
return Optional.ofNullable(index)
|
||||
.map(it -> {
|
||||
|
||||
IndexType thisIndexType = Optional.ofNullable(this.indexType).orElse(IndexType.FUNCTIONAL);
|
||||
|
||||
boolean result = ObjectUtils.nullSafeEquals(it.getIndexedExpression(), this.expression)
|
||||
&& ObjectUtils.nullSafeEquals(it.getFromClause(), this.from)
|
||||
&& ObjectUtils.nullSafeEquals(IndexType.valueOf(it.getType()), thisIndexType);
|
||||
|
||||
return result;
|
||||
})
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private boolean isNotIndexDefinitionMatch(Index index) {
|
||||
return !isIndexDefinitionMatch(index);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private Index warnOnIndexDefinitionMismatch(Index existingIndex, String indexName, String action) {
|
||||
|
||||
if (isNotIndexDefinitionMatch(existingIndex)) {
|
||||
|
||||
String existingIndexDefinition = String.format(BASIC_INDEX_DEFINITION, existingIndex.getIndexedExpression(),
|
||||
existingIndex.getFromClause(), IndexType.valueOf(existingIndex.getType()));
|
||||
|
||||
logWarning("WARNING! %1$s existing Index [%2$s] having a definition [%3$s]"
|
||||
+ " that does not match the Index defined [%4$s] by this IndexFactoryBean [%5$s]",
|
||||
action, existingIndex.getName(), existingIndexDefinition, toBasicIndexDefinition(), indexName);
|
||||
}
|
||||
|
||||
return existingIndex;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private Index handleIgnore(Index existingIndex) {
|
||||
|
||||
registerAlias(getBeanName(), existingIndex.getName());
|
||||
|
||||
return existingIndex;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private Index handleOverride(Index existingIndex, QueryService queryService, String indexName) {
|
||||
try {
|
||||
// No way to tell whether the QueryService.remove(:Index) was successful or not! o.O
|
||||
// Should return a boolean! Does it throw an RuntimeException? Javadoc is useless; #sigh
|
||||
queryService.removeIndex(existingIndex);
|
||||
|
||||
return createIndex(queryService, indexName, true);
|
||||
}
|
||||
catch (Exception cause) {
|
||||
throw new GemfireIndexException(String.format(
|
||||
"Attempt to 'override' existing Index [%1$s] with the Index that would be created"
|
||||
+ " by this IndexFactoryBean [%2$s] failed; you should verify the state of"
|
||||
+ " your system and make sure the previously existing Index [%1$s] still exits",
|
||||
existingIndex.getName(), indexName), cause);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private Index handleSmartOverride(Index existingIndex, QueryService queryService, String indexName) {
|
||||
|
||||
return Optional.of(existingIndex)
|
||||
.filter(it -> it.getName().equalsIgnoreCase(indexName))
|
||||
.filter(it -> isIndexDefinitionMatch(existingIndex))
|
||||
.orElseGet(() -> handleOverride(existingIndex, queryService, indexName));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
String toBasicIndexDefinition() {
|
||||
return String.format(BASIC_INDEX_DEFINITION, this.expression, this.from, this.indexType);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
String toDetailedIndexDefinition() {
|
||||
return String.format(DETAILED_INDEX_DEFINITION,
|
||||
this.name, this.expression, this.from, this.imports, this.indexType);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -249,8 +440,8 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
Index createHashIndex(QueryService queryService, String indexName, String expression, String from,
|
||||
String imports) throws Exception {
|
||||
Index createHashIndex(QueryService queryService, String indexName, String expression, String from, String imports)
|
||||
throws Exception {
|
||||
|
||||
boolean hasImports = StringUtils.hasText(imports);
|
||||
|
||||
@@ -301,15 +492,31 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
Index getExistingIndex(QueryService queryService, String indexName) {
|
||||
Optional<Index> tryToFindExistingIndexByDefinition(QueryService queryService,
|
||||
String expression, String fromClause, IndexType indexType) {
|
||||
|
||||
for (Index index : nullSafeCollection(queryService.getIndexes())) {
|
||||
if (index.getName().equalsIgnoreCase(indexName)) {
|
||||
return index;
|
||||
if (index.getIndexedExpression().equalsIgnoreCase(expression)
|
||||
&& index.getFromClause().equalsIgnoreCase(fromClause)
|
||||
&& indexType.equals(IndexType.valueOf(index.getType()))) {
|
||||
|
||||
return Optional.of(index);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
Optional<Index> tryToFindExistingIndexByName(QueryService queryService, String indexName) {
|
||||
|
||||
for (Index index : nullSafeCollection(queryService.getIndexes())) {
|
||||
if (index.getName().equalsIgnoreCase(indexName)) {
|
||||
return Optional.of(index);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,13 +530,23 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
return this.compositeIndexConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link Index} created by this {@link IndexFactoryBean}.
|
||||
*
|
||||
* @return a reference to the {@link Index} created by this {@link IndexFactoryBean}.
|
||||
* @see org.apache.geode.cache.query.Index
|
||||
*/
|
||||
public Index getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Index getObject() {
|
||||
return Optional.ofNullable(this.index)
|
||||
.orElseGet(() -> this.index = getExistingIndex(queryService, indexName));
|
||||
return Optional.ofNullable(getIndex()).orElseGet(() ->
|
||||
this.index = tryToFindExistingIndexByName(resolveQueryService(), resolveIndexName()).orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,74 +555,151 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<?> getObjectType() {
|
||||
return Optional.ofNullable(this.index).map(Index::getClass).orElse((Class) Index.class);
|
||||
return Optional.ofNullable(getIndex()).map(Index::getClass).orElse((Class) Index.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the underlying cache used for creating indexes.
|
||||
* Sets a reference to the {@link RegionService}.
|
||||
*
|
||||
* @param cache cache used for creating indexes.
|
||||
* @param cache reference to the {@link RegionService}.
|
||||
* @see org.apache.geode.cache.RegionService
|
||||
*/
|
||||
public void setCache(RegionService cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query service used for creating indexes.
|
||||
* Sets the name of the {@link Index}.
|
||||
*
|
||||
* @param service query service used for creating indexes.
|
||||
*/
|
||||
public void setQueryService(QueryService service) {
|
||||
this.queryService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
* @param name {@link String} containing the name given to the {@link Index}.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a boolean condition to indicate whether the Index declared and defined by this FactoryBean will only be
|
||||
* defined initially, or defined and created. If defined-only, the IndexFactoryBean will receive a callback at
|
||||
* the end of the Spring container lifecycle to subsequently "create" all "defined-only" Indexes once, in a
|
||||
* single operation.
|
||||
* Sets the {@link QueryService} used to create the {@link Index}.
|
||||
*
|
||||
* @param define a boolean value indicating the define or define/create status. If true, the Index declared
|
||||
* by this FactoryBean will only be defined initially and subsequently created when this SmartLifecycle bean
|
||||
* receives an appropriate callback from the Spring container; if false, the Index will be created immediately.
|
||||
* @param service {@link QueryService} used to create the {@link Index}.
|
||||
* @see org.apache.geode.cache.query.QueryService
|
||||
*/
|
||||
public void setQueryService(QueryService service) {
|
||||
this.queryService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a boolean condition to indicate whether the {@link Index} declared and defined by this
|
||||
* {@link IndexFactoryBean} will only be defined initially, or defined and created. If defined-only,
|
||||
* the {@link IndexFactoryBean} will receive a callback at the end of the Spring container lifecycle
|
||||
* to subsequently "create" all "defined-only" {@link Index Indexes} once, in a single operation.
|
||||
*
|
||||
* @param define a boolean value indicating the define or define/create status. If {@literal true},
|
||||
* the {@link Index} declared by this {@link IndexFactoryBean} will only be defined initially
|
||||
* and subsequently created when this bean receives an appropriate callback from the Spring container;
|
||||
* if {@literal false}, the {@link Index} will be created immediately.
|
||||
*/
|
||||
public void setDefine(boolean define) {
|
||||
this.define = define;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
/**
|
||||
* Returns a boolean indicating whether the {@link Index} declared and defined by this {@link IndexFactoryBean}
|
||||
* will only be defined initially, or defined and created. If defined-only, the {@link IndexFactoryBean}
|
||||
* will receive a callback at the end of the Spring container lifecycle to subsequently "create" all "defined-only"
|
||||
* {@link Index Indexes} once, in a single operation.
|
||||
*
|
||||
* @return a boolean value indicating the define or define/create status. If {@literal true}, the {@link Index}
|
||||
* declared by this {@link IndexFactoryBean} will only be defined initially and subsequently created when this bean
|
||||
* receives an appropriate callback from the Spring container; if {@literal false}, the {@link Index}
|
||||
* will be created immediately.
|
||||
*/
|
||||
protected boolean isDefine() {
|
||||
return define;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expression the expression to set
|
||||
* @param expression Index expression to set
|
||||
*/
|
||||
public void setExpression(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from the from to set
|
||||
* @param from Index from clause to set
|
||||
*/
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param imports the imports to set
|
||||
* @param imports Index imports to set
|
||||
*/
|
||||
public void setImports(String imports) {
|
||||
this.imports = imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}
|
||||
* when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown.
|
||||
*
|
||||
* An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
|
||||
* but with another name.
|
||||
*
|
||||
* An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
|
||||
* but possibly a different definition.
|
||||
*
|
||||
* When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown,
|
||||
* then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation
|
||||
* and the name of the existing {@link Index} is added as an alias for this bean.
|
||||
*
|
||||
* When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown,
|
||||
* then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation.
|
||||
* A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index}
|
||||
* are different.
|
||||
*
|
||||
* {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @param ignore boolean value indicating whether to ignore the {@link Index} defined by
|
||||
* this {@link IndexFactoryBean}. Default is {@literal false}.
|
||||
* @see #setOverride(boolean)
|
||||
*/
|
||||
public void setIgnoreIfExists(boolean ignore) {
|
||||
this.ignoreIfExists = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}
|
||||
* when an {@link IndexExistsException} or {@link IndexNameConflictException} is thrown.
|
||||
*
|
||||
* An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
|
||||
* but with another name.
|
||||
*
|
||||
* An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
|
||||
* but possibly a different definition.
|
||||
*
|
||||
* When {@literal ignoreIfExists} is set to {@literal true} and an {@link IndexExistsException} is thrown,
|
||||
* then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation
|
||||
* and the name of the existing {@link Index} is added as an alias for this bean.
|
||||
*
|
||||
* When {@literal ignoreIfExists} is set to {@literal true} and {@link IndexNameConflictException} is thrown,
|
||||
* then the existing {@link Index} will be returned as the object of this {@link IndexFactoryBean} creation.
|
||||
* A warning is logged if the definition of this {@link IndexFactoryBean} and the existing {@link Index}
|
||||
* are different.
|
||||
*
|
||||
* {@literal ignoreIfExists} takes precedence over {@link #isOverride() override}.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @return a boolean value indicating whether to ignore the {@link Index} defined by this {@link IndexFactoryBean}.
|
||||
* Default is {@literal false}.
|
||||
* @see #setIgnoreIfExists(boolean)
|
||||
*/
|
||||
public boolean isIgnoreIfExists() {
|
||||
return this.ignoreIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation to set an array of {@link IndexConfigurer IndexConfigurers} used to apply
|
||||
* additional configuration to this {@link IndexFactoryBean} when using Annotation-based configuration.
|
||||
@@ -432,28 +726,88 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
}
|
||||
|
||||
/**
|
||||
* @param override the override to set
|
||||
* Configures whether to override an existing {@link Index} having the same definition but different name
|
||||
* as the {@link Index} that would be created by this {@link IndexFactoryBean}.
|
||||
*
|
||||
* An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
|
||||
* but with another name.
|
||||
*
|
||||
* An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
|
||||
* but possibly a different definition.
|
||||
*
|
||||
* With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override
|
||||
* is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index}
|
||||
* will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean}
|
||||
* under the new {@link #resolveIndexName() name} having the same definition.
|
||||
*
|
||||
* With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown,
|
||||
* then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition.
|
||||
* When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding
|
||||
* effectively just rebuilds the {@link Index}.
|
||||
*
|
||||
* {@literal ignoreIfExists} takes precedence over {@literal override}.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @param override boolean value indicating whether an existing {@link Index} will be removed and recreated
|
||||
* by this {@link IndexFactoryBean}. Default is {@literal false}.
|
||||
* @see #setIgnoreIfExists(boolean)
|
||||
*/
|
||||
public void setOverride(boolean override) {
|
||||
this.override = override;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type the type to set
|
||||
* Determines whether to override an existing {@link Index} having the same definition but different name
|
||||
* as the {@link Index} that would be created by this {@link IndexFactoryBean}.
|
||||
*
|
||||
* An {@link IndexExistsException} is thrown when there exists another {@link Index} with the same definition
|
||||
* but with another name.
|
||||
*
|
||||
* An {@link IndexNameConflictException} is thrown when there exists another {@link Index} with the same name
|
||||
* but possibly a different definition.
|
||||
*
|
||||
* With {@literal override} set to {@literal true} when an {@link IndexExistsException} is thrown, then override
|
||||
* is effectively the same as "renaming" the existing {@link Index}. In other words, the existing {@link Index}
|
||||
* will be {@link QueryService#removeIndex(Index) removed} and recreated by this {@link IndexFactoryBean}
|
||||
* under the new {@link #resolveIndexName() name} having the same definition.
|
||||
*
|
||||
* With {@literal override} set to {@literal true} when an {@link IndexNameConflictException} is thrown,
|
||||
* then overriding the existing {@link Index} is equivalent to changing the existing {@link Index} definition.
|
||||
* When this happens, a warning is logged. If the existing {@link Index} definition is the same then overriding
|
||||
* effectively just rebuilds the {@link Index}.
|
||||
*
|
||||
* {@literal ignoreIfExists} takes precedence over {@literal override}.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @return a boolean value indicating whether an existing {@link Index} will be removed and recreated
|
||||
* by this {@link IndexFactoryBean}. Default is {@literal false}.
|
||||
* @see #setOverride(boolean)
|
||||
*/
|
||||
public boolean isOverride() {
|
||||
return this.override;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link IndexType type} of the {@link Index} as a {@link String}.
|
||||
*
|
||||
* @param type {@link String} specifying the {@link IndexType type} of the {@link Index}.
|
||||
* @see org.springframework.data.gemfire.IndexType#valueOf(String)
|
||||
* @see #setType(IndexType)
|
||||
*/
|
||||
public void setType(String type) {
|
||||
setType(IndexType.valueOfIgnoreCase(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of GemFire Index to create.
|
||||
* Set the {@link IndexType type} of the {@link Index}.
|
||||
*
|
||||
* @param indexType the IndexType enumerated value indicating the type of GemFire Index
|
||||
* that will be created by this Spring FactoryBean.
|
||||
* @param type {@link IndexType} indicating the type of the {@link Index}.
|
||||
* @see org.springframework.data.gemfire.IndexType
|
||||
*/
|
||||
public void setType(IndexType indexType) {
|
||||
this.indexType = indexType;
|
||||
public void setType(IndexType type) {
|
||||
this.indexType = type;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -466,96 +820,109 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
private final String indexName;
|
||||
|
||||
protected IndexWrapper(QueryService queryService, String indexName) {
|
||||
Assert.notNull(queryService, "QueryService must not be null");
|
||||
Assert.hasText(indexName, "The name of the Index must be specified!");
|
||||
|
||||
Assert.notNull(queryService, "QueryService is required");
|
||||
Assert.hasText(indexName, "Name of Index is required");
|
||||
|
||||
this.queryService = queryService;
|
||||
this.indexName = indexName;
|
||||
|
||||
}
|
||||
|
||||
protected synchronized Index getIndex() {
|
||||
if (this.index == null) {
|
||||
String localIndexName = getIndexName();
|
||||
/* (non-Javadoc) */
|
||||
protected synchronized Index resolveIndex() {
|
||||
|
||||
for (Index localIndex : getQueryService().getIndexes()) {
|
||||
if (localIndex.getName().equals(localIndexName)) {
|
||||
this.index = localIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String indexName = getIndexName();
|
||||
|
||||
if (this.index == null) {
|
||||
throw new GemfireIndexException(new IndexInvalidException(String.format(
|
||||
"index with name (%1$s) was not found", localIndexName)));
|
||||
}
|
||||
}
|
||||
return Optional.ofNullable(this.index)
|
||||
.orElseGet(() -> {
|
||||
|
||||
return index;
|
||||
AtomicReference<Index> searchResult = new AtomicReference<>();
|
||||
|
||||
nullSafeCollection(getQueryService().getIndexes()).forEach(index -> {
|
||||
if (index.getName().equalsIgnoreCase(indexName)) {
|
||||
searchResult.set(index);
|
||||
}
|
||||
});
|
||||
|
||||
return Optional.of(searchResult).map(it -> {
|
||||
this.index = it.get();
|
||||
return this.index;
|
||||
}).orElseThrow(() -> new GemfireIndexException(
|
||||
String.format("Index with name [%s] was not found", indexName), (Exception) null));
|
||||
});
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected Index getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected String getIndexName() {
|
||||
Assert.state(StringUtils.hasText(indexName), "The Index 'name' was not properly initialized!");
|
||||
return indexName;
|
||||
return Optional.ofNullable(this.indexName).filter(StringUtils::hasText).orElseThrow(() ->
|
||||
newIllegalStateException("Index name is required"));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected QueryService getQueryService() {
|
||||
return queryService;
|
||||
return this.queryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getIndex().getName();
|
||||
return resolveIndex().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalizedFromClause() {
|
||||
return getIndex().getCanonicalizedFromClause();
|
||||
return resolveIndex().getCanonicalizedFromClause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalizedIndexedExpression() {
|
||||
return getIndex().getCanonicalizedIndexedExpression();
|
||||
return resolveIndex().getCanonicalizedIndexedExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalizedProjectionAttributes() {
|
||||
return getIndex().getCanonicalizedProjectionAttributes();
|
||||
return resolveIndex().getCanonicalizedProjectionAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFromClause() {
|
||||
return getIndex().getFromClause();
|
||||
return resolveIndex().getFromClause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexedExpression() {
|
||||
return getIndex().getIndexedExpression();
|
||||
return resolveIndex().getIndexedExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProjectionAttributes() {
|
||||
return getIndex().getProjectionAttributes();
|
||||
return resolveIndex().getProjectionAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region<?, ?> getRegion() {
|
||||
return getIndex().getRegion();
|
||||
return resolveIndex().getRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexStatistics getStatistics() {
|
||||
return getIndex().getStatistics();
|
||||
return resolveIndex().getStatistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public org.apache.geode.cache.query.IndexType getType() {
|
||||
return getIndex().getType();
|
||||
return resolveIndex().getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -567,20 +934,25 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
return (getIndexName().equals(((IndexWrapper) obj).getIndexName()));
|
||||
}
|
||||
|
||||
return getIndex().equals(obj);
|
||||
return resolveIndex().equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = 37;
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getIndexName());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(index);
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (index != null ? String.valueOf(index) : getIndexName());
|
||||
|
||||
return Optional.ofNullable(getIndex()).map(String::valueOf)
|
||||
.orElseGet(this::getIndexName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ package org.springframework.data.gemfire;
|
||||
*/
|
||||
@SuppressWarnings({ "deprecation", "unused" })
|
||||
public enum IndexType {
|
||||
|
||||
FUNCTIONAL(org.apache.geode.cache.query.IndexType.FUNCTIONAL),
|
||||
HASH(org.apache.geode.cache.query.IndexType.HASH),
|
||||
PRIMARY_KEY(org.apache.geode.cache.query.IndexType.PRIMARY_KEY),
|
||||
@@ -49,7 +50,7 @@ public enum IndexType {
|
||||
* @return a boolean value indicating whether the IndexType is a "FUNCTIONAL" Index.
|
||||
* @see #isFunctional()
|
||||
*/
|
||||
public static boolean isFunctional(final IndexType indexType) {
|
||||
public static boolean isFunctional(IndexType indexType) {
|
||||
return (indexType != null && indexType.isFunctional());
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ public enum IndexType {
|
||||
* @return a boolean value indicating whether the IndexType is a "HASH" Index.
|
||||
* @see #isHash()
|
||||
*/
|
||||
public static boolean isHash(final IndexType indexType) {
|
||||
public static boolean isHash(IndexType indexType) {
|
||||
return (indexType != null && indexType.isHash());
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ public enum IndexType {
|
||||
* @return a boolean value indicating whether the IndexType is a "KEY" Index.
|
||||
* @see #isFunctional()
|
||||
*/
|
||||
public static boolean isKey(final IndexType indexType) {
|
||||
public static boolean isKey(IndexType indexType) {
|
||||
return (indexType != null && indexType.isKey());
|
||||
}
|
||||
|
||||
@@ -84,7 +85,8 @@ public enum IndexType {
|
||||
* any IndexType in this enumeration.
|
||||
* @see org.apache.geode.cache.query.IndexType
|
||||
*/
|
||||
public static IndexType valueOf(final org.apache.geode.cache.query.IndexType gemfireIndexType) {
|
||||
public static IndexType valueOf(org.apache.geode.cache.query.IndexType gemfireIndexType) {
|
||||
|
||||
for (IndexType indexType : values()) {
|
||||
if (indexType.getGemfireIndexType().equals(gemfireIndexType)) {
|
||||
return indexType;
|
||||
@@ -101,7 +103,8 @@ public enum IndexType {
|
||||
* @return an IndexType matching the given String.
|
||||
* @see java.lang.String#equalsIgnoreCase(String)
|
||||
*/
|
||||
public static IndexType valueOfIgnoreCase(final String value) {
|
||||
public static IndexType valueOfIgnoreCase(String value) {
|
||||
|
||||
for (IndexType indexType : values()) {
|
||||
if (indexType.name().equalsIgnoreCase(value)) {
|
||||
return indexType;
|
||||
@@ -147,5 +150,4 @@ public enum IndexType {
|
||||
public boolean isKey() {
|
||||
return (this.equals(KEY) || this.equals(PRIMARY_KEY));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
|
||||
getAnnotationAttributes(importingClassMetadata, getEnableIndexingAnnotationTypeName());
|
||||
|
||||
localPersistentEntity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> {
|
||||
|
||||
persistentProperty.findAnnotation(Id.class).ifPresent(idAnnotation ->
|
||||
registerIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity, persistentProperty,
|
||||
IndexType.KEY, idAnnotation, registry));
|
||||
@@ -147,6 +148,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
|
||||
persistentProperty.findAnnotation(LuceneIndexed.class).ifPresent( luceneIndexAnnotation ->
|
||||
registerLuceneIndexBeanDefinition(enableIndexingAttributes, localPersistentEntity,
|
||||
persistentProperty, luceneIndexAnnotation, registry));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,12 +197,13 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
|
||||
indexFactoryBeanBuilder.addPropertyValue("from",
|
||||
resolveFrom(persistentEntity, persistentProperty, indexedAttributes));
|
||||
|
||||
indexFactoryBeanBuilder.addPropertyValue("ignoreIfExists", Boolean.TRUE);
|
||||
|
||||
indexFactoryBeanBuilder.addPropertyValue("indexConfigurers", resolveIndexConfigurers());
|
||||
|
||||
indexFactoryBeanBuilder.addPropertyValue("name", indexName);
|
||||
|
||||
indexFactoryBeanBuilder.addPropertyValue("override",
|
||||
resolveOverride(persistentEntity, persistentProperty, indexedAttributes));
|
||||
indexFactoryBeanBuilder.addPropertyValue("override", Boolean.FALSE);
|
||||
|
||||
indexFactoryBeanBuilder.addPropertyValue("type",
|
||||
resolveType(persistentEntity, persistentProperty, indexedAttributes, indexType).toString());
|
||||
@@ -325,15 +328,6 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration {
|
||||
StringUtils.capitalize(indexType.name().toLowerCase()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unused")
|
||||
private boolean resolveOverride(GemfirePersistentEntity persistentEntity,
|
||||
GemfirePersistentProperty persistentProperty, AnnotationAttributes indexedAttributes) {
|
||||
|
||||
return (indexedAttributes.containsKey("override")
|
||||
&& indexedAttributes.getBoolean("override"));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unused")
|
||||
private IndexType resolveType(GemfirePersistentEntity<?> persistentEntity,
|
||||
|
||||
@@ -42,8 +42,10 @@ class IndexParser extends AbstractSimpleBeanDefinitionParser {
|
||||
private static final AtomicBoolean REGISTERED = new AtomicBoolean(false);
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static void registerDefinedIndexesApplicationListener(ParserContext parserContext) {
|
||||
private static void registerDefinedIndexesApplicationListener(ParserContext parserContext) {
|
||||
|
||||
if (REGISTERED.compareAndSet(false, true)) {
|
||||
|
||||
AbstractBeanDefinition createDefinedIndexesApplicationListener = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DefinedIndexesApplicationListener.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
|
||||
@@ -70,13 +70,6 @@ public @interface Indexed {
|
||||
*/
|
||||
String from() default "";
|
||||
|
||||
/**
|
||||
* Determines whether this given {@link Index} definition should override any existing {@link Index} definition.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*/
|
||||
boolean override() default false;
|
||||
|
||||
/**
|
||||
* Type of Index to create.
|
||||
*
|
||||
|
||||
@@ -203,4 +203,54 @@ public abstract class AbstractFactoryBeanSupport<T> implements FactoryBean<T>,
|
||||
.filter(Log::isInfoEnabled)
|
||||
.ifPresent(log -> log.info(message.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the {@link String message} formatted with the array of {@link Object arguments} at warn level.
|
||||
*
|
||||
* @param message {@link String} containing the message to log.
|
||||
* @param args array of {@link Object arguments} used to format the {@code message}.
|
||||
* @see #logWarning(Supplier)
|
||||
*/
|
||||
protected void logWarning(String message, Object... args) {
|
||||
logWarning(() -> String.format(message, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the {@link String message} supplied by the given {@link Supplier} at warn level.
|
||||
*
|
||||
* @param message {@link Supplier} containing the {@link String message} and arguments to log.
|
||||
* @see org.apache.commons.logging.Log#isWarnEnabled()
|
||||
* @see org.apache.commons.logging.Log#warn(Object)
|
||||
* @see #getLog()
|
||||
*/
|
||||
protected void logWarning(Supplier<String> message) {
|
||||
Optional.ofNullable(getLog())
|
||||
.filter(Log::isWarnEnabled)
|
||||
.ifPresent(log -> log.warn(message.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the {@link String message} formatted with the array of {@link Object arguments} at error level.
|
||||
*
|
||||
* @param message {@link String} containing the message to log.
|
||||
* @param args array of {@link Object arguments} used to format the {@code message}.
|
||||
* @see #logError(Supplier)
|
||||
*/
|
||||
protected void logError(String message, Object... args) {
|
||||
logError(() -> String.format(message, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the {@link String message} supplied by the given {@link Supplier} at error level.
|
||||
*
|
||||
* @param message {@link Supplier} containing the {@link String message} and arguments to log.
|
||||
* @see org.apache.commons.logging.Log#isErrorEnabled()
|
||||
* @see org.apache.commons.logging.Log#error(Object)
|
||||
* @see #getLog()
|
||||
*/
|
||||
protected void logError(Supplier<String> message) {
|
||||
Optional.ofNullable(getLog())
|
||||
.filter(Log::isErrorEnabled)
|
||||
.ifPresent(log -> log.error(message.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,14 +126,14 @@ public abstract class CacheUtils extends DistributedSystemUtils {
|
||||
try {
|
||||
return ClientCacheFactory.getAnyInstance();
|
||||
}
|
||||
catch (CacheClosedException ignore) {
|
||||
catch (CacheClosedException | IllegalStateException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static GemFireCache resolveGemFireCache() {
|
||||
return Optional.<GemFireCache>ofNullable(getCache()).orElseGet(CacheUtils::getClientCache);
|
||||
return Optional.<GemFireCache>ofNullable(getClientCache()).orElseGet(CacheUtils::getCache);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
|
||||
@@ -2256,11 +2256,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="imports" type="xsd:string" use="optional" />
|
||||
<xsd:attribute name="override" type="xsd:string" use="optional" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
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>
|
||||
|
||||
@@ -2358,11 +2358,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="imports" type="xsd:string" use="optional" />
|
||||
<xsd:attribute name="override" type="xsd:string" use="optional" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
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>
|
||||
|
||||
@@ -16,25 +16,21 @@
|
||||
|
||||
package org.springframework.data.gemfire;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.query.Index;
|
||||
import org.apache.geode.cache.query.IndexExistsException;
|
||||
import org.apache.geode.cache.query.IndexNameConflictException;
|
||||
import org.apache.geode.cache.query.QueryService;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -43,26 +39,49 @@ import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* The IndexConflictsIntegrationTest class...
|
||||
* Integration tests with test cases testing the numerous conflicting {@link Index} configurations.
|
||||
*
|
||||
* An {@link IndexExistsException} is thrown when 2 or more {@link Index Indexes} share the same definition
|
||||
* but have different names.
|
||||
*
|
||||
* An {@link IndexNameConflictException} is thrown when 2 or more {@link Index Indexes} share the same name
|
||||
* but have potentially different definitions.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @link https://jira.spring.io/browse/SGF-432
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.query.Index
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see <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());
|
||||
@@ -71,185 +90,260 @@ public class IndexConflictsIntegrationTest {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Index getIndex(String indexName) {
|
||||
|
||||
for (Index index : nullSafeCollection(getQueryService().getIndexes())) {
|
||||
if (index.getName().equalsIgnoreCase(indexName)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getIndexCount() {
|
||||
return nullSafeCollection(getQueryService().getIndexes()).size();
|
||||
}
|
||||
|
||||
private QueryService getQueryService() {
|
||||
return this.applicationContext.getBean("gemfireCache", GemFireCache.class).getQueryService();
|
||||
}
|
||||
|
||||
private boolean hasIndex(String indexName) {
|
||||
return (getIndex(indexName) != null);
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
|
||||
return new AnnotationConfigApplicationContext(annotatedClasses);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
System.getProperties().remove("gemfire.cache.region.index.override");
|
||||
|
||||
assertThat(System.getProperties().containsKey("gemfire.cache.region.index.override"), is(false));
|
||||
assertThat(IGNORE.get()).isFalse();
|
||||
assertThat(OVERRIDE.get()).isFalse();
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void indexDefinitionConflictThrowsException() {
|
||||
ConfigurableApplicationContext applicationContext = null;
|
||||
@After
|
||||
public void tearDown() {
|
||||
|
||||
OVERRIDE.set(false);
|
||||
IGNORE.set(false);
|
||||
|
||||
assertThat(close(this.applicationContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexDefinitionConflictIgnoresIndex() {
|
||||
|
||||
assertThat(IGNORE.compareAndSet(false, true)).isTrue();
|
||||
|
||||
this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
|
||||
|
||||
assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue();
|
||||
assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue();
|
||||
assertIndexCount(1);
|
||||
assertThat(hasIndex("customerIdIndex")).isTrue();
|
||||
assertThat(hasIndex("customerIdentifierIndex")).isFalse();
|
||||
|
||||
Index customersIdIndex = getIndex("customerIdIndex");
|
||||
|
||||
assertIndex(customersIdIndex, "customerIdIndex",
|
||||
"id", "/Customers", IndexType.PRIMARY_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexDefinitionConflictOverridesIndex() {
|
||||
|
||||
assertThat(OVERRIDE.compareAndSet(false, true)).isTrue();
|
||||
|
||||
this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
|
||||
|
||||
assertThat(this.applicationContext.containsBean("customerIdIndex")).isTrue();
|
||||
assertThat(this.applicationContext.containsBean("customerIdentifierIndex")).isTrue();
|
||||
assertIndexCount(1);
|
||||
assertThat(hasIndex("customerIdIndex")).isFalse();
|
||||
assertThat(hasIndex("customerIdentifierIndex")).isTrue();
|
||||
|
||||
Index customersIdentifierIndex = getIndex("customerIdentifierIndex");
|
||||
|
||||
assertIndex(customersIdentifierIndex, "customerIdentifierIndex",
|
||||
"id", "/Customers", IndexType.PRIMARY_KEY);
|
||||
}
|
||||
|
||||
@Test(expected = IndexExistsException.class)
|
||||
public void indexDefinitionConflictThrowsIndexExistsException() throws Throwable {
|
||||
try {
|
||||
applicationContext = new AnnotationConfigApplicationContext(
|
||||
IndexDefinitionConflictGemFireConfiguration.class);
|
||||
this.applicationContext = newApplicationContext(IndexDefinitionConflictConfiguration.class);
|
||||
}
|
||||
catch (BeanCreationException expected) {
|
||||
assertThat(expected.getMessage(), containsString("Error creating bean with name 'customerIdentityIndex'"
|
||||
+ " defined in org.springframework.data.gemfire.IndexConflictsIntegrationTest$IndexDefinitionConflictGemFireConfiguration:"
|
||||
+ " Invocation of init method failed"));
|
||||
assertThat(expected.getCause(), is(instanceOf(GemfireIndexException.class)));
|
||||
assertThat(expected.getCause().getMessage(),
|
||||
containsString("An Index with a different name having the same definition"
|
||||
+ " as this Index (customerIdentityIndex) already exists"));
|
||||
assertThat(expected.getCause().getCause(), is(instanceOf(IndexExistsException.class)));
|
||||
assertThat(expected.getCause().getCause().getMessage(), is(equalTo("Similar Index Exists")));
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
assertThat(close(applicationContext), is(true));
|
||||
assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerIdentifierIndex'");
|
||||
|
||||
assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class);
|
||||
|
||||
String existingIndexDefinition = String.format(IndexFactoryBean.BASIC_INDEX_DEFINITION,
|
||||
"id", "/Customers", IndexType.PRIMARY_KEY);
|
||||
|
||||
assertThat(expected.getCause()).hasMessageStartingWith(String.format(
|
||||
"An Index with a different name [customerIdIndex] having the same definition [%s] already exists",
|
||||
existingIndexDefinition));
|
||||
|
||||
assertThat(expected.getCause()).hasCauseInstanceOf(IndexExistsException.class);
|
||||
|
||||
assertThat(expected.getCause().getCause()).hasMessage("Similar Index Exists");
|
||||
|
||||
assertThat(expected.getCause().getCause()).hasNoCause();
|
||||
|
||||
throw expected.getCause().getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexNameConflictOverridesExistingIndex() {
|
||||
ConfigurableApplicationContext applicationContext = null;
|
||||
public void indexNameConflictIgnoresIndex() {
|
||||
|
||||
try {
|
||||
applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class);
|
||||
assertThat(IGNORE.compareAndSet(false, true)).isTrue();
|
||||
|
||||
assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2)));
|
||||
this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
|
||||
|
||||
Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class);
|
||||
assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue();
|
||||
assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue();
|
||||
assertIndexCount(1);
|
||||
assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue();
|
||||
|
||||
assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1)));
|
||||
Index customerLastNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME);
|
||||
|
||||
Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class);
|
||||
|
||||
assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
|
||||
"lastName", "/Customers", IndexType.HASH);
|
||||
|
||||
Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class);
|
||||
|
||||
assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
|
||||
"firstName", "/Customers", IndexType.FUNCTIONAL);
|
||||
|
||||
assertThat(customerFirstNameIndex, is(not(sameInstance(customerLastNameIndex))));
|
||||
assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(),
|
||||
is(sameInstance(customerFirstNameIndex)));
|
||||
}
|
||||
finally {
|
||||
assertThat(close(applicationContext), is(true));
|
||||
}
|
||||
assertIndex(customerLastNameIndex, IndexNameConflictConfiguration.INDEX_NAME,
|
||||
"lastName", "/Customers", IndexType.HASH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexNameConflictReturnsExistingIndex() {
|
||||
ConfigurableApplicationContext applicationContext = null;
|
||||
public void indexNameConflictOverridesIndex() {
|
||||
|
||||
assertThat(OVERRIDE.compareAndSet(false, true)).isTrue();
|
||||
|
||||
this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
|
||||
|
||||
assertThat(this.applicationContext.getBeansOfType(Index.class)).hasSize(2);
|
||||
assertThat(this.applicationContext.containsBean("customerLastNameIndex")).isTrue();
|
||||
assertThat(this.applicationContext.containsBean("customerFirstNameIndex")).isTrue();
|
||||
assertIndexCount(1);
|
||||
assertThat(hasIndex(IndexNameConflictConfiguration.INDEX_NAME)).isTrue();
|
||||
|
||||
Index customerFirstNameIndex = getIndex(IndexNameConflictConfiguration.INDEX_NAME);
|
||||
|
||||
assertIndex(customerFirstNameIndex, IndexNameConflictConfiguration.INDEX_NAME,
|
||||
"firstName", "/Customers", IndexType.FUNCTIONAL);
|
||||
}
|
||||
|
||||
@Test(expected = IndexNameConflictException.class)
|
||||
public void indexNameConflictThrowsIndexNameConflictException() throws Throwable {
|
||||
try {
|
||||
System.setProperty("gemfire.cache.region.index.override", Boolean.FALSE.toString());
|
||||
|
||||
assertThat(System.getProperty("gemfire.cache.region.index.override", "true"),
|
||||
is(equalTo(Boolean.FALSE.toString())));
|
||||
|
||||
applicationContext = new AnnotationConfigApplicationContext(IndexNameConflictGemFireConfiguration.class);
|
||||
|
||||
assertThat(applicationContext.getBeansOfType(Index.class).size(), is(equalTo(2)));
|
||||
|
||||
Cache gemfireCache = applicationContext.getBean("gemfireCache", Cache.class);
|
||||
|
||||
assertThat(gemfireCache.getQueryService().getIndexes().size(), is(equalTo(1)));
|
||||
|
||||
Index customerLastNameIndex = applicationContext.getBean("customerLastNameIndex", Index.class);
|
||||
|
||||
assertIndex(customerLastNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
|
||||
"lastName", "/Customers", IndexType.HASH);
|
||||
|
||||
Index customerFirstNameIndex = applicationContext.getBean("customerFirstNameIndex", Index.class);
|
||||
|
||||
assertIndex(customerFirstNameIndex, IndexNameConflictGemFireConfiguration.INDEX_NAME,
|
||||
"lastName", "/Customers", IndexType.HASH);
|
||||
|
||||
assertThat(customerFirstNameIndex, is(sameInstance(customerLastNameIndex)));
|
||||
assertThat(gemfireCache.getQueryService().getIndexes().iterator().next(),
|
||||
is(sameInstance(customerLastNameIndex)));
|
||||
this.applicationContext = newApplicationContext(IndexNameConflictConfiguration.class);
|
||||
}
|
||||
finally {
|
||||
System.getProperties().remove("gemfire.cache.region.index.override");
|
||||
catch (BeanCreationException expected) {
|
||||
|
||||
if (applicationContext != null) {
|
||||
applicationContext.close();
|
||||
}
|
||||
assertThat(expected).hasMessageStartingWith("Error creating bean with name 'customerFirstNameIndex'");
|
||||
|
||||
assertThat(expected).hasCauseInstanceOf(GemfireIndexException.class);
|
||||
|
||||
assertThat(expected.getCause()).hasMessageStartingWith(String.format(
|
||||
"An Index with the same name [%s] having possibly a different definition already exists;",
|
||||
IndexNameConflictConfiguration.INDEX_NAME));
|
||||
|
||||
assertThat(expected.getCause()).hasCauseInstanceOf(IndexNameConflictException.class);
|
||||
|
||||
assertThat(expected.getCause().getCause()).hasMessage(String.format("Index named ' %s ' already exists.",
|
||||
IndexNameConflictConfiguration.INDEX_NAME));
|
||||
|
||||
assertThat(expected.getCause().getCause()).hasNoCause();
|
||||
|
||||
throw expected.getCause().getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public static class BaseGemFireConfiguration {
|
||||
public static class GemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
public Properties gemfireProperties() {
|
||||
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", IndexConflictsIntegrationTest.class.getSimpleName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level", "warning");
|
||||
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheFactoryBean gemfireCache() {
|
||||
|
||||
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
|
||||
|
||||
cacheFactoryBean.setClose(true);
|
||||
cacheFactoryBean.setProperties(gemfireProperties());
|
||||
cacheFactoryBean.setUseBeanFactoryLocator(false);
|
||||
|
||||
return cacheFactoryBean;
|
||||
}
|
||||
|
||||
@Bean(name = "Customers")
|
||||
public ReplicatedRegionFactoryBean customersRegion(Cache gemfireCache) {
|
||||
public ReplicatedRegionFactoryBean customersRegion(GemFireCache gemfireCache) {
|
||||
|
||||
ReplicatedRegionFactoryBean customersRegionFactory = new ReplicatedRegionFactoryBean();
|
||||
|
||||
customersRegionFactory.setCache(gemfireCache);
|
||||
customersRegionFactory.setName("Customers");
|
||||
customersRegionFactory.setClose(false);
|
||||
customersRegionFactory.setPersistent(false);
|
||||
|
||||
return customersRegionFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(BaseGemFireConfiguration.class)
|
||||
@Import(GemFireConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public static class IndexDefinitionConflictGemFireConfiguration {
|
||||
public static class IndexDefinitionConflictConfiguration {
|
||||
|
||||
@Bean
|
||||
public IndexFactoryBean customerIdIndex(Cache gemfireCache) {
|
||||
public IndexFactoryBean customerIdIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
|
||||
|
||||
indexFactoryBean.setCache(gemfireCache);
|
||||
indexFactoryBean.setExpression("id");
|
||||
indexFactoryBean.setFrom("/Customers");
|
||||
indexFactoryBean.setType(IndexType.PRIMARY_KEY);
|
||||
|
||||
return indexFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn("customerIdIndex")
|
||||
public IndexFactoryBean customerIdentityIndex(Cache gemfireCache) {
|
||||
public IndexFactoryBean customerIdentifierIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
|
||||
|
||||
indexFactoryBean.setCache(gemfireCache);
|
||||
indexFactoryBean.setExpression("id");
|
||||
indexFactoryBean.setIgnoreIfExists(IGNORE.get());
|
||||
indexFactoryBean.setFrom("/Customers");
|
||||
indexFactoryBean.setOverride(OVERRIDE.get());
|
||||
indexFactoryBean.setType(IndexType.PRIMARY_KEY);
|
||||
|
||||
return indexFactoryBean;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(BaseGemFireConfiguration.class)
|
||||
@Import(GemFireConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public static class IndexNameConflictGemFireConfiguration {
|
||||
public static class IndexNameConflictConfiguration {
|
||||
|
||||
protected static final String INDEX_NAME = "CustomerNameIdx";
|
||||
|
||||
@Bean
|
||||
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
||||
return new PropertyPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IndexFactoryBean customerLastNameIndex(Cache gemfireCache,
|
||||
@Value("${gemfire.cache.region.index.override:true}") boolean override) {
|
||||
public IndexFactoryBean customerLastNameIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
|
||||
|
||||
@@ -257,7 +351,6 @@ public class IndexConflictsIntegrationTest {
|
||||
indexFactoryBean.setExpression("lastName");
|
||||
indexFactoryBean.setFrom("/Customers");
|
||||
indexFactoryBean.setName(INDEX_NAME);
|
||||
indexFactoryBean.setOverride(override);
|
||||
indexFactoryBean.setType(IndexType.HASH);
|
||||
|
||||
return indexFactoryBean;
|
||||
@@ -265,20 +358,19 @@ public class IndexConflictsIntegrationTest {
|
||||
|
||||
@Bean
|
||||
@DependsOn("customerLastNameIndex")
|
||||
public IndexFactoryBean customerFirstNameIndex(Cache gemfireCache,
|
||||
@Value("${gemfire.cache.region.index.override:true}") boolean override) {
|
||||
public IndexFactoryBean customerFirstNameIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean indexFactoryBean = new IndexFactoryBean();
|
||||
|
||||
indexFactoryBean.setCache(gemfireCache);
|
||||
indexFactoryBean.setExpression("firstName");
|
||||
indexFactoryBean.setFrom("/Customers");
|
||||
indexFactoryBean.setIgnoreIfExists(IGNORE.get());
|
||||
indexFactoryBean.setName(INDEX_NAME);
|
||||
indexFactoryBean.setOverride(override);
|
||||
indexFactoryBean.setOverride(OVERRIDE.get());
|
||||
indexFactoryBean.setType(IndexType.FUNCTIONAL);
|
||||
|
||||
return indexFactoryBean;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -38,7 +37,10 @@ import org.apache.geode.cache.RegionShortcut;
|
||||
import org.apache.geode.cache.lucene.LuceneIndex;
|
||||
import org.apache.geode.cache.lucene.LuceneService;
|
||||
import org.apache.geode.cache.query.Index;
|
||||
import org.apache.geode.cache.query.IndexExistsException;
|
||||
import org.apache.geode.cache.query.IndexNameConflictException;
|
||||
import org.apache.geode.cache.query.QueryService;
|
||||
import org.apache.geode.internal.concurrent.ConcurrentHashSet;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
@@ -71,7 +73,7 @@ import org.springframework.data.gemfire.config.annotation.test.entities.Replicat
|
||||
*/
|
||||
public class EnableIndexingConfigurationUnitTests {
|
||||
|
||||
private static final Set<Index> indexes = new HashSet<>();
|
||||
private static final Set<Index> indexes = new ConcurrentHashSet<>();
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@@ -82,16 +84,19 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertIndex(Index index, String name, String expression, String from, IndexType indexType) {
|
||||
assertThat(index).isNotNull();
|
||||
assertThat(index.getName()).isEqualTo(name);
|
||||
assertThat(index.getIndexedExpression()).isEqualTo(expression);
|
||||
assertThat(index.getFromClause()).isEqualTo(from);
|
||||
assertThat(index.getType()).isEqualTo(indexType.getGemfireIndexType());
|
||||
private static Index findIndexByName(String indexName) {
|
||||
|
||||
for (Index index : indexes) {
|
||||
if (index.getName().equalsIgnoreCase(indexName)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertLuceneIndex(LuceneIndex index, String name, String regionPath, String... fields) {
|
||||
private void assertLuceneIndex(LuceneIndex index, String name, String regionPath, String... fields) {
|
||||
assertThat(index).isNotNull();
|
||||
assertThat(index.getName()).isEqualTo(name);
|
||||
assertThat(index.getRegionPath()).isEqualTo(regionPath);
|
||||
@@ -100,68 +105,87 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
|
||||
private void assertOqlIndex(Index index, String name, String expression, String from, IndexType indexType) {
|
||||
assertThat(index).isNotNull();
|
||||
assertThat(index.getName()).isEqualTo(name);
|
||||
assertThat(index.getIndexedExpression()).isEqualTo(expression);
|
||||
assertThat(index.getFromClause()).isEqualTo(from);
|
||||
assertThat(index.getType()).isEqualTo(indexType.getGemfireIndexType());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
|
||||
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses);
|
||||
applicationContext.registerShutdownHook();
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistentEntityIndexesCreatedSuccessfully() {
|
||||
applicationContext = newApplicationContext(IndexedPersistentEntityConfiguration.class);
|
||||
public void persistentEntityIndexesAreCreated() {
|
||||
|
||||
applicationContext = newApplicationContext(IndexingEnabledWithIndexedPersistentEntityConfiguration.class);
|
||||
|
||||
assertLuceneIndexes(applicationContext);
|
||||
assertOqlIndexes(applicationContext);
|
||||
}
|
||||
|
||||
private void assertLuceneIndexes(ConfigurableApplicationContext applicationContext) {
|
||||
|
||||
LuceneIndex luceneIndex = applicationContext.getBean("TitleLuceneIdx", LuceneIndex.class);
|
||||
|
||||
assertLuceneIndex(luceneIndex, "TitleLuceneIdx", "Customers", "title");
|
||||
}
|
||||
|
||||
private void assertOqlIndexes(ConfigurableApplicationContext applicationContext) {
|
||||
Index customersIdIdx = applicationContext.getBean("CustomersIdKeyIdx", Index.class);
|
||||
|
||||
assertIndex(customersIdIdx, "CustomersIdKeyIdx", "id", "Customers", IndexType.KEY);
|
||||
Index customersIdIndex = applicationContext.getBean("CustomersIdKeyIdx", Index.class);
|
||||
|
||||
Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
|
||||
assertOqlIndex(customersIdIndex, "CustomersIdKeyIdx", "id", "Customers", IndexType.KEY);
|
||||
|
||||
assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx", "first_name",
|
||||
Index customersFirstNameIndex = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
|
||||
|
||||
assertOqlIndex(customersFirstNameIndex, "CustomersFirstNameFunctionalIdx", "first_name",
|
||||
"/LoyalCustomers", IndexType.FUNCTIONAL);
|
||||
|
||||
Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class);
|
||||
Index lastNameIndex = applicationContext.getBean("LastNameIdx", Index.class);
|
||||
|
||||
assertIndex(lastNameIdx, "LastNameIdx", "surname", "Customers", IndexType.HASH);
|
||||
assertOqlIndex(lastNameIndex, "LastNameIdx", "surname", "Customers", IndexType.HASH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistentEntityIndexesWillNotBeCreated() {
|
||||
applicationContext = newApplicationContext(NoIndexesCreatedForIndexedPersistentEntityConfiguration.class);
|
||||
public void persistentEntityIndexesAreNotCreated() {
|
||||
|
||||
applicationContext = newApplicationContext(IndexingNotEnabledWithIndexedPersistentEntityConfiguration.class);
|
||||
|
||||
Map<String, Index> indexes = applicationContext.getBeansOfType(Index.class);
|
||||
|
||||
assertThat(indexes).isNotNull();
|
||||
assertThat(indexes.isEmpty()).isTrue();
|
||||
assertThat(indexes).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexAnnotatedEntityPropertyDoesNotOverrideIndexBeanDefinition() {
|
||||
applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration.class);
|
||||
public void indexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinition() {
|
||||
|
||||
Index lastNameIdx = applicationContext.getBean("LastNameIdx", Index.class);
|
||||
applicationContext = newApplicationContext(
|
||||
IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinitionConfiguration.class);
|
||||
|
||||
assertIndex(lastNameIdx, "LastNameIdx", "last_name", "/People", IndexType.HASH);
|
||||
}
|
||||
Index firstNameIndex = applicationContext.getBean("LoyalCustomersFirstNameFunctionalIdx", Index.class);
|
||||
|
||||
@Test
|
||||
public void indexAnnotatedEntityPropertyOverridesIndexBeanDefinition() {
|
||||
applicationContext = newApplicationContext(IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration.class);
|
||||
|
||||
Index customersFirstNameIdx = applicationContext.getBean("CustomersFirstNameFunctionalIdx", Index.class);
|
||||
|
||||
assertIndex(customersFirstNameIdx, "CustomersFirstNameFunctionalIdx",
|
||||
assertOqlIndex(firstNameIndex, "LoyalCustomersFirstNameFunctionalIdx",
|
||||
"first_name", "/LoyalCustomers", IndexType.FUNCTIONAL);
|
||||
|
||||
assertThat(findIndexByName("CustomersFirstNameFunctionalIdx")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameName() {
|
||||
|
||||
applicationContext = newApplicationContext(
|
||||
IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameNameConfiguration.class);
|
||||
|
||||
Index lastNameIndex = applicationContext.getBean("LastNameIdx", Index.class);
|
||||
|
||||
assertOqlIndex(lastNameIndex, "LastNameIdx", "last_name", "/People", IndexType.HASH);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -175,10 +199,10 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
}
|
||||
|
||||
Cache mockQueryService(Cache mockCache) throws Exception {
|
||||
|
||||
QueryService mockQueryService = mock(QueryService.class);
|
||||
|
||||
when(mockCache.getQueryService()).thenReturn(mockQueryService);
|
||||
when(mockQueryService.getIndexes()).thenReturn(indexes);
|
||||
|
||||
when(mockQueryService.createHashIndex(anyString(), anyString(), anyString()))
|
||||
.thenAnswer(new HashIndexAnswer());
|
||||
@@ -189,11 +213,24 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
when(mockQueryService.createKeyIndex(anyString(), anyString(), anyString()))
|
||||
.thenAnswer(new KeyIndexAnswer());
|
||||
|
||||
when(mockQueryService.getIndexes()).thenReturn(indexes);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
|
||||
Index indexToRemove = invocation.getArgument(0);
|
||||
|
||||
indexes.remove(findIndexByName(indexToRemove.getName()));
|
||||
|
||||
return null;
|
||||
|
||||
}).when(mockQueryService).removeIndex(any(Index.class));
|
||||
|
||||
return mockCache;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Cache mockRegionFactory(Cache mockCache) {
|
||||
|
||||
RegionFactory mockRegionFactory = mock(RegionFactory.class);
|
||||
|
||||
when(mockCache.createRegionFactory()).thenReturn(mockRegionFactory);
|
||||
@@ -206,9 +243,11 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
|
||||
@Bean
|
||||
LuceneService luceneService() {
|
||||
|
||||
LuceneService mockLuceneService = mock(LuceneService.class);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
|
||||
LuceneIndex mockLuceneIndex = mock(LuceneIndex.class);
|
||||
|
||||
String indexName = invocation.getArgument(0);
|
||||
@@ -220,6 +259,7 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
when(mockLuceneService.getIndex(eq(indexName), eq(regionPath))).thenReturn(mockLuceneIndex);
|
||||
|
||||
return mockLuceneIndex;
|
||||
|
||||
}).when(mockLuceneService).createIndex(anyString(), anyString(), Matchers.<String[]>anyVararg());
|
||||
|
||||
return mockLuceneService;
|
||||
@@ -237,16 +277,22 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
|
||||
@Override
|
||||
public Index answer(InvocationOnMock invocation) throws Throwable {
|
||||
|
||||
IndexType indexType = getType();
|
||||
|
||||
String name = invocation.getArgument(0);
|
||||
String expression = invocation.getArgument(1);
|
||||
String from = invocation.getArgument(2);
|
||||
|
||||
validateIndexDefinition(name, expression, from, indexType);
|
||||
validateIndexName(name);
|
||||
|
||||
Index mockIndex = mock(Index.class, name);
|
||||
|
||||
when(mockIndex.getName()).thenReturn(name);
|
||||
when(mockIndex.getIndexedExpression()).thenReturn(expression);
|
||||
when(mockIndex.getFromClause()).thenReturn(from);
|
||||
when(mockIndex.getType()).thenReturn(getType().getGemfireIndexType());
|
||||
when(mockIndex.getType()).thenReturn(indexType.getGemfireIndexType());
|
||||
|
||||
indexes.add(mockIndex);
|
||||
|
||||
@@ -255,6 +301,30 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
|
||||
abstract IndexType getType();
|
||||
|
||||
private void validateIndexDefinition(String name, String expression, String fromClause, IndexType type)
|
||||
throws IndexExistsException {
|
||||
|
||||
for (Index index : indexes) {
|
||||
if (index.getIndexedExpression().equalsIgnoreCase(expression)
|
||||
&& index.getFromClause().equalsIgnoreCase(fromClause)
|
||||
&& index.getType().equals(type.getGemfireIndexType())) {
|
||||
|
||||
throw new IndexExistsException(String.format(
|
||||
"Index [%1$s] has the same definition as existing Index [%2$s]",
|
||||
name, index.getName()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIndexName(String name) throws IndexNameConflictException {
|
||||
|
||||
for (Index index : indexes) {
|
||||
if (index.getName().equalsIgnoreCase(name)) {
|
||||
throw new IndexNameConflictException(String.format("Index with name [%s] already exists", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class FunctionalIndexAnswer extends AbstractIndexAnswer {
|
||||
@@ -286,7 +356,7 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
|
||||
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
|
||||
LocalRegionEntity.class, ReplicateRegionEntity.class }))
|
||||
static class IndexedPersistentEntityConfiguration extends GemFireConfiguration {
|
||||
private static class IndexingEnabledWithIndexedPersistentEntityConfiguration extends GemFireConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -294,7 +364,7 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
|
||||
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
|
||||
LocalRegionEntity.class, ReplicateRegionEntity.class }))
|
||||
static class NoIndexesCreatedForIndexedPersistentEntityConfiguration extends GemFireConfiguration {
|
||||
private static class IndexingNotEnabledWithIndexedPersistentEntityConfiguration extends GemFireConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -303,42 +373,46 @@ public class EnableIndexingConfigurationUnitTests {
|
||||
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
|
||||
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
|
||||
LocalRegionEntity.class, ReplicateRegionEntity.class }))
|
||||
static class IndexAnnotatedEntityPropertyDoesNotOverrideBeanDefinitionConfiguration extends GemFireConfiguration {
|
||||
static class IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameDefinitionConfiguration
|
||||
extends GemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unused")
|
||||
IndexFactoryBean firstNameIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean firstNameIndex = new IndexFactoryBean();
|
||||
|
||||
firstNameIndex.setCache(gemfireCache);
|
||||
firstNameIndex.setName("LoyalCustomersFirstNameFunctionalIdx");
|
||||
firstNameIndex.setExpression("first_name");
|
||||
firstNameIndex.setFrom("/LoyalCustomers");
|
||||
firstNameIndex.setType(IndexType.FUNCTIONAL);
|
||||
|
||||
return firstNameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableIndexing
|
||||
@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
|
||||
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
|
||||
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
|
||||
LocalRegionEntity.class, ReplicateRegionEntity.class }))
|
||||
static class IndexAnnotatedEntityPropertyIsIgnoredWithExistingIndexHavingSameNameConfiguration
|
||||
extends GemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unused")
|
||||
IndexFactoryBean lastNameIndex(GemFireCache gemfireCache) {
|
||||
|
||||
IndexFactoryBean lastNameIndex = new IndexFactoryBean();
|
||||
|
||||
lastNameIndex.setCache(gemfireCache);
|
||||
lastNameIndex.setName("LastNameIdx");
|
||||
lastNameIndex.setExpression("last_name");
|
||||
lastNameIndex.setFrom("/People");
|
||||
lastNameIndex.setName("LastNameIdx");
|
||||
lastNameIndex.setType(IndexType.HASH);
|
||||
|
||||
return lastNameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableIndexing
|
||||
@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
|
||||
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
|
||||
ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
|
||||
LocalRegionEntity.class, ReplicateRegionEntity.class }))
|
||||
static class IndexAnnotatedEntityPropertyOverridesIndexBeanDefinitionConfiguration extends GemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unused")
|
||||
IndexFactoryBean firstNameIndex(GemFireCache gemfireCache) {
|
||||
IndexFactoryBean firstNameIndex = new IndexFactoryBean();
|
||||
|
||||
firstNameIndex.setCache(gemfireCache);
|
||||
firstNameIndex.setExpression("given_name");
|
||||
firstNameIndex.setFrom("/ProspectiveCustomers");
|
||||
firstNameIndex.setName("CustomersFirstNameFunctionalIdx");
|
||||
firstNameIndex.setType(IndexType.HASH);
|
||||
|
||||
return firstNameIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class PartitionRegionEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Indexed(expression = "first_name", from = "/LoyalCustomers", override = true, type = IndexType.FUNCTIONAL)
|
||||
@Indexed(expression = "first_name", from = "/LoyalCustomers", type = IndexType.FUNCTIONAL)
|
||||
private String firstName;
|
||||
|
||||
@Indexed(name = "LastNameIdx", expression = "surname")
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.data.gemfire.config.xml;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -24,13 +25,16 @@ import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.query.Index;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.gemfire.IndexFactoryBean;
|
||||
import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* The IndexNamespaceTest is a test suite of test cases testing the functionality of GemFire Index creation using
|
||||
* the Spring Data GemFire XML namespace (XSD).
|
||||
* Integration tests with test cases testing the functionality of GemFire Index creation using
|
||||
* the Spring Data GemFire XML namespace (XSD) and the {@link IndexParser}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author David Turanski
|
||||
@@ -42,30 +46,42 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations="index-ns.xml", initializers=GemfireTestApplicationContextInitializer.class)
|
||||
@SuppressWarnings({ "deprecation", "unused" })
|
||||
public class IndexNamespaceTest {
|
||||
|
||||
private static final String TEST_REGION_NAME = "IndexedRegion";
|
||||
|
||||
@Resource(name = "simple")
|
||||
private Index simple;
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Resource(name = "basic")
|
||||
private Index basic;
|
||||
|
||||
@Resource(name = "complex")
|
||||
private Index complex;
|
||||
|
||||
@Test
|
||||
public void testBasicIndex() throws Exception {
|
||||
assertEquals("simple", simple.getName());
|
||||
assertEquals("status", simple.getIndexedExpression());
|
||||
assertEquals(Region.SEPARATOR + TEST_REGION_NAME, simple.getFromClause());
|
||||
assertEquals(TEST_REGION_NAME, simple.getRegion().getName());
|
||||
assertEquals(org.apache.geode.cache.query.IndexType.FUNCTIONAL, simple.getType());
|
||||
public void basicIndexIsCorrect() throws Exception {
|
||||
assertEquals("basic", basic.getName());
|
||||
assertEquals("status", basic.getIndexedExpression());
|
||||
assertEquals(Region.SEPARATOR + TEST_REGION_NAME, basic.getFromClause());
|
||||
assertEquals(TEST_REGION_NAME, basic.getRegion().getName());
|
||||
assertEquals(org.apache.geode.cache.query.IndexType.FUNCTIONAL, basic.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexIndex() throws Exception {
|
||||
public void basicIndexFactoryBeanIsCorrect() {
|
||||
|
||||
IndexFactoryBean basicIndexFactoryBean = applicationContext.getBean("&basic", IndexFactoryBean.class);
|
||||
|
||||
assertThat(basicIndexFactoryBean.isIgnoreIfExists()).isFalse();
|
||||
assertThat(basicIndexFactoryBean.isOverride()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexIndexIsCorrect() throws Exception {
|
||||
assertEquals("complex-index", complex.getName());
|
||||
assertEquals("tsi.name", complex.getIndexedExpression());
|
||||
assertEquals(Region.SEPARATOR + TEST_REGION_NAME + " tsi", complex.getFromClause());
|
||||
@@ -73,4 +89,13 @@ public class IndexNamespaceTest {
|
||||
assertEquals(org.apache.geode.cache.query.IndexType.HASH, complex.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexWithIgnoreAndOverrideIsCorrect() {
|
||||
|
||||
IndexFactoryBean indexFactoryBean =
|
||||
applicationContext.getBean("&index-with-ignore-and-override", IndexFactoryBean.class);
|
||||
|
||||
assertThat(indexFactoryBean.isIgnoreIfExists()).isTrue();
|
||||
assertThat(indexFactoryBean.isOverride()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ public class LuceneOperationsIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void findsDoctorDoesAsTypePersonSuccessfully() {
|
||||
|
||||
Collection<Person> doctorDoes = template.queryForValues("title: Doctor*", "title");
|
||||
|
||||
assertThat(doctorDoes).isNotNull();
|
||||
@@ -139,6 +140,7 @@ public class LuceneOperationsIntegrationTests {
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void findsMasterDoesAsTypeUserSuccessfully() {
|
||||
|
||||
List<User> masterDoes = template.query("title: Master*", "title", User.class);
|
||||
|
||||
assertThat(masterDoes).isNotNull();
|
||||
|
||||
@@ -133,6 +133,26 @@ public class AbstractFactoryBeanSupportUnitTests {
|
||||
verify(mockLog, times(1)).info(eq("info log test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsWarningWhenWarnIsEnabled() {
|
||||
when(mockLog.isWarnEnabled()).thenReturn(true);
|
||||
|
||||
factoryBeanSupport.logWarning("%s log test", "warn");
|
||||
|
||||
verify(mockLog, times(1)).isWarnEnabled();
|
||||
verify(mockLog, times(1)).warn(eq("warn log test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsWarningWhenErrorIsEnabled() {
|
||||
when(mockLog.isErrorEnabled()).thenReturn(true);
|
||||
|
||||
factoryBeanSupport.logError("%s log test", "error");
|
||||
|
||||
verify(mockLog, times(1)).isErrorEnabled();
|
||||
verify(mockLog, times(1)).error(eq("error log test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suppressesDebugLoggingWhenDebugIsDisabled() {
|
||||
when(mockLog.isDebugEnabled()).thenReturn(false);
|
||||
@@ -153,6 +173,26 @@ public class AbstractFactoryBeanSupportUnitTests {
|
||||
verify(mockLog, never()).info(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suppressesWarnLoggingWhenWarnIsDisabled() {
|
||||
when(mockLog.isWarnEnabled()).thenReturn(false);
|
||||
|
||||
factoryBeanSupport.logWarning(() -> "test");
|
||||
|
||||
verify(mockLog, times(1)).isWarnEnabled();
|
||||
verify(mockLog, never()).warn(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suppressesErrorLoggingWhenInfoIsDisabled() {
|
||||
when(mockLog.isErrorEnabled()).thenReturn(false);
|
||||
|
||||
factoryBeanSupport.logError(() -> "test");
|
||||
|
||||
verify(mockLog, times(1)).isErrorEnabled();
|
||||
verify(mockLog, never()).error(any());
|
||||
}
|
||||
|
||||
private static class TestFactoryBeanSupport<T> extends AbstractFactoryBeanSupport<T> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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