SGF-741 - Add support for enforcing Lucene Index creation before Region creation.

This commit is contained in:
John Blum
2018-05-03 23:07:30 -07:00
parent d788cc97c4
commit 1113a1d0e4
10 changed files with 847 additions and 57 deletions

View File

@@ -5,12 +5,12 @@ https://pivotal.io/pivotal-gemfire[Pivotal GemFire] integrates with http://lucen
to index and search on data stored in Pivotal GemFire using Lucene queries. Search-based queries also includes
the capability to page through query results.
Additionally, _Spring Data GemFire_ adds support for query projections based on _Spring Data Commons_
Additionally, _Spring Data for Pivotal GemFire_ adds support for query projections based on _Spring Data Commons_
Projection infrastructure. This feature enables the query results to be projected into first-class,
application domain types as needed or required by the application use case.
However, a Lucene `Index` must be created first before any Lucene search-based query can be ran. A `LuceneIndex`
can be created in _Spring (Data GemFire)_ XML config like so...
However, a Lucene `Index` must be created before any Lucene search-based query can be ran. A `LuceneIndex`
can be created in _Spring (Data for Pivotal GemFire)_ XML config like so...
[source,xml]
----
@@ -38,16 +38,46 @@ and can be configured using...
----
Of course, the `Map` can be specified as a top-level bean definition and referenced using the `ref` attribute
on the nested `<gfe:field-analyzers>` element like this, `<gfe-field-analyzers ref="refToTopLevelMapBeanDefinition"/>`.
in the nested `<gfe:field-analyzers>` element like this, `<gfe-field-analyzers ref="refToTopLevelMapBeanDefinition"/>`.
Alternatively, a `LuceneIndex` can be declared in _Spring_ Java config, inside a `@Configuration` class with...
Spring Data for Pivotal GemFire's `LuceneIndexFactoryBean` API and SDG's XML namespace also allows the addition of a
http://gemfire-95-javadocs.docs.pivotal.io/org/apache/geode/cache/lucene/LuceneSerializer.html[`org.apache.geode.cache.lucene.LuceneSerializer`]
to be specified when creating the `LuceneIndex`. The `LuceneSerializer` is used to configure the way objects
are converted to Lucene documents for the index when the object is indexed.
To add an `LuceneSerializer` to the `LuceneIndex`, you only need to...
[source,xml]
----
<bean id="MyLuceneSerializer" class="example.CustomLuceneSerializer"/>
<gfe:lucene-index id="IndexThree" lucene-service-ref="luceneService" region-path="/YetAnotherExample">
<gfe:lucene-serializer ref="MyLuceneSerializer">
</gfe:lucene-index>
----
Of course, you may specify the `LuceneSerializer` as a anonymous, nested bean definition as well, like so...
[source,xml]
----
<gfe:lucene-index id="IndexThree" lucene-service-ref="luceneService" region-path="/YetAnotherExample">
<gfe:lucene-serializer>
<bean class="example.CustomLuceneSerializer"/>
</gfe:lucene-serializer>
</gfe:lucene-index>
----
Alternatively, a developer may declare or define a `LuceneIndex` in Spring Java config,
inside a `@Configuration` class with...
[source,java]
----
@Bean(name = "People")
@DependsOn("personTitleIndex")
PartitionedRegionFactoryBean<Long, Person> peopleRegion(GemFireCache gemfireCache) {
PartitionedRegionFactoryBean<Long, Person> peopleRegion = new PartitionedRegionFactoryBean<>();
@Bean(name = "Books")
@DependsOn("bookTitleIndex")
PartitionedRegionFactoryBean<Long, Book> booksRegion(GemFireCache gemfireCache) {
PartitionedRegionFactoryBean<Long, Book> peopleRegion =
new PartitionedRegionFactoryBean<>();
peopleRegion.setCache(gemfireCache);
peopleRegion.setClose(false);
@@ -57,37 +87,60 @@ PartitionedRegionFactoryBean<Long, Person> peopleRegion(GemFireCache gemfireCach
}
@Bean
LuceneIndexFactoryBean personTitleIndex(GemFireCache gemFireCache) {
LuceneIndexFactoryBean bookTitleIndex(GemFireCache gemFireCache,
LuceneSerializer luceneSerializer) {
LuceneIndexFactoryBean luceneIndex = new LuceneIndexFactoryBean();
luceneIndex.setCache(gemFireCache);
luceneIndex.setFields("title");
luceneIndex.setRegionPath("/People");
luceneIndex.setLuceneSerializer(luceneSerializer);
luceneIndex.setRegionPath("/Books");
return luceneIndex;
}
@Bean
CustomLuceneSerializer myLuceneSerialier() {
return new CustomeLuceneSerializer();
}
----
There are a few limitations of Pivotal GemFire's, Apache Lucene integration support. First, a `LuceneIndex` can only
be created on a GemFire `PARTITION` Region. Second, all `LuceneIndexes` must be created before the the Region on which
the `LuceneIndex` is applied.
There are a few limitations of Pivotal GemFire's, Apache Lucene integration and support.
First, a `LuceneIndex` can only be created on an Pivotal GemFire `PARTITION` Region.
Second, all `LuceneIndexes` must be created before the Region to which the `LuceneIndex` applies.
NOTE: To help ensure that all declared `LuceneIndexes` defined in a Spring context are created before the Regions
on which they apply, SDG includes the `org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor`.
You may register this Spring https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanFactoryPostProcessor.html[`BeanFactoryPostProcessor`]
in XML config using `<bean class="org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor"/>`
The `o.s.d.g.config.support.LuceneIndexRegionBeanFactoryPostProcessor` may only be used when using SDG XML config.
More details about Spring's `BeanFactoryPostProcessors` can be found https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factory-postprocessors[here].
It is possible that these Pivotal GemFire restrictions will not apply in a future release which is why
the SDG `LuceneIndexFactoryBean` API takes a reference to the Region directly as well, rather than just the Region path.
This is more ideal if think about the case in which users may want to define a `LuceneIndex` on an existing Region
with data at a later point during the application's lifecycle and as requirements demand. Where possible, SDG strives
to stick to strongly-typed objects.
to adhere to strongly-typed objects. However, for the time being, you must use the `regionPath` property
to specify the Region to which the `LuceneIndex` will be applied.
NOTE: Additional, in the example above, you will notice the presence of Spring's `@DependsOn` annotation
on the "Books" Region bean definition. This is used to create a dependency from the "Books" Region bean
to the "bookTitleIndex" LuceneIndex bean definition ensuring that the `LuceneIndex` will be created before
the Region on which it applies.
Now that we have a `LuceneIndex` we can perform Lucene based data access operations, such as queries.
== Lucene Template Data Accessors
_Spring Data GemFire_ provides 2 primary templates for Lucene data access operations, depending on how low a level
your application is prepared to deal with.
_Spring Data for Pivotal GemFire_ provides 2 primary templates for Lucene data access operations, depending on
how low of a level your application is prepared to deal with.
The `LuceneOperations` interface defines query operations using Pivotal GemFire
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/lucene/package-frame.html[Lucene types].
http://gemfire-95-javadocs.docs.pivotal.io/org/apache/geode/cache/lucene/package-summary.html[Lucene types].
[source,java]
----
@@ -118,18 +171,18 @@ public interface LuceneOperations {
NOTE: The `[, int resultLimit]` indicates that the `resultLimit` parameter is optional.
The operations in the `LuceneOperations` interface match the operations provided by the Pivotal GemFire's
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/lucene/LuceneQuery.html[LuceneQuery] interface.
However, SDG has the added value of translating proprietary GemFire or Lucene `Exceptions` into _Spring's_ highly
consistent and expressive DAO
http://gemfire-95-javadocs.docs.pivotal.io/org/apache/geode/cache/lucene/LuceneQuery.html[LuceneQuery] interface.
However, SDG has the added value of translating proprietary Pivotal GemFire or Apache Lucene `Exceptions`
into _Spring's_ highly consistent and expressive DAO
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#dao-exceptions[Exception Hierarchy],
particularly as many modern data access operations involve more than single store or repository.
Additionally, SDG's `LuceneOperations` interface can shield your application from interface breaking changes
introduced by the underlying Pivotal GemFire or Apache Lucene APIs when they do and will occur.
However, it would be remorse to only offer a Lucene Data Access Object that only uses Pivotal GemFire and Apache Lucene
data types (e.g. GemFire's `LuceneResultStruct`), therefore SDG gives you the `ProjectingLuceneOperations` interface
to remedy these important application concerns.
However, it would be remorse to only offer a Lucene Data Access Object (DAO) that only uses Pivotal GemFire
and Apache Lucene data types (e.g. Pivotal GemFire's `LuceneResultStruct`), therefore SDG gives you the
`ProjectingLuceneOperations` interface to remedy these important application concerns.
[source,java]
----
@@ -145,14 +198,13 @@ public interface ProjectingLuceneOperations {
}
----
The `ProjectingLuceneOperations` interface primarily uses application domain object types to work with
your application data. The `query` method variants accept a projection type and the template applies
the query results to instances of the given projection type using the _Spring Data Commons_
Projection infrastructure.
The `ProjectingLuceneOperations` interface primarily uses application domain object types allowing you to work with
your application data. The `query` method variants accept a projection type and the template applies the query results
to instances of the given projection type using the _Spring Data Commons_ Projection infrastructure.
Additionally, the template wraps the paged Lucene query results in an instance of the _Spring Data Commons_
abstraction representing a `Page`. The same projection logic can still be applied to the results in the page
and are lazily projected as each page in the collection is accessed.
`Page` abstraction. The same projection logic can still be applied to the results in the page and are lazily projected
as each page in the collection is accessed.
By way of example, suppose I have a class representing a `Person` like so...
@@ -182,6 +234,7 @@ Additionally, I might have a single interface to represent people as `Customers`
interface Customer {
String getName()
}
----
@@ -191,7 +244,9 @@ If I define the following `LuceneIndex`...
----
@Bean
LuceneIndexFactoryBean personLastNameIndex(GemFireCache gemfireCache) {
LuceneIndexFactoryBean personLastNameIndex = new LuceneIndexFactoryBean();
LuceneIndexFactoryBean personLastNameIndex =
new LuceneIndexFactoryBean();
personLastNameIndex.setCache(gemfireCache);
personLastNameIndex.setFields("lastName");
@@ -215,7 +270,7 @@ Or as a `Page` of type `Customer`...
Page<Customer> customers = luceneTemplate.query("lastName: D*", "lastName", 100, 20, Customer.class);
----
The `Page` can then be used to fetch individual pages of results...
The `Page` can then be used to fetch individual pages of the results...
[source,java]
----
@@ -223,7 +278,7 @@ List<Customer> firstPage = customers.getContent();
----
Conveniently, the _Spring Data Commons_ `Page` interface implements `java.lang.Iterable<T>` too making it very easy
to iterate over the content as well.
to iterate over the contents.
The only restriction to the _Spring Data Commons_ Projection infrastructure is that the projection type
must be an interface. However, it is possible to extend the provided, out-of-the-box (OOTB)
@@ -235,9 +290,9 @@ A custom `ProjectionFactory` can be set on a Lucene template using `setProjectio
== Annotation configuration support
Finally, _Spring Data GemFire_ provides Annotation configuration support for `LuceneIndexes`. Eventually, the SDG Lucene
support will find its way into the _Repository_ infrastructure extension for Pivotal GemFire so that Lucene queries
can be expressed as methods on an application `Repository` interface, much like the
Finally, _Spring Data for Pivotal GemFire_ provides Annotation configuration support for `LuceneIndexes`.
Eventually, the SDG Lucene support will find its way into the _Repository_ infrastructure extension for Pivotal GemFire
so that Lucene queries can be expressed as methods on an application `Repository` interface, much like the
http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#gemfire-repositories.executing-queries[OQL support]
today.
@@ -263,8 +318,8 @@ class Person {
}
----
You must be using the SDG Annotation configuration support along with the `@EnableEntityDefineRegions`
and `@EnableIndexing` Annotations to enable this feature...
You must use SDG's Annotation configuration support along with the `@EnableEntityDefineRegions` and `@EnableIndexing`
Annotations to enable this feature...
[source,java]
----
@@ -277,9 +332,12 @@ class ApplicationConfiguration {
}
----
NOTE: Keep in mind that `LuceneIndexes` can only be created on Apache Geode Servers since `LuceneIndexes` only apply
to `PARTTION` Regions.
Given our definition of the `Person` class above, the SDG Annotation configuration support
will find the `Person` entity class definition, determine that people will be stored in
a `PARTITION` Region called "People" and that the Person will have an OQL `Index` on `birthDate`
a `PARTITION` Region called "People" and that the `Person` will have an OQL `Index` on `birthDate`
along with a `LuceneIndex` on `lastName`.
More will be described with this feature in subsequent releases.

View File

@@ -102,9 +102,6 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
private String indexName;
private String name;
/**
* @inheritDoc
*/
@Override
public void afterPropertiesSet() throws Exception {

View File

@@ -17,36 +17,77 @@
package org.springframework.data.gemfire.config.support;
import java.util.Optional;
import java.util.function.Predicate;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.apache.geode.cache.query.Index;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.MethodMetadata;
import org.springframework.data.gemfire.GenericRegionFactoryBean;
import org.springframework.data.gemfire.LocalRegionFactoryBean;
import org.springframework.data.gemfire.PartitionedRegionFactoryBean;
import org.springframework.data.gemfire.ReplicatedRegionFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.client.PoolFactoryBean;
import org.springframework.data.gemfire.util.SpringUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The AbstractDependencyStructuringBeanFactoryPostProcessor class...
* The {@link AbstractDependencyStructuringBeanFactoryPostProcessor} class is a Spring {@link BeanFactoryPostProcessor}
* post processing the Spring {@link BeanFactory} to help ensure that the dependencies between different Apache Geode
* or Pivotal GemFire objects (e.g. {@link Region} and a {@link LuceneIndex} or an OQL {@link Index}) have been
* properly declared in order to the lifecycle of those components are upheld according to Apache Geode
* or Pivotal GemFire requirements/rules.
*
* @author John Blum
* @since 1.0.0
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.beans.factory.config.BeanDefinition
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
* @since 2.1.0
*/
@SuppressWarnings("unused")
public abstract class AbstractDependencyStructuringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
protected BeanDefinition addDependsOn(BeanDefinition beanDefinition, String... beanNames) {
return SpringUtils.addDependsOn(beanDefinition, beanNames);
}
protected Optional<Object> getPropertyValue(BeanDefinition beanDefinition, String propertyName) {
return SpringUtils.getPropertyValue(beanDefinition, propertyName);
}
protected boolean isBeanDefinitionOfType(BeanDefinition beanDefinition, Class<?> type) {
Assert.notNull(type, "Class type must not be null");
return isBeanDefinitionOfType(beanDefinition, typeName -> type.getName().equals(typeName));
}
protected boolean isBeanDefinitionOfType(BeanDefinition beanDefinition, String typeName) {
return isBeanDefinitionOfType(beanDefinition,
typeNameArgument -> String.valueOf(typeName).equals(typeNameArgument));
}
protected boolean isBeanDefinitionOfType(BeanDefinition beanDefinition, Predicate<String> typeFilter) {
return Optional.of(beanDefinition)
.map(it -> beanDefinition.getBeanClassName())
.filter(StringUtils::hasText)
.map(beanClassName -> type.getName().equals(beanClassName))
.map(typeFilter::test)
.orElseGet(() ->
Optional.ofNullable(beanDefinition.getFactoryMethodName())
.filter(StringUtils::hasText)
.filter(it -> beanDefinition instanceof AnnotatedBeanDefinition)
.map(it -> ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata())
.map(methodMetadata -> type.getName().equals(methodMetadata.getReturnTypeName()))
.map(MethodMetadata::getReturnTypeName)
.map(typeFilter::test)
.orElse(false)
);
}
@@ -62,4 +103,15 @@ public abstract class AbstractDependencyStructuringBeanFactoryPostProcessor impl
protected boolean isPoolBean(BeanDefinition beanDefinition) {
return isBeanDefinitionOfType(beanDefinition, PoolFactoryBean.class);
}
protected Predicate<String> isRegionBeanType() {
Predicate<String> genericRegionBeanType =
typeName -> GenericRegionFactoryBean.class.getName().equals(typeName);
return genericRegionBeanType.or(typeName -> ClientRegionFactoryBean.class.getName().equals(typeName))
.or(typeName -> LocalRegionFactoryBean.class.getName().equals(typeName))
.or(typeName -> PartitionedRegionFactoryBean.class.getName().equals(typeName))
.or(typeName -> ReplicatedRegionFactoryBean.class.getName().equals(typeName));
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.support;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean;
import org.springframework.util.StringUtils;
/**
* The {@link LuceneIndexRegionBeanFactoryPostProcessor} class is a Spring {@link BeanFactoryPostProcessor} ensuring
* that a {@link LuceneIndex} is created before the {@link Region} on which the {@link LuceneIndex} is defined.
*
* @author John Blum
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.lucene.LuceneIndex
* @see org.springframework.beans.factory.config.BeanDefinition
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
* @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean
* @see org.springframework.data.gemfire.config.support.AbstractDependencyStructuringBeanFactoryPostProcessor
* @since 2.1.0
*/
@SuppressWarnings("unused")
public class LuceneIndexRegionBeanFactoryPostProcessor extends AbstractDependencyStructuringBeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Map<String, String> regionNameToLuceneIndexBeanName = new HashMap<>();
Map<String, String> regionNameToRegionBeanName = new HashMap<>();
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(beanName -> {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (isBeanDefinitionOfType(beanDefinition, LuceneIndexFactoryBean.class)) {
resolveRegionNameFromLuceneIndex(beanDefinition)
.ifPresent(regionName -> regionNameToLuceneIndexBeanName.put(regionName, beanName));
}
else if (isBeanDefinitionOfType(beanDefinition, isRegionBeanType())) {
resolveRegionNameFromRegionBean(beanName, beanDefinition)
.ifPresent(regionName -> regionNameToRegionBeanName.put(regionName, beanName));
}
});
regionNameToRegionBeanName.keySet().stream().forEach(regionName -> {
if (regionNameToLuceneIndexBeanName.containsKey(regionName)) {
String regionBeanName = regionNameToRegionBeanName.get(regionName);
String luceneIndexBeanName = regionNameToLuceneIndexBeanName.get(regionName);
addDependsOn(beanFactory.getBeanDefinition(regionBeanName), luceneIndexBeanName);
}
});
}
private Optional<String> resolveRegionNameFromLuceneIndex(BeanDefinition beanDefinition) {
return getPropertyValue(beanDefinition, "regionPath")
.map(this::asFullyQualifiedRegionName);
}
private Optional<String> resolveRegionNameFromRegionBean(String beanName, BeanDefinition beanDefinition) {
String regionName = asString(getPropertyValue(beanDefinition, "regionName"));
String name = asString(getPropertyValue(beanDefinition, "name"));
return Optional.ofNullable(StringUtils.hasText(regionName) ? regionName
: (StringUtils.hasText(name) ? name : beanName)).filter(StringUtils::hasText);
}
private String asFullyQualifiedRegionName(Object regionPath) {
return Optional.ofNullable(regionPath)
.map(String::valueOf)
.map(String::trim)
.map(it -> {
int index = it.lastIndexOf(Region.SEPARATOR);
return index > 0 ? it : it.substring(1);
})
.filter(StringUtils::hasText)
.orElse(null);
}
@SuppressWarnings("all")
private String asString(Optional<Object> value) {
return value.map(String::valueOf)
.map(String::trim)
.filter(StringUtils::hasText)
.orElse(null);
}
}

View File

@@ -24,6 +24,7 @@ import org.apache.geode.cache.lucene.LuceneService;
import org.apache.geode.cache.lucene.LuceneServiceProvider;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
import org.springframework.util.Assert;
/**
@@ -38,7 +39,7 @@ import org.springframework.util.Assert;
* @since 1.1.0
*/
@SuppressWarnings("unused")
public class LuceneServiceFactoryBean implements FactoryBean<LuceneService>, InitializingBean {
public class LuceneServiceFactoryBean extends AbstractFactoryBeanSupport<LuceneService> implements InitializingBean {
private GemFireCache gemfireCache;
@@ -49,6 +50,7 @@ public class LuceneServiceFactoryBean implements FactoryBean<LuceneService>, Ini
*/
@Override
public void afterPropertiesSet() throws Exception {
GemFireCache gemfireCache = getCache();
Assert.state(gemfireCache != null,
@@ -83,15 +85,10 @@ public class LuceneServiceFactoryBean implements FactoryBean<LuceneService>, Ini
*/
@Override
public Class<?> getObjectType() {
return Optional.ofNullable(this.luceneService).<Class<?>>map(LuceneService::getClass).orElse(LuceneService.class);
}
/**
* @inheritDoc
*/
@Override
public boolean isSingleton() {
return true;
return Optional.ofNullable(this.luceneService)
.<Class<?>>map(LuceneService::getClass)
.orElse(LuceneService.class);
}
/**

View File

@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -51,6 +52,14 @@ public abstract class SpringUtils {
return bean;
}
public static Optional<Object> getPropertyValue(BeanDefinition beanDefinition, String propertyName) {
return Optional.ofNullable(beanDefinition)
.map(it -> it.getPropertyValues())
.map(propertyValues -> propertyValues.getPropertyValue(propertyName))
.map(propertyValue -> propertyValue.getValue());
}
public static BeanDefinition setPropertyReference(BeanDefinition beanDefinition,
String propertyName, String beanName) {

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.geode.cache.Region;
import org.apache.shiro.util.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.data.gemfire.test.mock.context.GemFireMockObjectsApplicationContextInitializer;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Unit tests for {@link LuceneIndexRegionBeanFactoryPostProcessor}.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor
* @since 2.1.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(initializers = GemFireMockObjectsApplicationContextInitializer.class)
public class LuceneIndexRegionBeanFactoryPostProcessorIntegrationTests {
private static final List<String> beanNames = new CopyOnWriteArrayList<>();
@Autowired
private ApplicationContext applicationContext;
private BeanDefinition getBeanDefinition(String beanName) {
return Optional.ofNullable(this.applicationContext)
.filter(it -> it instanceof AbstractApplicationContext)
.map(it -> (AbstractApplicationContext) it)
.map(it -> it.getBeanFactory())
.map(beanFactory -> beanFactory.getBeanDefinition(beanName))
.orElse(null);
}
@Test
public void regionLuceneIndexAndDiskStoreBeanDependenciesAreCorrect() {
BeanDefinition mockDiskStore = getBeanDefinition("MockDiskStore");
assertThat(mockDiskStore).isNotNull();
assertThat(nullSafeArray(mockDiskStore.getDependsOn(), String.class))
.doesNotContain("BookTitleLuceneIndex", "ContractDescriptionLuceneIndex");
BeanDefinition bookTitleLuceneIndex = getBeanDefinition("BookTitleLuceneIndex");
assertThat(bookTitleLuceneIndex).isNotNull();
assertThat(nullSafeArray(bookTitleLuceneIndex.getDependsOn(), String.class))
.doesNotContain("Books", "BookTitleLuceneIndex", "ContractDescriptionLuceneIndex");
BeanDefinition contractDescriptionLuceneIndex = getBeanDefinition("ContractDescriptionLuceneIndex");
assertThat(contractDescriptionLuceneIndex).isNotNull();
assertThat(nullSafeArray(contractDescriptionLuceneIndex.getDependsOn(), String.class))
.doesNotContain("Contracts", "BookTitleLuceneIndex", "ContractDescriptionLuceneIndex");
BeanDefinition booksRegion = getBeanDefinition("Books");
assertThat(booksRegion).isNotNull();
assertThat(booksRegion.getDependsOn()).contains("BookTitleLuceneIndex");
assertThat(booksRegion.getDependsOn()).doesNotContain("ContractDescriptionLuceneIndex");
BeanDefinition contractsRegion = getBeanDefinition("Contracts");
assertThat(contractsRegion).isNotNull();
assertThat(contractsRegion.getDependsOn()).contains("ContractDescriptionLuceneIndex");
assertThat(contractsRegion.getDependsOn()).doesNotContain("BookTitleLuceneIndex");
BeanDefinition peopleRegion = getBeanDefinition("People");
assertThat(peopleRegion).isNotNull();
assertThat(nullSafeArray(peopleRegion.getDependsOn(), String.class))
.doesNotContain("BookTitleLuceneIndex", "ContractDescriptionLucenenIndex");
}
@Test
public void gemfireBeanProcessingOrderIsCorrect() {
assertThat(beanNames.indexOf("BookTitleLuceneIndex")).isLessThan(beanNames.indexOf("Books"));
assertThat(beanNames.indexOf("ContractDescriptionLuceneIndex")).isLessThan(beanNames.indexOf("Contracts"));
}
static class BeanProcessingOrderRecordingBeanPostProcessor implements BeanPostProcessor {
@Nullable @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (isRegionBean(bean)) {
Assert.isTrue(!"Books".equals(beanName) || beanNames.contains("BookTitleLuceneIndex"),
"Expected [BookTitleLuceneIndex] to already exist");
Assert.isTrue(!"Contracts".equals(beanName) || beanNames.contains("ContractDescriptionLuceneIndex"),
"Expected [ContractDescriptionLuceneIndex] to already exist");
}
if (!beanNames.contains(beanName)) {
beanNames.add(beanName);
}
return bean;
}
private boolean isRegionBean(Object bean) {
return bean instanceof Region || bean instanceof RegionFactoryBean;
}
}
}

View File

@@ -31,6 +31,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.asSet;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.NOT_SUPPORTED;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
@@ -55,6 +56,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -97,6 +99,13 @@ import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.client.PoolFactory;
import org.apache.geode.cache.control.ResourceManager;
import org.apache.geode.cache.execute.RegionFunctionContext;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.apache.geode.cache.lucene.LuceneIndexFactory;
import org.apache.geode.cache.lucene.LuceneQuery;
import org.apache.geode.cache.lucene.LuceneQueryFactory;
import org.apache.geode.cache.lucene.LuceneQueryProvider;
import org.apache.geode.cache.lucene.LuceneSerializer;
import org.apache.geode.cache.lucene.LuceneService;
import org.apache.geode.cache.query.CqAttributes;
import org.apache.geode.cache.query.CqQuery;
import org.apache.geode.cache.query.Index;
@@ -118,6 +127,7 @@ import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.concurrent.ConcurrentHashSet;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.lucene.analysis.Analyzer;
import org.mockito.ArgumentMatchers;
import org.mockito.stubbing.Answer;
import org.springframework.data.gemfire.IndexType;
@@ -277,6 +287,20 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
}
}
private static <T> T doSafeOperation(ExceptionThrowingOperation<T> operation) {
return doSafeOperation(operation, null);
}
private static <T> T doSafeOperation(ExceptionThrowingOperation<T> operation, T defaultValue) {
try {
return operation.doExceptionThrowingOperation();
}
catch (Exception ignore) {
return defaultValue;
}
}
/**
* Determines whether the given {@link Region} is a root {@link Region}.
*
@@ -400,6 +424,19 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
newIllegalStateException("RegionAttributes with ID [%s] cannot be found", regionAttributesId));
}
private static <T> T rethrowAsRuntimeException(ExceptionThrowingOperation<T> operation) {
try {
return operation.doExceptionThrowingOperation();
}
catch (RuntimeException cause) {
throw cause;
}
catch (Exception cause) {
throw new RuntimeException(cause);
}
}
/**
* Converts the given {@link String Region name} into a proper {@link Region#getName() Region name}.
*
@@ -1772,6 +1809,204 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
return mock(IndexStatistics.class, mockObjectIdentifier(name));
}
public static LuceneIndexFactory mockLuceneIndexFactory() {
return mockLuceneIndexFactory(null);
}
private static LuceneIndexFactory mockLuceneIndexFactory(Map<LuceneIndexKey, LuceneIndex> luceneIndexes) {
LuceneIndexFactory mockLuceneIndexFactory = mock(LuceneIndexFactory.class);
AtomicReference<LuceneSerializer> luceneSerializerReference = new AtomicReference<>(null);
Map<String, Analyzer> fieldAnalyzers = new ConcurrentHashMap<>();
Set<String> fields = new CopyOnWriteArraySet<>();
when(mockLuceneIndexFactory.addField(anyString())).thenAnswer(invocation -> {
String fieldName = invocation.getArgument(0);
fields.add(fieldName);
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.addField(anyString(), any(Analyzer.class))).thenAnswer(invocation -> {
String fieldName = invocation.getArgument(0);
Analyzer analyzer = invocation.getArgument(1);
fieldAnalyzers.put(fieldName, analyzer);
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setFields(ArgumentMatchers.<String[]>any())).thenAnswer(invocation -> {
Object[] fieldsArgument = invocation.getArguments();
fields.clear();
Arrays.stream(nullSafeArray(fieldsArgument, Object.class))
.filter(field -> field instanceof String)
.map(String::valueOf)
.forEach(fields::add);
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setFields(any(Map.class))).thenAnswer(invocation -> {
Map<String, Analyzer> fieldAnalyzersArgument = invocation.getArgument(0);
fieldAnalyzers.clear();
fieldAnalyzers.putAll(nullSafeMap(fieldAnalyzers));
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setLuceneSerializer(any(LuceneSerializer.class))).thenAnswer(invocation -> {
Optional.ofNullable(invocation.<LuceneSerializer>getArgument(0))
.map(luceneSerializer -> {
luceneSerializerReference.set(luceneSerializer);
return luceneSerializer;
})
.orElseGet(() -> {
luceneSerializerReference.set(null);
return null;
});
return mockLuceneIndexFactory;
});
doAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionPath = invocation.getArgument(1);
LuceneIndexKey key = LuceneIndexKey.of(indexName, regionPath);
LuceneIndex mockLuceneIndex = mock(LuceneIndex.class, key.toString());
when(mockLuceneIndex.getFieldAnalyzers()).thenReturn(Collections.unmodifiableMap(fieldAnalyzers));
when(mockLuceneIndex.getFieldNames()).thenAnswer(in -> fields.toArray(new String[fields.size()]));
when(mockLuceneIndex.getLuceneSerializer()).thenAnswer(in -> luceneSerializerReference.get());
when(mockLuceneIndex.getName()).thenReturn(indexName);
when(mockLuceneIndex.getRegionPath()).thenReturn(regionPath);
Optional.ofNullable(luceneIndexes).ifPresent(it -> it.put(key, mockLuceneIndex));
return null;
}).when(mockLuceneIndexFactory).create(anyString(), anyString());
return mockLuceneIndexFactory;
}
public static LuceneQueryFactory mockLuceneQueryFactory() {
LuceneQueryFactory mockLuceneQueryFactory = mock(LuceneQueryFactory.class);
AtomicInteger limit = new AtomicInteger(LuceneQueryFactory.DEFAULT_LIMIT);
AtomicInteger pageSize = new AtomicInteger(LuceneQueryFactory.DEFAULT_PAGESIZE);
when(mockLuceneQueryFactory.setLimit(anyInt())).thenAnswer(invocation -> {
limit.set(invocation.getArgument(0));
return mockLuceneQueryFactory;
});
when(mockLuceneQueryFactory.setPageSize(anyInt())).thenAnswer(invocation -> {
pageSize.set(invocation.getArgument(0));
return mockLuceneQueryFactory;
});
when(mockLuceneQueryFactory.create(anyString(), anyString(), any(LuceneQueryProvider.class)))
.thenAnswer(invocation -> mockLuceneQuery(limit.get(), pageSize.get()));
when(mockLuceneQueryFactory.create(anyString(), anyString(), anyString(), anyString()))
.thenAnswer(invocation -> mockLuceneQuery(limit.get(), pageSize.get()));
return mockLuceneQueryFactory;
}
private static LuceneQuery mockLuceneQuery(int limit, int pageSize) {
LuceneQuery mockLuceneQuery = mock(LuceneQuery.class);
when(mockLuceneQuery.getLimit()).thenReturn(limit);
when(mockLuceneQuery.getPageSize()).thenReturn(pageSize);
doSafeOperation(() -> when(mockLuceneQuery.findKeys())
.thenReturn(Collections.emptySet()), Collections.emptySet());
rethrowAsRuntimeException(() -> when(mockLuceneQuery.findPages())
.thenThrow(newUnsupportedOperationException("Operation Not Supported!")));
doSafeOperation(() -> when(mockLuceneQuery.findResults())
.thenReturn(Collections.emptyList()), Collections.emptyList());
doSafeOperation(() -> when(mockLuceneQuery.findValues())
.thenReturn(Collections.emptyList()), Collections.emptyList());
return mockLuceneQuery;
}
public static LuceneService mockLuceneService(Cache mockCache) {
LuceneService mockLuceneService = mock(LuceneService.class);
Map<LuceneIndexKey, LuceneIndex> luceneIndexes = new ConcurrentHashMap<>();
when(mockLuceneService.createIndexFactory())
.thenAnswer(invocation -> mockLuceneIndexFactory(luceneIndexes));
when(mockLuceneService.createLuceneQueryFactory())
.thenAnswer(invocation -> mockLuceneQueryFactory());
doAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionName = invocation .getArgument(1);
luceneIndexes.remove(LuceneIndexKey.of(indexName, regionName));
return null;
}).when(mockLuceneService).destroyIndex(anyString(), anyString());
doAnswer(invocation -> {
String regionPath = invocation.getArgument(0);
luceneIndexes.keySet().stream().filter(key -> key.getRegionPath().equals(regionPath))
.collect(Collectors.toSet()).forEach(key -> luceneIndexes.remove(key));
return null;
}).when(mockLuceneService).destroyIndexes(anyString());
when(mockLuceneService.getAllIndexes()).thenAnswer(invocation ->
Collections.unmodifiableCollection(luceneIndexes.values()));
when(mockLuceneService.getCache()).thenReturn(mockCache);
when(mockLuceneService.getIndex(anyString(), anyString())).thenAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionPath = invocation.getArgument(1);
return luceneIndexes.get(LuceneIndexKey.of(indexName, regionPath));
});
doSafeOperation(() ->
when(mockLuceneService.waitUntilFlushed(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
.thenReturn(true));
return mockLuceneService;
}
@SuppressWarnings("unchecked")
public static <K, V> Region<K, V> mockRegion(RegionService regionService, String name,
RegionAttributes<K, V> regionAttributes) {
@@ -2550,7 +2785,78 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
return clientCacheFactorySpy;
}
protected interface ExceptionThrowingOperation<T> {
T doExceptionThrowingOperation() throws Exception;
}
protected interface IoExceptionThrowingOperation {
void doIo() throws IOException;
}
public static class LuceneIndexKey {
private final String indexName;
private final String regionPath;
public static LuceneIndexKey of(String indexName, Region<?, ?> region) {
Assert.notNull(region, "Region is required");
return of(indexName, region.getFullPath());
}
public static LuceneIndexKey of(String indexName, String regionPath) {
return new LuceneIndexKey(indexName, regionPath);
}
private LuceneIndexKey(String indexName, String regionPath) {
Assert.hasText(indexName, String.format("LuceneIndex name [%s] is required", indexName));
Assert.hasText(regionPath, String.format("Region path [%s] is required", regionPath));
this.indexName = indexName;
this.regionPath = regionPath;
}
protected String getIndexName() {
return this.indexName;
}
protected String getRegionPath() {
return this.regionPath;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof LuceneIndexKey)) {
return false;
}
LuceneIndexKey that = (LuceneIndexKey) obj;
return this.getIndexName().equals(that.getIndexName())
&& this.getRegionPath().equals(that.getRegionPath());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + getIndexName().hashCode();
hashValue = 37 * hashValue + getRegionPath().hashCode();
return hashValue;
}
@Override
public String toString() {
return String.format("%1$s.%2$s", getRegionPath(), getIndexName());
}
}
}

View File

@@ -18,8 +18,12 @@
package org.springframework.data.gemfire.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,6 +31,7 @@ import static org.springframework.data.gemfire.util.ArrayUtils.asArray;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -35,6 +40,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -61,7 +67,8 @@ public class SpringUtilsUnitTests {
when(mockBeanDefinition.getDependsOn()).thenReturn(asArray("testBeanNameOne", "testBeanNameTwo"));
assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree")).isSameAs(mockBeanDefinition);
assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree"))
.isSameAs(mockBeanDefinition);
verify(mockBeanDefinition, times(1)).getDependsOn();
verify(mockBeanDefinition, times(1))
@@ -73,7 +80,8 @@ public class SpringUtilsUnitTests {
when(mockBeanDefinition.getDependsOn()).thenReturn(null);
assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanName")).isSameAs(mockBeanDefinition);
assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanName"))
.isSameAs(mockBeanDefinition);
verify(mockBeanDefinition, times(1)).getDependsOn();
verify(mockBeanDefinition, times(1)).setDependsOn("testBeanName");
@@ -92,6 +100,69 @@ public class SpringUtilsUnitTests {
.setDependsOn("testBeanNameOne", "testBeanNameTwo", "testBeanNameThree", "testBeanNameFour");
}
@Test
public void getPropertyValueForExistingPropertyHavingValueReturnsValue() {
MutablePropertyValues propertyValues =
new MutablePropertyValues(Collections.singletonMap("testProperty", "testValue"));
when(mockBeanDefinition.getPropertyValues()).thenReturn(propertyValues);
assertThat(SpringUtils.getPropertyValue(mockBeanDefinition, "testProperty").orElse(null))
.isEqualTo("testValue");
verify(mockBeanDefinition, times(1)).getPropertyValues();
}
@Test
public void getPropertyValueForExistingPropertyHavingNullValueReturnsNull() {
MutablePropertyValues testPropertyValues = spy(new MutablePropertyValues());
PropertyValue testPropertyValue = spy(new PropertyValue("testProperty", null));
when(mockBeanDefinition.getPropertyValues()).thenReturn(testPropertyValues);
doReturn(testPropertyValue).when(testPropertyValues).getPropertyValue(anyString());
assertThat(SpringUtils.getPropertyValue(mockBeanDefinition, "testProperty").orElse(null))
.isNull();
verify(mockBeanDefinition, times(1)).getPropertyValues();
verify(testPropertyValues, times(1)).getPropertyValue(eq("testProperty"));
verify(testPropertyValue, times(1)).getValue();
}
@Test
public void getPropertyValueForNonExistingPropertyReturnsNull() {
MutablePropertyValues testPropertyValues = spy(new MutablePropertyValues());
when(mockBeanDefinition.getPropertyValues()).thenReturn(testPropertyValues);
assertThat(SpringUtils.getPropertyValue(mockBeanDefinition, "testProperty").orElse(null))
.isNull();
verify(mockBeanDefinition, times(1)).getPropertyValues();
verify(testPropertyValues, times(1)).getPropertyValue(eq("testProperty"));
}
@Test
public void getPropertyValueWithNullPropertyValuesReturnsNull() {
when(mockBeanDefinition.getPropertyValues()).thenReturn(null);
assertThat(SpringUtils.getPropertyValue(mockBeanDefinition, "testProperty").orElse(null))
.isNull();
verify(mockBeanDefinition, times(1)).getPropertyValues();
}
@Test
public void getPropertyValueWithNullBeanDefinitionReturnsNull() {
assertThat(SpringUtils.getPropertyValue(null, "testProperty").orElse(null))
.isNull();
}
@Test
@SuppressWarnings("all")
public void setBeanDefinitionPropertyReference() {
@@ -136,6 +207,7 @@ public class SpringUtilsUnitTests {
@Test
public void defaultIfEmptyReturnsValue() {
assertThat(SpringUtils.defaultIfEmpty("test", "DEFAULT")).isEqualTo("test");
assertThat(SpringUtils.defaultIfEmpty("abc123", "DEFAULT")).isEqualTo("abc123");
assertThat(SpringUtils.defaultIfEmpty("123", "DEFAULT")).isEqualTo("123");
@@ -148,6 +220,7 @@ public class SpringUtilsUnitTests {
@Test
public void defaultIfEmptyReturnsDefault() {
assertThat(SpringUtils.defaultIfEmpty(" ", "DEFAULT")).isEqualTo("DEFAULT");
assertThat(SpringUtils.defaultIfEmpty("", "DEFAULT")).isEqualTo("DEFAULT");
assertThat(SpringUtils.defaultIfEmpty(null, "DEFAULT")).isEqualTo("DEFAULT");
@@ -155,6 +228,7 @@ public class SpringUtilsUnitTests {
@Test
public void defaultIfNullReturnsValue() {
assertThat(SpringUtils.defaultIfNull(true, false)).isTrue();
assertThat(SpringUtils.defaultIfNull('x', 'A')).isEqualTo('x');
assertThat(SpringUtils.defaultIfNull(1, 2)).isEqualTo(1);
@@ -164,6 +238,7 @@ public class SpringUtilsUnitTests {
@Test
public void defaultIfNullReturnsDefault() {
assertThat(SpringUtils.defaultIfNull(null, false)).isFalse();
assertThat(SpringUtils.defaultIfNull(null, 'A')).isEqualTo('A');
assertThat(SpringUtils.defaultIfNull(null, 2)).isEqualTo(2);
@@ -174,6 +249,7 @@ public class SpringUtilsUnitTests {
@Test
@SuppressWarnings("unchecked")
public void defaultIfNullWithSupplierReturnsValue() {
Supplier<String> mockSupplier = mock(Supplier.class);
assertThat(SpringUtils.defaultIfNull("value", mockSupplier)).isEqualTo("value");
@@ -184,6 +260,7 @@ public class SpringUtilsUnitTests {
@Test
@SuppressWarnings("unchecked")
public void defaultIfNullWithSupplierReturnsSupplierValue() {
Supplier<String> mockSupplier = mock(Supplier.class);
when(mockSupplier.get()).thenReturn("supplier");
@@ -200,6 +277,7 @@ public class SpringUtilsUnitTests {
@Test
public void equalsIgnoreNullIsTrue() {
assertThat(SpringUtils.equalsIgnoreNull(null, null)).isTrue();
assertThat(SpringUtils.equalsIgnoreNull(true, true)).isTrue();
assertThat(SpringUtils.equalsIgnoreNull('x', 'x')).isTrue();
@@ -211,6 +289,7 @@ public class SpringUtilsUnitTests {
@Test
public void equalsIgnoreNullIsFalse() {
assertThat(SpringUtils.equalsIgnoreNull(null, "null")).isFalse();
assertThat(SpringUtils.equalsIgnoreNull(true, false)).isFalse();
assertThat(SpringUtils.equalsIgnoreNull('x', 'X')).isFalse();

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="http://www.springframework.org/schema/gemfire"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
">
<gfe:cache/>
<bean class="org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor"/>
<bean class="org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessorIntegrationTests.BeanProcessingOrderRecordingBeanPostProcessor"/>
<bean id="MockLuceneService" class="org.springframework.data.gemfire.test.mock.GemFireMockObjectsSupport"
factory-method="mockLuceneService">
<constructor-arg index="0" ref="gemfireCache"/>
</bean>
<gfe:partitioned-region id="Books" persistent="false"/>
<gfe:partitioned-region id="Contracts" persistent="false"/>
<gfe:partitioned-region id="People" persistent="false"/>
<gfe:lucene-index id="BookTitleLuceneIndex" lucene-service-ref="MockLuceneService"
fields="title" region-path="/Books"/>
<gfe:lucene-index id="ContractDescriptionLuceneIndex" lucene-service-ref="MockLuceneService"
fields="description" region-path="/Contracts"/>
<gfe:disk-store id="MockDiskStore"/>
</beans>