SGF-741 - Add support for enforcing Lucene Index creation before Region creation.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -102,9 +102,6 @@ public class IndexFactoryBean extends AbstractFactoryBeanSupport<Index> implemen
|
||||
private String indexName;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.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 {@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
|
||||
* @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(typeFilter::test)
|
||||
.orElseGet(() ->
|
||||
Optional.ofNullable(beanDefinition.getFactoryMethodName())
|
||||
.filter(StringUtils::hasText)
|
||||
.filter(it -> beanDefinition instanceof AnnotatedBeanDefinition)
|
||||
.map(it -> ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata())
|
||||
.map(MethodMetadata::getReturnTypeName)
|
||||
.map(typeFilter::test)
|
||||
.orElse(false)
|
||||
);
|
||||
}
|
||||
|
||||
protected boolean isClientCacheBean(BeanDefinition beanDefinition) {
|
||||
return isBeanDefinitionOfType(beanDefinition, ClientCacheFactoryBean.class);
|
||||
}
|
||||
|
||||
protected boolean isClientRegionBean(BeanDefinition beanDefinition) {
|
||||
return isBeanDefinitionOfType(beanDefinition, ClientRegionFactoryBean.class);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.springframework.util.StringUtils;
|
||||
// TODO rename this utility class using a more descriptive, intuitive and meaningful name
|
||||
public abstract class SpringUtils {
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static BeanDefinition addDependsOn(BeanDefinition bean, String... beanNames) {
|
||||
|
||||
List<String> dependsOnList = new ArrayList<>();
|
||||
@@ -54,7 +53,14 @@ public abstract class SpringUtils {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
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) {
|
||||
|
||||
@@ -63,7 +69,6 @@ public abstract class SpringUtils {
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static BeanDefinition setPropertyValue(BeanDefinition beanDefinition,
|
||||
String propertyName, Object propertyValue) {
|
||||
|
||||
@@ -72,57 +77,46 @@ public abstract class SpringUtils {
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static String defaultIfEmpty(String value, String defaultValue) {
|
||||
return (StringUtils.hasText(value) ? value : defaultValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T defaultIfNull(T value, T defaultValue) {
|
||||
return Optional.ofNullable(value).orElse(defaultValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T defaultIfNull(T value, Supplier<T> supplier) {
|
||||
return Optional.ofNullable(value).orElseGet(supplier);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static String dereferenceBean(String beanName) {
|
||||
return String.format("%1$s%2$s", BeanFactory.FACTORY_BEAN_PREFIX, beanName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static boolean equalsIgnoreNull(Object obj1, Object obj2) {
|
||||
return (obj1 == null ? obj2 == null : obj1.equals(obj2));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static boolean nullOrEquals(Object obj1, Object obj2) {
|
||||
return (obj1 == null || obj1.equals(obj2));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static boolean nullSafeEquals(Object obj1, Object obj2) {
|
||||
return (obj1 != null && obj1.equals(obj2));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T safeGetValue(Supplier<T> valueSupplier) {
|
||||
return safeGetValue(valueSupplier, (T) null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T safeGetValue(Supplier<T> valueSupplier, T defaultValue) {
|
||||
return safeGetValue(valueSupplier, (Supplier<T>) () -> defaultValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T safeGetValue(Supplier<T> valueSupplier, Supplier<T> defaultValueSupplier) {
|
||||
return safeGetValue(valueSupplier, (Function<Throwable, T>) exception -> defaultValueSupplier.get());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static <T> T safeGetValue(Supplier<T> valueSupplier, Function<Throwable, T> exceptionHandler) {
|
||||
try {
|
||||
return valueSupplier.get();
|
||||
|
||||
Reference in New Issue
Block a user