diff --git a/build.gradle b/build.gradle
index 00456f14..720be948 100644
--- a/build.gradle
+++ b/build.gradle
@@ -107,13 +107,14 @@ dependencies {
}
// Apache Geode (a.k.a. Pivotal GemFire)
- compile("org.apache.geode:geode-core:$gemfireVersion") {
+ compile("org.apache.geode:geode-core:$geodeVersion") {
exclude group: "org.apache.logging.log4j", module: "log4j-jcl"
exclude group: "org.apache.logging.log4j", module: "log4j-jul"
exclude group: "org.apache.logging.log4j", module: "log4j-slf4j-impl"
}
- compile("org.apache.geode:geode-cq:$gemfireVersion")
- compile("org.apache.geode:geode-wan:$gemfireVersion")
+ compile("org.apache.geode:geode-cq:$geodeVersion")
+ compile("org.apache.geode:geode-lucene:$geodeVersion")
+ compile("org.apache.geode:geode-wan:$geodeVersion")
optional"org.apache.shiro:shiro-spring:$shiroVersion"
runtime("antlr:antlr:$antlrVersion")
diff --git a/gradle.properties b/gradle.properties
index f1087e27..e7133318 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,8 +1,8 @@
antlrVersion=2.7.7
aspectjVersion=1.8.9
-assertjVersion=3.6.1
+assertjVersion=3.6.2
cdiVersion=1.0
-gemfireVersion=1.1.0
+geodeVersion=1.1.0
hamcrestVersion=1.3
jacksonVersion=2.7.6
junitVersion=4.12
diff --git a/pom.xml b/pom.xml
index 341f2998..beab2b85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
SGF
2.7.7
1.3.1
- 3.6.1
+ 3.6.2
1.1.0
1.01
2.5
@@ -98,6 +98,11 @@
geode-cq
${gemfire.version}
+
+ org.apache.geode
+ geode-lucene
+ ${gemfire.version}
+
org.apache.geode
geode-wan
diff --git a/src/main/java/org/springframework/data/gemfire/GemfireOperations.java b/src/main/java/org/springframework/data/gemfire/GemfireOperations.java
index 4a6aa479..21e526f0 100644
--- a/src/main/java/org/springframework/data/gemfire/GemfireOperations.java
+++ b/src/main/java/org/springframework/data/gemfire/GemfireOperations.java
@@ -48,12 +48,12 @@ public interface GemfireOperations {
V putIfAbsent(K key, V value);
+ V remove(K key);
+
V replace(K key, V value);
boolean replace(K key, V oldValue, V newValue);
- V remove(K key);
-
/**
* Executes a GemFire query with the given (optional) parameters and returns the result. Note this method expects the query to return multiple results; for queries that return only one
* element use {@link #findUnique(String, Object...)}.
diff --git a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
index 0b8c6005..5587b0a7 100644
--- a/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
+++ b/src/main/java/org/springframework/data/gemfire/IndexFactoryBean.java
@@ -78,6 +78,10 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
private String indexName;
private String name;
+ /**
+ * @inheritDoc
+ */
+ @Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "The GemFire Cache reference must not be null!");
@@ -251,15 +255,27 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
return null;
}
+ /**
+ * @inheritDoc
+ */
+ @Override
public Index getObject() {
index = (index != null ? index : getExistingIndex(queryService, indexName));
return index;
}
+ /**
+ * @inheritDoc
+ */
+ @Override
public Class> getObjectType() {
return (index != null ? index.getClass() : Index.class);
}
+ /**
+ * @inheritDoc
+ */
+ @Override
public boolean isSingleton() {
return true;
}
@@ -381,7 +397,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
private final String indexName;
- protected IndexWrapper(final QueryService queryService, 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!");
this.queryService = queryService;
@@ -470,7 +486,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
}
@Override
- public boolean equals(final Object obj) {
+ public boolean equals(Object obj) {
if (obj == this) {
return true;
}
@@ -499,5 +515,4 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B
return (index != null ? String.valueOf(index) : getIndexName());
}
}
-
}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneAccessor.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneAccessor.java
new file mode 100644
index 00000000..a0342ef7
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneAccessor.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
+import static org.springframework.data.gemfire.util.SpringUtils.safeGetValue;
+
+import java.util.Optional;
+
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+import org.apache.geode.cache.lucene.LuceneQuery;
+import org.apache.geode.cache.lucene.LuceneQueryException;
+import org.apache.geode.cache.lucene.LuceneQueryFactory;
+import org.apache.geode.cache.lucene.LuceneService;
+import org.apache.geode.cache.lucene.LuceneServiceProvider;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.data.gemfire.search.lucene.support.LuceneOperationsSupport;
+import org.springframework.data.gemfire.util.CacheUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link LuceneAccessor} is an abstract class supporting implementations of the {@link LuceneOperations} interface
+ * encapsulating common functionality necessary to execute Lucene queries.
+ *
+ * @author John Blum
+ * @see org.springframework.beans.factory.InitializingBean
+ * @see org.springframework.data.gemfire.search.lucene.support.LuceneOperationsSupport
+ * @see org.apache.geode.cache.GemFireCache
+ * @see org.apache.geode.cache.Region
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @see org.apache.geode.cache.lucene.LuceneQuery
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see org.apache.geode.cache.lucene.LuceneServiceProvider
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public abstract class LuceneAccessor extends LuceneOperationsSupport implements InitializingBean {
+
+ private GemFireCache gemfireCache;
+
+ private LuceneIndex luceneIndex;
+
+ private LuceneService luceneService;
+
+ private Region, ?> region;
+
+ private String indexName;
+ private String regionPath;
+
+ /**
+ * Constructs an uninitialized instance of {@link LuceneAccessor}.
+ */
+ public LuceneAccessor() {
+ }
+
+ /**
+ * Constructs an instance of the {@link LuceneAccessor} initialized with the given {@link LuceneIndex}
+ * used to perform Lucene queries (searches).
+ *
+ * @param luceneIndex {@link LuceneIndex} used in Lucene queries.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ */
+ public LuceneAccessor(LuceneIndex luceneIndex) {
+ this.luceneIndex = luceneIndex;
+ }
+
+ /**
+ * Constructs an instance of the {@link LuceneAccessor} initialized with the given Lucene index name
+ * and {@link Region} reference upon which Lucene queries are executed.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex} used in Lucene queries.
+ * @param region {@link Region} on which Lucene queries are executed.
+ * @see org.apache.geode.cache.Region
+ */
+ public LuceneAccessor(String indexName, Region, ?> region) {
+ this.indexName = indexName;
+ this.region = region;
+ }
+
+ /**
+ * Constructs an instance of the {@link LuceneAccessor} initialized with the given Lucene index name
+ * and {@link Region} reference upon which Lucene queries are executed.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex} used in Lucene queries.
+ * @param regionPath {@link String} containing the name of the {@link Region} on which Lucene queries are executed.
+ */
+ public LuceneAccessor(String indexName, String regionPath) {
+ this.indexName = indexName;
+ this.regionPath = regionPath;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ this.gemfireCache = resolveCache();
+ this.luceneService = resolveLuceneService();
+ this.indexName = resolveIndexName();
+ this.regionPath = resolveRegionPath();
+ }
+
+ /**
+ * Creates an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ *
+ * @return an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see #getLuceneService()
+ */
+ public LuceneQueryFactory createLuceneQueryFactory() {
+ return resolveLuceneService().createLuceneQueryFactory();
+ }
+
+ /**
+ * Creates an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ *
+ * @param projectionFields {@link String[]} containing the fields of the object to project.
+ * @return an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see #createLuceneQueryFactory(int, int, String...)
+ */
+ public LuceneQueryFactory createLuceneQueryFactory(String... projectionFields) {
+ return createLuceneQueryFactory(DEFAULT_RESULT_LIMIT, DEFAULT_PAGE_SIZE, projectionFields);
+ }
+
+ /**
+ * Creates an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ *
+ * @param resultLimit limit to the number of results returned by the query.
+ * @param projectionFields {@link String[]} containing the fields of the object to project.
+ * @return an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see #createLuceneQueryFactory(int, int, String...)
+ */
+ public LuceneQueryFactory createLuceneQueryFactory(int resultLimit, String... projectionFields) {
+ return createLuceneQueryFactory(resultLimit, DEFAULT_PAGE_SIZE, projectionFields);
+ }
+
+ /**
+ * Creates an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ *
+ * @param resultLimit limit to the number of results returned by the query.
+ * @param pageSize number of results appearing on a single page.
+ * @param projectionFields {@link String[]} containing the fields of the object to project.
+ * @return an instance of the {@link LuceneQueryFactory} to create and execute {@link LuceneQuery Lucene queries}.
+ * @see #createLuceneQueryFactory()
+ */
+ @SuppressWarnings("deprecation")
+ public LuceneQueryFactory createLuceneQueryFactory(int resultLimit, int pageSize, String... projectionFields) {
+ return createLuceneQueryFactory().setResultLimit(resultLimit).setPageSize(pageSize)
+ .setProjectionFields(nullSafeArray(projectionFields, String.class));
+ }
+
+ /**
+ * Resolves a reference to the {@link GemFireCache}.
+ *
+ * @return a reference to the single instance of the {@link GemFireCache}.
+ * @see org.springframework.data.gemfire.util.CacheUtils#resolveGemFireCache()
+ * @see org.apache.geode.cache.GemFireCache
+ * @see #getCache()
+ */
+ protected GemFireCache resolveCache() {
+ return Optional.ofNullable(getCache()).orElseGet(CacheUtils::resolveGemFireCache);
+ }
+
+ /**
+ * Resolves the {@link LuceneService} used by this data access object to perform Lucene queries.
+ *
+ * @return a reference to the {@link GemFireCache} {@link LuceneService}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see #getLuceneService()
+ * @see #resolveCache()
+ * @see #resolveLuceneService(GemFireCache)
+ */
+ protected LuceneService resolveLuceneService() {
+ return Optional.ofNullable(getLuceneService()).orElseGet(() -> resolveLuceneService(resolveCache()));
+ }
+
+ /**
+ * Resolves the {@link LuceneService} used by this data access object to perform Lucene queries.
+ *
+ * @param gemfireCache {@link GemFireCache} used to resolve the {@link LuceneService}.
+ * @return a reference to the {@link GemFireCache} {@link LuceneService}.
+ * @throws IllegalArgumentException if {@link GemFireCache} is {@literal null}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ protected LuceneService resolveLuceneService(GemFireCache gemfireCache) {
+ Assert.notNull(gemfireCache, "Cache reference was not properly configured");
+ return LuceneServiceProvider.get(gemfireCache);
+ }
+
+ /**
+ * Resolves the name of the {@link LuceneIndex} required in the Lucene data access, query operations
+ * when a {@link LuceneIndex} is not specifically provided.
+ *
+ * @return the name of the resolve {@link LuceneIndex}.
+ * @throws IllegalStateException if the name of the {@link LuceneIndex} cannot be resolved.
+ * @see org.apache.geode.cache.lucene.LuceneIndex#getName()
+ * @see #getIndexName()
+ * @see #getLuceneIndex()
+ */
+ protected String resolveIndexName() {
+ String resolvedIndexName = Optional.ofNullable(getIndexName())
+ .orElseGet(() -> safeGetValue(() -> getLuceneIndex().getName()));
+
+ Assert.state(StringUtils.hasText(resolvedIndexName),
+ "The name of the Lucene Index could not be resolved");
+
+ return resolvedIndexName;
+ }
+
+ /**
+ * Resolves the fully-qualified pathname of the {@link Region} to which the Lucene data access, query operations
+ * are performed and the {@link LuceneIndex} is applied, when a {@link String region path}
+ * is not specifically provided.
+ *
+ * @return a {@link String} containing the fully-qualified pathname of the {@link Region} on which the Lucene
+ * data access, query operations are performed and the {@link LuceneIndex} is applied.
+ * @throws IllegalStateException if the fully-qualified pathname of the {@link Region} cannot be resolved.
+ * @see #getRegionPath()
+ * @see #getRegion()
+ */
+ protected String resolveRegionPath() {
+ String resolvedRegionPath = Optional.ofNullable(getRegionPath())
+ .orElseGet(() -> safeGetValue(() -> getRegion().getFullPath()));
+
+ Assert.state(StringUtils.hasText(resolvedRegionPath), "Region path could not be resolved");
+
+ return resolvedRegionPath;
+ }
+
+ /**
+ * Sets a reference to the {@link GemFireCache}.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param gemfireCache {@link GemFireCache} reference.
+ * @return this {@link LuceneAccessor}.
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ @SuppressWarnings("unchecked")
+ public T setCache(GemFireCache gemfireCache) {
+ this.gemfireCache = gemfireCache;
+ return (T) this;
+ }
+
+ /**
+ * Returns a reference to the {@link GemFireCache}.
+ *
+ * @return a reference to the {@link GemFireCache}.
+ * @see org.apache.geode.cache.GemFireCache
+ * @see #resolveCache()
+ */
+ protected GemFireCache getCache() {
+ return this.gemfireCache;
+ }
+
+ /**
+ * Sets the name of the {@link LuceneIndex} used in Lucene queries.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @return this {@link LuceneAccessor}.
+ * @see #setLuceneIndex(LuceneIndex)
+ */
+ @SuppressWarnings("unchecked")
+ public T setIndexName(String indexName) {
+ this.indexName = indexName;
+ return (T) this;
+ }
+
+ /**
+ * Returns the name of the {@link LuceneIndex} used in Lucene queries.
+ *
+ * @return a {@link String} containing the name of the {@link LuceneIndex}.
+ * @see #resolveIndexName()
+ * @see #getLuceneIndex()
+ */
+ public String getIndexName() {
+ return this.indexName;
+ }
+
+ /**
+ * Sets a reference to the {@link LuceneIndex} used in Lucene queries.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param luceneIndex {@link LuceneIndex} used in Lucene data access, query operations.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @return this {@link LuceneAccessor}.
+ * @see #setIndexName(String)
+ */
+ @SuppressWarnings("unchecked")
+ public T setLuceneIndex(LuceneIndex luceneIndex) {
+ this.luceneIndex = luceneIndex;
+ return (T) this;
+ }
+
+ /**
+ * Returns a reference to the {@link LuceneIndex} used in Lucene queries.
+ *
+ * @return a {@link LuceneIndex} used in Lucene data access, query operations.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @see #resolveIndexName()
+ * @see #getIndexName()
+ */
+ public LuceneIndex getLuceneIndex() {
+ return this.luceneIndex;
+ }
+
+ /**
+ * Sets a reference to the {@link LuceneService} used to perform Lucene query data access operations.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param luceneService {@link LuceneService} used to perform Lucene queries.
+ * @return this {@link LuceneAccessor}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ */
+ @SuppressWarnings("unchecked")
+ public T setLuceneService(LuceneService luceneService) {
+ this.luceneService = luceneService;
+ return (T) this;
+ }
+
+ /**
+ * Returns a reference to the {@link LuceneService} used to perform Lucene query data access operations.
+ *
+ * @return a reference to the {@link LuceneService} used to perform Lucene queries.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ */
+ protected LuceneService getLuceneService() {
+ return this.luceneService;
+ }
+
+ /**
+ * Sets a reference to the {@link Region} used to specify Lucene queries.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param region {@link Region} used to specify Lucene queries.
+ * @return this {@link LuceneAccessor}.
+ * @see org.apache.geode.cache.Region
+ * @see #setRegionPath(String)
+ */
+ @SuppressWarnings("unchecked")
+ public T setRegion(Region, ?> region) {
+ this.region = region;
+ return (T) this;
+ }
+
+ /**
+ * Returns a reference to the {@link Region} used to specify Lucene queries.
+ *
+ * @return a {@link Region} used to specify Lucene queries.
+ * @see org.apache.geode.cache.Region
+ * @see #resolveRegionPath()
+ * @see #getRegionPath()
+ */
+ public Region, ?> getRegion() {
+ return this.region;
+ }
+
+ /**
+ * Sets a fully-qualified pathname to the {@link Region} used to specify Lucene queries.
+ *
+ * @param {@link Class} type of the {@link LuceneAccessor}.
+ * @param regionPath {@link String} containing the fully-qualified pathname to the {@link Region}
+ * used to specify Lucene queries.
+ * @return this {@link LuceneAccessor}.
+ * @see #setRegion(Region)
+ */
+ @SuppressWarnings("unchecked")
+ public T setRegionPath(String regionPath) {
+ this.regionPath = regionPath;
+ return (T) this;
+ }
+
+ /**
+ * Returns a fully-qualified pathname to the {@link Region} used to specify Lucene queries.
+ *
+ * @return a {@link String} containing the fully-qualified pathname to the {@link Region}
+ * used to specify Lucene queries.
+ * @see #resolveRegionPath()
+ * @see #getRegion()
+ */
+ public String getRegionPath() {
+ return this.regionPath;
+ }
+
+ /* (non-Javadoc) */
+ protected T doFind(LuceneQueryExecutor queryExecutor, Object query, String regionPath, String indexName) {
+ try {
+ return queryExecutor.execute();
+ }
+ catch (LuceneQueryException e) {
+ throw new DataRetrievalFailureException(String.format(
+ "Failed to execute Lucene Query [%1$s] on Region [%2$s] with Lucene Index [%3$s]",
+ query, regionPath, indexName), e);
+ }
+ }
+
+ /* (non-Javadoc) */
+ @FunctionalInterface
+ protected interface LuceneQueryExecutor {
+ T execute() throws LuceneQueryException;
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java
new file mode 100644
index 00000000..ebc08979
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
+import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList;
+import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap;
+import static org.springframework.util.CollectionUtils.isEmpty;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+import org.apache.geode.cache.lucene.LuceneService;
+import org.apache.geode.cache.lucene.LuceneServiceProvider;
+import org.apache.lucene.analysis.Analyzer;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.data.gemfire.util.CacheUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Spring {@link FactoryBean} used to construct {@link LuceneIndex Lucene Indexes} on application domain object fields.
+ *
+ * @author John Blum
+ * @see org.springframework.beans.factory.BeanNameAware
+ * @see org.springframework.beans.factory.DisposableBean
+ * @see org.springframework.beans.factory.FactoryBean
+ * @see org.springframework.beans.factory.InitializingBean
+ * @see org.apache.geode.cache.GemFireCache
+ * @see org.apache.geode.cache.Region
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see org.apache.geode.cache.lucene.LuceneServiceProvider
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public class LuceneIndexFactoryBean implements FactoryBean,
+ BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean {
+
+ protected static final boolean DEFAULT_DESTROY = false;
+
+ private boolean destroy = DEFAULT_DESTROY;
+
+ private BeanFactory beanFactory;
+
+ private GemFireCache gemfireCache;
+
+ private List fields;
+
+ private LuceneIndex luceneIndex;
+
+ private LuceneService luceneService;
+
+ private Map fieldAnalyzers;
+
+ private Region, ?> region;
+
+ private String indexName;
+ private String regionPath;
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ String indexName = getIndexName();
+
+ this.gemfireCache = resolveCache();
+ this.luceneService = resolveLuceneService();
+ this.regionPath = resolveRegionPath();
+
+ setLuceneIndex(createIndex(indexName, getRegionPath()));
+ }
+
+ /**
+ * Creates a {@link LuceneIndex} with the given {@code indexName} on the {@link GemFireCache} {@link Region}
+ * identified by the given {@code regionPath}.
+ *
+ * @param indexName {@link String} containing the name for the {@link LuceneIndex}.
+ * @param regionPath {@link String} containing the fully-qualified pathname to
+ * the {@link GemFireCache} {@link Region}.
+ * @return a new instance of {@link LuceneIndex} with the given {@code indexName} on the named {@link Region}.
+ * @see org.apache.geode.cache.lucene.LuceneService#createIndex(String, String, Map)
+ * @see org.apache.geode.cache.lucene.LuceneService#createIndex(String, String, String...)
+ * @see org.apache.geode.cache.lucene.LuceneService#getIndex(String, String)
+ * @see #resolveLuceneService()
+ * @see #getFieldAnalyzers()
+ * @see #getFields()
+ * @see #resolveFields(List)
+ */
+ protected LuceneIndex createIndex(String indexName, String regionPath) {
+ LuceneService luceneService = resolveLuceneService();
+
+ Map fieldAnalyzers = getFieldAnalyzers();
+
+ if (isEmpty(fieldAnalyzers)) {
+ luceneService.createIndex(indexName, regionPath, asArray(resolveFields(getFields())));
+ }
+ else {
+ luceneService.createIndex(indexName, regionPath, fieldAnalyzers);
+ }
+
+ return luceneService.getIndex(indexName, regionPath);
+ }
+
+ /**
+ * Converts the {@link List} of {@link String Strings} into an {@link String[]} array.
+ *
+ * @param list {@link List} to convert into a typed array.
+ * @return a {@link String[]} array for the {@link List} of {@link String Strings}.
+ * @see java.util.List#toArray(Object[])
+ */
+ private String[] asArray(List list) {
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void destroy() throws Exception {
+ LuceneIndex luceneIndex = getObject();
+
+ if (isLuceneIndexDestroyable(luceneIndex)) {
+ resolveLuceneService().destroyIndex(luceneIndex);
+ }
+ }
+
+ /**
+ * Determine whether the given {@link LuceneIndex} created by this {@link FactoryBean} is destroyable.
+ *
+ * @param luceneIndex {@link LuceneIndex} subject to destruction.
+ * @return a boolean value indicating whether the given {@link LuceneIndex} created by this {@link FactoryBean}
+ * is destroyable.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @see #isDestroy()
+ */
+ protected boolean isLuceneIndexDestroyable(LuceneIndex luceneIndex) {
+ return (luceneIndex != null && isDestroy());
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public LuceneIndex getObject() throws Exception {
+ if (this.luceneIndex == null) {
+ setLuceneIndex(Optional.ofNullable(resolveLuceneService())
+ .map((luceneService) -> luceneService.getIndex(getIndexName(), resolveRegionPath()))
+ .orElse(null));
+ }
+
+ return this.luceneIndex;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Class> getObjectType() {
+ return Optional.ofNullable(this.luceneIndex).>map(LuceneIndex::getClass).orElse(LuceneIndex.class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * Resolves a reference to the {@link GemFireCache}.
+ *
+ * @return a reference to the single instance of the {@link GemFireCache}.
+ * @see org.springframework.data.gemfire.util.CacheUtils#resolveGemFireCache()
+ * @see org.apache.geode.cache.GemFireCache
+ * @see #getCache()
+ */
+ protected GemFireCache resolveCache() {
+ return Optional.ofNullable(getCache()).orElseGet(CacheUtils::resolveGemFireCache);
+ }
+
+ /**
+ * Resolves the {@link List} of fields on the object to index.
+ *
+ * @param fields {@link List} of fields to evaluate.
+ * @return a resolve {@link List} of object fields to index.
+ */
+ protected List resolveFields(List fields) {
+ return (!isEmpty(fields) ? fields : Collections.singletonList(LuceneService.REGION_VALUE_FIELD));
+ }
+
+ /**
+ * Resolves the {@link LuceneService} used by this {@link FactoryBean} to create the {@link LuceneIndex}.
+ *
+ * @return a reference to the {@link GemFireCache}, {@link LuceneService}.
+ * @see org.springframework.beans.factory.BeanFactory#getBean(Class)
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see #getBeanFactory()
+ * @see #getLuceneService()
+ * @see #resolveCache()
+ * @see #resolveLuceneService(GemFireCache)
+ */
+ protected LuceneService resolveLuceneService() {
+ return Optional.ofNullable(getLuceneService()).orElseGet(() ->
+ Optional.ofNullable(getBeanFactory()).map(beanFactory -> {
+ try {
+ return beanFactory.getBean(LuceneService.class);
+ }
+ catch (BeansException ignore) {
+ return null;
+ }
+ }).orElseGet(() -> resolveLuceneService(resolveCache())));
+ }
+
+ /**
+ * Resolves the {@link LuceneService} used by this {@link FactoryBean} to create the {@link LuceneIndex}.
+ *
+ * @param gemfireCache {@link GemFireCache} used to resolve the {@link LuceneService}.
+ * @return a reference to the {@link GemFireCache} {@link LuceneService}.
+ * @throws IllegalArgumentException if {@link GemFireCache} is {@literal null}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ protected LuceneService resolveLuceneService(GemFireCache gemfireCache) {
+ Assert.notNull(gemfireCache, "A reference to the GemFireCache was not properly configured");
+ return LuceneServiceProvider.get(gemfireCache);
+ }
+
+ /**
+ * Attempts to resolve the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex} will be created.
+ *
+ * @return a reference to the {@link GemFireCache} {@link Region} on which he {@link LuceneIndex} will be created.
+ * Returns {@literal null} if the {@link Region} cannot be resolved.
+ * @see org.apache.geode.cache.RegionService#getRegion(String)
+ * @see org.apache.geode.cache.Region
+ * @see #resolveCache()
+ * @see #getRegionPath()
+ */
+ protected Region, ?> resolveRegion() {
+ return Optional.ofNullable(getRegion()).orElseGet(() -> {
+ GemFireCache cache = resolveCache();
+ String regionPath = getRegionPath();
+
+ return (cache != null && StringUtils.hasText(regionPath) ? cache.getRegion(regionPath) : null);
+ });
+ }
+
+ /**
+ * Resolves the fully-qualified pathname of the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex}
+ * will be created.
+ *
+ * @return a {@link String} containing the fully-qualified pathname of the {@link GemFireCache} {@link Region}
+ * on which the {@link LuceneIndex} will be created.
+ * @throws IllegalStateException if the {@link Region} pathname could not resolved.
+ * @see #resolveRegion()
+ * @see #getRegionPath()
+ */
+ protected String resolveRegionPath() {
+ String regionPath = Optional.ofNullable(resolveRegion())
+ .map(Region::getFullPath).orElseGet(this::getRegionPath);
+
+ Assert.state(StringUtils.hasText(regionPath), "Either Region or regionPath must be specified");
+
+ return regionPath;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Returns a reference to the containing Spring {@link BeanFactory} if set.
+ *
+ * @return a reference to the containing Spring {@link BeanFactory} if set.
+ * @see org.springframework.beans.factory.BeanFactory
+ */
+ protected BeanFactory getBeanFactory() {
+ return this.beanFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void setBeanName(String name) {
+ setIndexName(name);
+ }
+
+ /**
+ * Sets a reference to the {@link GemFireCache}.
+ *
+ * @param gemfireCache {@link GemFireCache} reference.
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ public void setCache(GemFireCache gemfireCache) {
+ this.gemfireCache = gemfireCache;
+ }
+
+ /**
+ * Returns a reference to the {@link GemFireCache}.
+ *
+ * @return a reference to the {@link GemFireCache}.
+ * @see org.apache.geode.cache.GemFireCache
+ * @see #resolveCache()
+ */
+ protected GemFireCache getCache() {
+ return this.gemfireCache;
+ }
+
+ /**
+ * Sets whether to destroy the {@link LuceneIndex} on shutdown.
+ *
+ * @param destroy boolean value indicating whether to destroy the {@link LuceneIndex} on shutdown.
+ */
+ public void setDestroy(boolean destroy) {
+ this.destroy = destroy;
+ }
+
+ /**
+ * Determines whether the {@link LuceneIndex} will be destroyed on shutdown.
+ *
+ * @return a boolean value indicating whether the {@link LuceneIndex} will be destroyed on shutdown.
+ * @see org.springframework.beans.factory.DisposableBean#destroy()
+ */
+ protected boolean isDestroy() {
+ return this.destroy;
+ }
+
+ /**
+ * Set a {@link Map} of application domain object field names to {@link Analyzer Analyzers} used in the construction
+ * of the {@link LuceneIndex Lucene Indexes} for each field.
+ *
+ * @param fieldAnalyzers {@link Map} of fields names to {@link Analyzer Analyzers}.
+ * @see org.apache.lucene.analysis.Analyzer
+ * @see java.util.Map
+ */
+ public void setFieldAnalyzers(Map fieldAnalyzers) {
+ this.fieldAnalyzers = fieldAnalyzers;
+ }
+
+ /**
+ * Returns a {@link Map} of application domain object field names to {@link Analyzer Analyzers} used in
+ * the construction of the {@link LuceneIndex Lucene Indexes} for each field.
+ *
+ * @return a {@link Map} of fields names to {@link Analyzer Analyzers}.
+ * @see org.apache.lucene.analysis.Analyzer
+ * @see java.util.Map
+ * @see #getFields()
+ */
+ protected Map getFieldAnalyzers() {
+ return nullSafeMap(this.fieldAnalyzers);
+ }
+
+ /**
+ * Sets the application domain object fields to index.
+ *
+ * @param fields array of {@link String Strings} containing the names of the object fields ot index.
+ * @see #setFields(List)
+ */
+ public void setFields(String... fields) {
+ setFields(Arrays.asList(nullSafeArray(fields, String.class)));
+ }
+
+ /**
+ * Sets the application domain object fields to index.
+ *
+ * @param fields {@link List} of {@link String Strings} containing the names of the object fields ot index.
+ */
+ public void setFields(List fields) {
+ this.fields = fields;
+ }
+
+ /**
+ * Returns a {@link List} of application domain object fields to be indexed.
+ *
+ * @return a {@link List} of application domain object fields to be indexed.
+ * @see #getFieldAnalyzers()
+ * @see java.util.List
+ */
+ protected List getFields() {
+ return nullSafeList(this.fields);
+ }
+
+ /**
+ * Sets the name of the {@link LuceneIndex} as identified in the {@link GemFireCache}.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @see #setBeanName(String)
+ */
+ public void setIndexName(String indexName) {
+ this.indexName = indexName;
+ }
+
+ /**
+ * Returns the name of the {@link LuceneIndex} as identified in the {@link GemFireCache}.
+ *
+ * @return a {@link String} containing the name of the {@link LuceneIndex}.
+ * @throws IllegalStateException if the {@code indexName} was not specified.
+ */
+ protected String getIndexName() {
+ Assert.state(StringUtils.hasText(this.indexName), "indexName was not properly initialized");
+ return this.indexName;
+ }
+
+ /**
+ * Sets the {@link LuceneIndex} as the index created by this {@link FactoryBean}.
+ *
+ * This method is for testing purposes only!
+ *
+ * @param luceneIndex {@link LuceneIndex} created by this {@link FactoryBean}.
+ * @return this {@link LuceneIndexFactoryBean}.
+ * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ */
+ protected LuceneIndexFactoryBean setLuceneIndex(LuceneIndex luceneIndex) {
+ this.luceneIndex = luceneIndex;
+ return this;
+ }
+
+ /**
+ * Sets a reference to the {@link LuceneService} used by this {@link FactoryBean} to create the {@link LuceneIndex}.
+ *
+ * @param luceneService {@link LuceneService} used to create the {@link LuceneIndex}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ */
+ public void setLuceneService(LuceneService luceneService) {
+ this.luceneService = luceneService;
+ }
+
+ /**
+ * Returns a reference to the {@link LuceneService} used by this {@link FactoryBean} to create
+ * the {@link LuceneIndex}.
+ *
+ * @return a reference to the {@link LuceneService} used to create the {@link LuceneIndex}.
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see #resolveLuceneService()
+ */
+ protected LuceneService getLuceneService() {
+ return this.luceneService;
+ }
+
+ /**
+ * Sets a reference to the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex} will be created.
+ *
+ * @param region {@link Region} on which the {@link LuceneIndex} will be created.
+ * @see org.apache.geode.cache.Region
+ * @see #setRegionPath(String)
+ */
+ public void setRegion(Region, ?> region) {
+ this.region = region;
+ }
+
+ /**
+ * Returns a reference to the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex} will be created.
+ *
+ * @return a reference to the {@link Region} on which the {@link LuceneIndex} will be created.
+ * @see org.apache.geode.cache.Region
+ * @see #getRegionPath()
+ * @see #resolveRegion()
+ */
+ protected Region, ?> getRegion() {
+ return this.region;
+ }
+
+ /**
+ * Sets the fully-qualified pathname to the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex}
+ * will be created.
+ *
+ * @param pathname {@link String} containing the fully-qualified pathname to the {@link GemFireCache} {@link Region}
+ * on which the {@link LuceneIndex} will be created.
+ * @see #setRegion(Region)
+ */
+ public void setRegionPath(String pathname) {
+ this.regionPath = pathname;
+ }
+
+ /**
+ * Returns the fully-qualified pathname to the {@link GemFireCache} {@link Region} on which the {@link LuceneIndex}
+ * will be created.
+ *
+ * @return a {@link String} containing the fully-qualified pathname to the {@link GemFireCache} {@link Region}
+ * on which the {@link LuceneIndex} will be created.
+ * @see #getRegion()
+ */
+ protected String getRegionPath() {
+ return this.regionPath;
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneOperations.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneOperations.java
new file mode 100644
index 00000000..ffa7849c
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneOperations.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.geode.cache.lucene.LuceneQueryFactory;
+import org.apache.geode.cache.lucene.LuceneQueryProvider;
+import org.apache.geode.cache.lucene.LuceneResultStruct;
+import org.apache.geode.cache.lucene.PageableLuceneQueryResults;
+
+/**
+ * The {@link LuceneOperations} interface defines a contract for implementations defining data access operations
+ * using Lucene queries.
+ *
+ * @author John Blum
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see org.apache.geode.cache.lucene.LuceneQueryProvider
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see org.apache.geode.cache.lucene.PageableLuceneQueryResults
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public interface LuceneOperations {
+
+ int DEFAULT_PAGE_SIZE = LuceneQueryFactory.DEFAULT_PAGESIZE;
+ int DEFAULT_RESULT_LIMIT = LuceneQueryFactory.DEFAULT_LIMIT;
+
+ /**
+ * Executes the given Lucene {@link String query}.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link List} of {@link LuceneResultStruct} containing the query results.
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see #query(String, String, int, String...)
+ * @see java.util.List
+ */
+ default List> query(String query, String defaultField,
+ String... projectionFields) {
+
+ return query(query, defaultField, DEFAULT_RESULT_LIMIT, projectionFields);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} with a limit on the number of results returned.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @param resultLimit limit on the number of query results to return.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link List} of {@link LuceneResultStruct} containing the query results.
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see java.util.List
+ */
+ List> query(String query, String defaultField,
+ int resultLimit, String... projectionFields);
+
+ /**
+ * Executes the given Lucene {@link String query} with a limit on the number of results returned
+ * along with a page size for paging.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @param resultLimit limit on the number of query results to return.
+ * @param pageSize number of results per page.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link PageableLuceneQueryResults} data structure containing the results of the Lucene query.
+ * @see org.apache.geode.cache.lucene.PageableLuceneQueryResults
+ */
+ PageableLuceneQueryResults query(String query, String defaultField,
+ int resultLimit, int pageSize, String... projectionFields);
+
+ /**
+ * Executes the given Lucene {@link String query}.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link List} of {@link LuceneResultStruct} containing the query results.
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see #query(LuceneQueryProvider, int, String...)
+ * @see java.util.List
+ */
+ default List> query(LuceneQueryProvider queryProvider,
+ String... projectionFields) {
+
+ return query(queryProvider, DEFAULT_RESULT_LIMIT, projectionFields);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} with a limit on the number of results returned.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @param resultLimit limit on the number of query results to return.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link List} of {@link LuceneResultStruct} containing the query results.
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see java.util.List
+ */
+ List> query(LuceneQueryProvider queryProvider,
+ int resultLimit, String... projectionFields);
+
+ /**
+ * Executes the given Lucene {@link String query} with a limit on the number of results returned
+ * along with a page size for paging.
+ *
+ * @param {@link Class} type of the key.
+ * @param {@link Class} type of the value.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @param resultLimit limit on the number of query results to return.
+ * @param pageSize number of results per page.
+ * @param projectionFields array of {@link String} values specifying the query projection.
+ * @return a {@link PageableLuceneQueryResults} data structure containing the results of the Lucene query.
+ * @see org.apache.geode.cache.lucene.PageableLuceneQueryResults
+ */
+ PageableLuceneQueryResults query(LuceneQueryProvider queryProvider,
+ int resultLimit, int pageSize, String... projectionFields);
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of keys
+ * matching the query clause/predicate.
+ *
+ * The number of keys returned is limited by {@link LuceneQueryFactory#DEFAULT_LIMIT}.
+ *
+ * @param {@link Class} type of the key.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @return a {@link Collection} of keys matching the Lucene query clause (predicate).
+ * @see #queryForKeys(String, String, int)
+ * @see java.util.Collection
+ */
+ default Collection queryForKeys(String query, String defaultField) {
+ return queryForKeys(query, defaultField, DEFAULT_RESULT_LIMIT);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of keys
+ * matching the query clause/predicate with a limit on the number of keys returned.
+ *
+ * @param {@link Class} type of the key.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @param resultLimit limit on the number of keys returned.
+ * @return a {@link Collection} of keys matching the Lucene query clause (predicate).
+ * @see #queryForKeys(String, String, int)
+ * @see java.util.Collection
+ */
+ Collection queryForKeys(String query, String defaultField, int resultLimit);
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of keys
+ * matching the query clause/predicate.
+ *
+ * The number of keys returned is limited by {@link LuceneQueryFactory#DEFAULT_LIMIT}.
+ *
+ * @param {@link Class} type of the key.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @return a {@link Collection} of keys matching the Lucene query clause (predicate).
+ * @see #queryForKeys(String, String, int)
+ * @see java.util.Collection
+ */
+ default Collection queryForKeys(LuceneQueryProvider queryProvider) {
+ return queryForKeys(queryProvider, DEFAULT_RESULT_LIMIT);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of keys
+ * matching the query clause/predicate with a limit on the number of keys returned.
+ *
+ * @param {@link Class} type of the key.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @param resultLimit limit on the number of keys returned.
+ * @return a {@link Collection} of keys matching the Lucene query clause (predicate).
+ * @see #queryForKeys(String, String, int)
+ * @see java.util.Collection
+ */
+ Collection queryForKeys(LuceneQueryProvider queryProvider, int resultLimit);
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of values
+ * matching the query clause/predicate.
+ *
+ * The number of values returned is limited by {@link LuceneQueryFactory#DEFAULT_LIMIT}.
+ *
+ * @param {@link Class} type of the value.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @return a {@link Collection} of values matching Lucene query clause (predicate).
+ * @see #queryForValues(String, String, int)
+ * @see java.util.Collection
+ */
+ default Collection queryForValues(String query, String defaultField) {
+ return queryForValues(query, defaultField, DEFAULT_RESULT_LIMIT);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of values
+ * matching the query clause/predicate.
+ *
+ * @param {@link Class} type of the value.
+ * @param query {@link String} containing the Lucene query to execute.
+ * @param defaultField {@link String} specifying the default field used in Lucene queries when a field
+ * is not explicitly defined in the Lucene query clause.
+ * @param resultLimit limit on the number of values returned.
+ * @return a {@link Collection} of values matching Lucene query clause (predicate).
+ * @see #queryForValues(String, String, int)
+ * @see java.util.Collection
+ */
+ Collection queryForValues(String query, String defaultField, int resultLimit);
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of values
+ * matching the query clause/predicate.
+ *
+ * The number of values returned is limited by {@link LuceneQueryFactory#DEFAULT_LIMIT}.
+ *
+ * @param {@link Class} type of the value.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @return a {@link Collection} of values matching Lucene query clause (predicate).
+ * @see #queryForValues(String, String, int)
+ * @see java.util.Collection
+ */
+ default Collection queryForValues(LuceneQueryProvider queryProvider) {
+ return queryForValues(queryProvider, DEFAULT_RESULT_LIMIT);
+ }
+
+ /**
+ * Executes the given Lucene {@link String query} returning a {@link Collection} of values
+ * matching the query clause/predicate.
+ *
+ * @param {@link Class} type of the value.
+ * @param queryProvider {@link LuceneQueryProvider} is a provider implementation supplying the Lucene query
+ * to execute as well as de/serialize to distribute across the cluster.
+ * @param resultLimit limit on the number of values returned.
+ * @return a {@link Collection} of values matching Lucene query clause (predicate).
+ * @see #queryForValues(String, String, int)
+ * @see java.util.Collection
+ */
+ Collection queryForValues(LuceneQueryProvider queryProvider, int resultLimit);
+
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBean.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBean.java
new file mode 100644
index 00000000..7a1149ff
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBean.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import java.util.Optional;
+
+import org.apache.geode.cache.GemFireCache;
+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.util.Assert;
+
+/**
+ * Spring {@link FactoryBean} used to get an instance of the {@link GemFireCache} {@link LuceneService}.
+ *
+ * @author John Blum
+ * @see org.apache.geode.cache.GemFireCache
+ * @see org.apache.geode.cache.lucene.LuceneService
+ * @see org.apache.geode.cache.lucene.LuceneServiceProvider
+ * @see org.springframework.beans.factory.FactoryBean
+ * @see org.springframework.beans.factory.InitializingBean
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public class LuceneServiceFactoryBean implements FactoryBean, InitializingBean {
+
+ private GemFireCache gemfireCache;
+
+ private LuceneService luceneService;
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ GemFireCache gemfireCache = getCache();
+
+ Assert.state(gemfireCache != null,
+ "A reference to the GemFireCache was not properly configured");
+
+ this.luceneService = resolveLuceneService(gemfireCache);
+ }
+
+ /**
+ * Attempts to resolve the Singleton instance of the {@link GemFireCache} {@link LuceneService}
+ * from given the {@link GemFireCache}.
+ *
+ * @param gemFireCache {@link GemFireCache} used to resolve the {@link LuceneService} instance.
+ * @return a single instance of the GemFire {@link LuceneService}.
+ * @see org.apache.geode.cache.GemFireCache
+ * @see org.apache.geode.cache.lucene.LuceneService
+ */
+ protected LuceneService resolveLuceneService(GemFireCache gemFireCache) {
+ return LuceneServiceProvider.get(gemfireCache);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public LuceneService getObject() throws Exception {
+ return this.luceneService;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Class> getObjectType() {
+ return Optional.ofNullable(this.luceneService).>map(LuceneService::getClass).orElse(LuceneService.class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * Sets a reference to the single {@link GemFireCache} instance.
+ *
+ * @param gemfireCache {@link GemFireCache} reference.
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ public void setCache(GemFireCache gemfireCache) {
+ this.gemfireCache = gemfireCache;
+ }
+
+ /**
+ * Returns a reference to the single {@link GemFireCache} instance.
+ *
+ * @return a reference to the single {@link GemFireCache} instance.
+ * @see org.apache.geode.cache.GemFireCache
+ */
+ protected GemFireCache getCache() {
+ return this.gemfireCache;
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneTemplate.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneTemplate.java
new file mode 100644
index 00000000..a62da561
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneTemplate.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+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.LuceneResultStruct;
+import org.apache.geode.cache.lucene.PageableLuceneQueryResults;
+import org.springframework.data.gemfire.search.lucene.support.LuceneAccessorSupport;
+
+/**
+ * {@link LuceneTemplate} is a Lucene data access operations class encapsulating functionality
+ * for performing Lucene queries and other Lucene data access operations.
+ *
+ * @author John Blum
+ * @see org.springframework.data.gemfire.search.lucene.LuceneAccessor
+ * @see org.springframework.data.gemfire.search.lucene.LuceneOperations
+ * @see org.apache.geode.cache.Region
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ * @see org.apache.geode.cache.lucene.LuceneQuery
+ * @see org.apache.geode.cache.lucene.LuceneQueryFactory
+ * @see org.apache.geode.cache.lucene.LuceneQueryProvider
+ * @see org.apache.geode.cache.lucene.LuceneResultStruct
+ * @see org.apache.geode.cache.lucene.PageableLuceneQueryResults
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public class LuceneTemplate extends LuceneAccessorSupport implements LuceneOperations {
+
+ /**
+ * Constructs an uninitialized instance of {@link LuceneTemplate}.
+ */
+ public LuceneTemplate() {
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneTemplate} initialized with the given {@link LuceneIndex}.
+ *
+ * @param luceneIndex {@link LuceneIndex} used in Lucene queries.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ */
+ public LuceneTemplate(LuceneIndex luceneIndex) {
+ super(luceneIndex);
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneTemplate} initialized with the given Lucene {@link String index name}
+ * and {@link Region}.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @param region {@link Region} on which the Lucene query is executed.
+ * @see org.apache.geode.cache.Region
+ */
+ public LuceneTemplate(String indexName, Region, ?> region) {
+ super(indexName, region);
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneTemplate} initialized with the given Lucene {@link String index name}
+ * and {@link String fully-qualified Region path}.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @param regionPath {@link String} containing the fully-qualified path of the {@link Region}.
+ */
+ public LuceneTemplate(String indexName, String regionPath) {
+ super(indexName, regionPath);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(String query, String defaultField,
+ int resultLimit, String... projectionFields) {
+
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit, projectionFields);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, query, defaultField);
+
+ return doFind(queryWrapper::findResults, query, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(String query, String defaultField,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit, pageSize, projectionFields);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, query, defaultField);
+
+ return doFind(queryWrapper::findPages, query, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(LuceneQueryProvider queryProvider,
+ int resultLimit, String... projectionFields) {
+
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit, projectionFields);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, queryProvider);
+
+ return doFind(queryWrapper::findResults, queryProvider, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(LuceneQueryProvider queryProvider,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit, pageSize, projectionFields);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, queryProvider);
+
+ return doFind(queryWrapper::findPages, queryProvider, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(String query, String defaultField, int resultLimit) {
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, query, defaultField);
+
+ return doFind(queryWrapper::findKeys, query, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(LuceneQueryProvider queryProvider, int resultLimit) {
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit);
+
+ LuceneQuery queryWrapper = queryFactory.create(indexName, regionPath, queryProvider);
+
+ return doFind(queryWrapper::findKeys, queryProvider, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(String query, String defaultField, int resultLimit) {
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit);
+
+ LuceneQuery, V> queryWrapper = queryFactory.create(indexName, regionPath, query, defaultField);
+
+ return doFind(queryWrapper::findValues, query, regionPath, indexName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(LuceneQueryProvider queryProvider, int resultLimit) {
+ String indexName = resolveIndexName();
+ String regionPath = resolveRegionPath();
+
+ LuceneQueryFactory queryFactory = createLuceneQueryFactory(resultLimit);
+
+ LuceneQuery, V> queryWrapper = queryFactory.create(indexName, regionPath, queryProvider);
+
+ return doFind(queryWrapper::findValues, queryProvider, regionPath, indexName);
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/MappingLuceneOperations.java b/src/main/java/org/springframework/data/gemfire/search/lucene/MappingLuceneOperations.java
new file mode 100644
index 00000000..999fcac2
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/MappingLuceneOperations.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import java.util.List;
+
+import org.apache.geode.cache.lucene.LuceneQueryProvider;
+import org.springframework.data.domain.Page;
+
+/**
+ * The {@link MappingLuceneOperations} interface defines a contract for implementing classes to execute
+ * Lucene data access operations and mapping the results to entity domain {@link Class types}.
+ *
+ * @author John Blum
+ * @see org.springframework.data.domain.Page
+ * @see org.springframework.data.gemfire.search.lucene.LuceneOperations
+ * @see org.apache.geode.cache.lucene.LuceneQueryProvider
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public interface MappingLuceneOperations extends LuceneOperations {
+
+ default List query(Class entityType, String query, String defaultField,
+ String... projectionFields) {
+
+ return query(entityType, query, defaultField, DEFAULT_RESULT_LIMIT, projectionFields);
+ }
+
+ List query(Class entityType, String query, String defaultField,
+ int resultLimit, String... projectionFields);
+
+ Page query(Class entityType, String query, String defaultField,
+ int resultLimit, int pageSize, String... projectionFields);
+
+ default List query(Class entityType, LuceneQueryProvider queryProvider,
+ String... projectionFields) {
+
+ return query(entityType, queryProvider, DEFAULT_RESULT_LIMIT, projectionFields);
+ }
+
+ List query(Class entityType, LuceneQueryProvider queryProvider,
+ int resultLimit, String... projectionFields);
+
+ Page query(Class entityType, LuceneQueryProvider queryProvider,
+ int resultLimit, int pageSize, String... projectionFields);
+
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneAccessorSupport.java b/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneAccessorSupport.java
new file mode 100644
index 00000000..7df24176
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneAccessorSupport.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2016 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.search.lucene.support;
+
+import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+import org.apache.geode.cache.lucene.LuceneQueryProvider;
+import org.apache.geode.cache.lucene.LuceneResultStruct;
+import org.apache.geode.cache.lucene.PageableLuceneQueryResults;
+import org.springframework.data.gemfire.search.lucene.LuceneAccessor;
+import org.springframework.data.gemfire.util.RuntimeExceptionFactory;
+
+/**
+ * {@link LuceneAccessorSupport} is a {@link LuceneAccessor} class implementation providing support
+ * for extending classes.
+ *
+ * @author John Blum
+ * @see org.springframework.data.gemfire.search.lucene.LuceneAccessor
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public abstract class LuceneAccessorSupport extends LuceneAccessor {
+
+ /**
+ * Constructs an uninitialized instance of {@link LuceneAccessorSupport}.
+ */
+ @SuppressWarnings("all")
+ public LuceneAccessorSupport() {
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneAccessorSupport} initialized with the given {@link LuceneIndex}.
+ *
+ * @param luceneIndex {@link LuceneIndex} used in Lucene queries.
+ * @see org.apache.geode.cache.lucene.LuceneIndex
+ */
+ public LuceneAccessorSupport(LuceneIndex luceneIndex) {
+ super(luceneIndex);
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneAccessorSupport} initialized with the given Lucene
+ * {@link String index name} and {@link Region}.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @param region {@link Region} on which the Lucene query is executed.
+ * @see org.apache.geode.cache.Region
+ */
+ public LuceneAccessorSupport(String indexName, Region, ?> region) {
+ super(indexName, region);
+ }
+
+ /**
+ * Constructs an instance of {@link LuceneAccessorSupport} initialized with the given Lucene
+ * {@link String index name} and {@link String fully-qualified Region path}.
+ *
+ * @param indexName {@link String} containing the name of the {@link LuceneIndex}.
+ * @param regionPath {@link String} containing the fully-qualified path of the {@link Region}.
+ */
+ public LuceneAccessorSupport(String indexName, String regionPath) {
+ super(indexName, regionPath);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(String query, String defaultField,
+ int resultLimit, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(String query, String defaultField,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(LuceneQueryProvider queryProvider,
+ int resultLimit, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(LuceneQueryProvider queryProvider,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(String query, String defaultField, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(LuceneQueryProvider queryProvider, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(String query, String defaultField, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(LuceneQueryProvider queryProvider, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneOperationsSupport.java b/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneOperationsSupport.java
new file mode 100644
index 00000000..d8ff1f9c
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneOperationsSupport.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016 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.search.lucene.support;
+
+import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.geode.cache.lucene.LuceneQueryProvider;
+import org.apache.geode.cache.lucene.LuceneResultStruct;
+import org.apache.geode.cache.lucene.PageableLuceneQueryResults;
+import org.springframework.data.gemfire.search.lucene.LuceneOperations;
+import org.springframework.data.gemfire.util.RuntimeExceptionFactory;
+
+/**
+ * {@link LuceneOperationsSupport} is a abstract supporting class for implementations
+ * of the {@link LuceneOperations} interface.
+ *
+ * @author John Blum
+ * @see org.springframework.data.gemfire.search.lucene.LuceneOperations
+ * @since 1.1.0
+ */
+@SuppressWarnings("unused")
+public abstract class LuceneOperationsSupport implements LuceneOperations {
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(String query, String defaultField,
+ int resultLimit, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(String query, String defaultField,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public List> query(LuceneQueryProvider queryProvider,
+ int resultLimit, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public PageableLuceneQueryResults query(LuceneQueryProvider queryProvider,
+ int resultLimit, int pageSize, String... projectionFields) {
+
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(String query, String defaultField, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForKeys(LuceneQueryProvider queryProvider, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(String query, String defaultField, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public Collection queryForValues(LuceneQueryProvider queryProvider, int resultLimit) {
+ throw newUnsupportedOperationException(RuntimeExceptionFactory.NOT_IMPLEMENTED);
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
index 7e2a554a..3a236547 100644
--- a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
+++ b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java
@@ -124,4 +124,8 @@ public abstract class CacheUtils extends DistributedSystemUtils {
return null;
}
}
+
+ public static GemFireCache resolveGemFireCache() {
+ return defaultIfNull(getCache(), CacheUtils::getClientCache);
+ }
}
diff --git a/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java b/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java
new file mode 100644
index 00000000..1dc58603
--- /dev/null
+++ b/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.springframework.data.gemfire.util;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link RuntimeExceptionFactory} class is a factory for creating common {@link RuntimeException RuntimeExceptions}
+ * with the added convenience of message formatting and optional {@link Throwable causes}.
+ *
+ * @author John Blum
+ * @see java.lang.RuntimeException
+ * @since 1.0.0
+ */
+@SuppressWarnings("unused")
+public abstract class RuntimeExceptionFactory {
+
+ public static final String NOT_IMPLEMENTED = "Not Implemented";
+
+ /**
+ * Constructs and initializes an {@link IllegalArgumentException} with the given {@link String message}
+ * and {@link Object[] arguments} used to format the message.
+ *
+ * @param message {@link String} describing the {@link IllegalArgumentException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link IllegalArgumentException} with the given {@link String message}.
+ * @see #newIllegalArgumentException(Throwable, String, Object...)
+ * @see java.lang.IllegalArgumentException
+ */
+ public static IllegalArgumentException newIllegalArgumentException(String message, Object... args) {
+ return newIllegalArgumentException(null, message, args);
+ }
+
+ /**
+ * Constructs and initializes an {@link IllegalArgumentException} with the given {@link Throwable cause},
+ * {@link String message} and {@link Object[] arguments} used to format the message.
+ *
+ * @param cause {@link Throwable} identifying the reason the {@link IllegalArgumentException} was thrown.
+ * @param message {@link String} describing the {@link IllegalArgumentException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link IllegalArgumentException} with the given {@link Throwable cause} and {@link String message}.
+ * @see java.lang.IllegalArgumentException
+ */
+ public static IllegalArgumentException newIllegalArgumentException(Throwable cause,
+ String message, Object... args) {
+
+ return new IllegalArgumentException(format(message, args), cause);
+ }
+
+ /**
+ * Constructs and initializes an {@link IllegalStateException} with the given {@link String message}
+ * and {@link Object[] arguments} used to format the message.
+ *
+ * @param message {@link String} describing the {@link IllegalStateException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link IllegalStateException} with the given {@link String message}.
+ * @see #newIllegalStateException(Throwable, String, Object...)
+ * @see java.lang.IllegalStateException
+ */
+ public static IllegalStateException newIllegalStateException(String message, Object... args) {
+ return newIllegalStateException(null, message, args);
+ }
+
+ /**
+ * Constructs and initializes an {@link IllegalStateException} with the given {@link Throwable cause},
+ * {@link String message} and {@link Object[] arguments} used to format the message.
+ *
+ * @param cause {@link Throwable} identifying the reason the {@link IllegalStateException} was thrown.
+ * @param message {@link String} describing the {@link IllegalStateException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link IllegalStateException} with the given {@link Throwable cause} and {@link String message}.
+ * @see java.lang.IllegalStateException
+ */
+ public static IllegalStateException newIllegalStateException(Throwable cause, String message, Object... args) {
+ return new IllegalStateException(format(message, args), cause);
+ }
+
+ /**
+ * Constructs and initializes an {@link RuntimeException} with the given {@link String message}
+ * and {@link Object[] arguments} used to format the message.
+ *
+ * @param message {@link String} describing the {@link RuntimeException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link RuntimeException} with the given {@link String message}.
+ * @see #newRuntimeException(Throwable, String, Object...)
+ * @see java.lang.RuntimeException
+ */
+ public static RuntimeException newRuntimeException(String message, Object... args) {
+ return newRuntimeException(null, message, args);
+ }
+
+ /**
+ * Constructs and initializes an {@link RuntimeException} with the given {@link Throwable cause},
+ * {@link String message} and {@link Object[] arguments} used to format the message.
+ *
+ * @param cause {@link Throwable} identifying the reason the {@link RuntimeException} was thrown.
+ * @param message {@link String} describing the {@link RuntimeException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link RuntimeException} with the given {@link Throwable cause} and {@link String message}.
+ * @see java.lang.RuntimeException
+ */
+ public static RuntimeException newRuntimeException(Throwable cause, String message, Object... args) {
+ return new RuntimeException(format(message, args), cause);
+ }
+
+ /**
+ * Constructs and initializes an {@link UnsupportedOperationException} with the given {@link String message}
+ * and {@link Object[] arguments} used to format the message.
+ *
+ * @param message {@link String} describing the {@link UnsupportedOperationException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}
+ * @return a new {@link UnsupportedOperationException} with the given {@link String message}.
+ * @see #newUnsupportedOperationException(Throwable, String, Object...)
+ * @see java.lang.UnsupportedOperationException
+ */
+ public static UnsupportedOperationException newUnsupportedOperationException(String message, Object... args) {
+ return newUnsupportedOperationException(null, message, args);
+ }
+
+ /**
+ * Constructs and initializes an {@link UnsupportedOperationException} with the given {@link Throwable cause},
+ * {@link String message} and {@link Object[] arguments} used to format the message.
+ *
+ * @param cause {@link Throwable} identifying the reason the {@link UnsupportedOperationException} was thrown.
+ * @param message {@link String} describing the {@link UnsupportedOperationException exception}.
+ * @param args {@link Object[] arguments} used to replace format placeholders in the {@link String message}.
+ * @return a new {@link UnsupportedOperationException} with the given {@link Throwable cause}
+ * and {@link String message}.
+ * @see java.lang.UnsupportedOperationException
+ */
+ public static UnsupportedOperationException newUnsupportedOperationException(Throwable cause,
+ String message, Object... args) {
+
+ return new UnsupportedOperationException(format(message, args), cause);
+ }
+
+ /**
+ * Formats the given {@link String message} using the given {@link Object[] arguments}.
+ *
+ * @param message {@link String} containing the message pattern to format.
+ * @param args {@link Object[] arguments} used in the message to replace format placeholders.
+ * @return the formatted {@link String message}.
+ * @see java.lang.String#format(String, Object...)
+ * @see java.text.MessageFormat#format(String, Object...)
+ */
+ protected static String format(String message, Object... args) {
+ return MessageFormat.format(String.format(message, args), args);
+ }
+}
diff --git a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java
index 429ac57a..a4c3ccb0 100644
--- a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java
+++ b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java
@@ -20,8 +20,10 @@ package org.springframework.data.gemfire.util;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -34,15 +36,15 @@ import org.springframework.util.StringUtils;
* @since 1.8.0
*/
@SuppressWarnings("unused")
-// TODO rename this utiltiy class using a more descriptive, intuitive and meaningful name
+// 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 beanName) {
+ public static BeanDefinition addDependsOn(BeanDefinition bean, String... beanNames) {
List dependsOnList = new ArrayList<>();
Collections.addAll(dependsOnList, nullSafeArray(bean.getDependsOn(), String.class));
- dependsOnList.add(beanName);
+ dependsOnList.addAll(Arrays.asList(nullSafeArray(beanNames, String.class)));
bean.setDependsOn(dependsOnList.toArray(new String[dependsOnList.size()]));
return bean;
@@ -58,6 +60,16 @@ public abstract class SpringUtils {
return (value != null ? value : defaultValue);
}
+ /* (non-Javadoc) */
+ public static T defaultIfNull(T value, Supplier supplier) {
+ return (value != null ? value : supplier.get());
+ }
+
+ /* (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));
@@ -74,7 +86,17 @@ public abstract class SpringUtils {
}
/* (non-Javadoc) */
- public static String dereferenceBean(String beanName) {
- return String.format("%1$s%2$s", BeanFactory.FACTORY_BEAN_PREFIX, beanName);
+ public static T safeGetValue(Supplier supplier) {
+ return safeGetValue(supplier, null);
+ }
+
+ /* (non-Javadoc) */
+ public static T safeGetValue(Supplier supplier, T defaultValue) {
+ try {
+ return supplier.get();
+ }
+ catch (Throwable ignore) {
+ return defaultValue;
+ }
}
}
diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneAccessorUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneAccessorUnitTests.java
new file mode 100644
index 00000000..c857a791
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneAccessorUnitTests.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyVararg;
+import static org.mockito.Matchers.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;
+import static org.springframework.data.gemfire.search.lucene.LuceneAccessor.LuceneQueryExecutor;
+
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+import org.apache.geode.cache.lucene.LuceneQueryException;
+import org.apache.geode.cache.lucene.LuceneQueryFactory;
+import org.apache.geode.cache.lucene.LuceneService;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.data.gemfire.search.lucene.support.LuceneAccessorSupport;
+
+/**
+ * Unit tests for {@link LuceneAccessor}.
+ *
+ * @author John Blum
+ * @see org.junit.Rule
+ * @see org.junit.Test
+ * @see org.junit.runner.RunWith
+ * @see org.mockito.Mock
+ * @see org.mockito.Mockito
+ * @see org.mockito.Spy
+ * @see org.mockito.runners.MockitoJUnitRunner
+ * @see org.springframework.data.gemfire.search.lucene.LuceneAccessor
+ * @since 1.0.0
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class LuceneAccessorUnitTests {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Mock
+ private GemFireCache mockCache;
+
+ // SUT
+ private LuceneAccessor luceneAccessor;
+
+ @Mock
+ private LuceneIndex mockLuceneIndex;
+
+ @Mock
+ private LuceneQueryFactory mockLuceneQueryFactory;
+
+ @Mock
+ private LuceneService mockLuceneService;
+
+ @Mock
+ private Region, ?> mockRegion;
+
+ @Before
+ public void setup() {
+ luceneAccessor = spy(new LuceneAccessorSupport() { });
+ }
+
+ @Test
+ public void afterPropertiesSetInitializesLuceneAccessorProperly() throws Exception {
+ assertThat(luceneAccessor.getCache()).isNull();
+ assertThat(luceneAccessor.getLuceneService()).isNull();
+ assertThat(luceneAccessor.getIndexName()).isNullOrEmpty();
+ assertThat(luceneAccessor.getRegion()).isNull();
+ assertThat(luceneAccessor.getRegionPath()).isNullOrEmpty();
+
+ doReturn(mockCache).when(luceneAccessor).resolveCache();
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService();
+ doReturn("TestIndex").when(luceneAccessor).resolveIndexName();
+ doReturn("/Example").when(luceneAccessor).resolveRegionPath();
+
+ luceneAccessor.afterPropertiesSet();
+
+ assertThat(luceneAccessor.getCache()).isEqualTo(mockCache);
+ assertThat(luceneAccessor.getLuceneService()).isEqualTo(mockLuceneService);
+ assertThat(luceneAccessor.getIndexName()).isEqualTo("TestIndex");
+ assertThat(luceneAccessor.getRegion()).isNull();
+ assertThat(luceneAccessor.getRegionPath()).isEqualTo("/Example");
+
+ verify(luceneAccessor, times(1)).resolveCache();
+ verify(luceneAccessor, times(1)).resolveLuceneService();
+ verify(luceneAccessor, times(1)).resolveIndexName();
+ verify(luceneAccessor, times(1)).resolveRegionPath();
+ }
+
+ @Test
+ public void createLuceneQueryFactory() {
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService();
+ when(mockLuceneService.createLuceneQueryFactory()).thenReturn(mockLuceneQueryFactory);
+
+ assertThat(luceneAccessor.createLuceneQueryFactory()).isEqualTo(mockLuceneQueryFactory);
+
+ verify(luceneAccessor).resolveLuceneService();
+ verify(mockLuceneService, times(1)).createLuceneQueryFactory();
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void createLuceneQueryFactoryWithProjectionFields() {
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService();
+ when(mockLuceneService.createLuceneQueryFactory()).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setPageSize(anyInt())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setProjectionFields(anyVararg())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setResultLimit(anyInt())).thenReturn(mockLuceneQueryFactory);
+
+ assertThat(luceneAccessor.createLuceneQueryFactory("fieldOne", "fieldTwo"))
+ .isEqualTo(mockLuceneQueryFactory);
+
+ verify(luceneAccessor).resolveLuceneService();
+ verify(mockLuceneService, times(1)).createLuceneQueryFactory();
+ verify(mockLuceneQueryFactory, times(1))
+ .setResultLimit(eq(LuceneAccessor.DEFAULT_RESULT_LIMIT));
+ verify(mockLuceneQueryFactory, times(1))
+ .setPageSize(eq(LuceneAccessor.DEFAULT_PAGE_SIZE));
+ verify(mockLuceneQueryFactory, times(1))
+ .setProjectionFields(eq("fieldOne"), eq("fieldTwo"));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void createLuceneQueryFactoryWithResultLimitAndProjectionFields() {
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService();
+ when(mockLuceneService.createLuceneQueryFactory()).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setPageSize(anyInt())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setProjectionFields(anyVararg())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setResultLimit(anyInt())).thenReturn(mockLuceneQueryFactory);
+
+ assertThat(luceneAccessor.createLuceneQueryFactory(1000, "fieldOne", "fieldTwo"))
+ .isEqualTo(mockLuceneQueryFactory);
+
+ verify(luceneAccessor).resolveLuceneService();
+ verify(mockLuceneService, times(1)).createLuceneQueryFactory();
+ verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(1000));
+ verify(mockLuceneQueryFactory, times(1))
+ .setPageSize(eq(LuceneAccessor.DEFAULT_PAGE_SIZE));
+ verify(mockLuceneQueryFactory, times(1))
+ .setProjectionFields(eq("fieldOne"), eq("fieldTwo"));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void createLuceneQueryFactoryWithResultLimitPageSizeAndProjectionFields() {
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService();
+ when(mockLuceneService.createLuceneQueryFactory()).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setPageSize(anyInt())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setProjectionFields(anyVararg())).thenReturn(mockLuceneQueryFactory);
+ when(mockLuceneQueryFactory.setResultLimit(anyInt())).thenReturn(mockLuceneQueryFactory);
+
+ assertThat(luceneAccessor.createLuceneQueryFactory(1000, 20,
+ "fieldOne", "fieldTwo")).isEqualTo(mockLuceneQueryFactory);
+
+ verify(luceneAccessor).resolveLuceneService();
+ verify(mockLuceneService, times(1)).createLuceneQueryFactory();
+ verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(1000));
+ verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(20));
+ verify(mockLuceneQueryFactory, times(1))
+ .setProjectionFields(eq("fieldOne"), eq("fieldTwo"));
+ }
+
+ @Test
+ public void resolveCacheReturnsConfiguredCache() {
+ luceneAccessor.setCache(mockCache);
+
+ assertThat(luceneAccessor.getCache()).isSameAs(mockCache);
+ assertThat(luceneAccessor.resolveCache()).isSameAs(mockCache);
+ }
+
+ @Test
+ public void resolveLuceneServiceReturnsConfiguredLuceneService() {
+ luceneAccessor.setLuceneService(mockLuceneService);
+
+ assertThat(luceneAccessor.getLuceneService()).isSameAs(mockLuceneService);
+ assertThat(luceneAccessor.resolveLuceneService()).isSameAs(mockLuceneService);
+ }
+
+ @Test
+ public void resolveLuceneServiceLooksUpLuceneService() {
+ doReturn(mockCache).when(luceneAccessor).resolveCache();
+ doReturn(mockLuceneService).when(luceneAccessor).resolveLuceneService(eq(mockCache));
+
+ assertThat(luceneAccessor.getLuceneService()).isNull();
+ assertThat(luceneAccessor.resolveLuceneService()).isSameAs(mockLuceneService);
+
+ verify(luceneAccessor, times(1)).resolveCache();
+ verify(luceneAccessor, times(1)).resolveLuceneService(eq(mockCache));
+ }
+
+ @Test
+ public void resolveLuceneServiceThrowsIllegalArgumentExceptionWhenCacheIsNull() {
+ exception.expect(IllegalArgumentException.class);
+ exception.expectCause(is(nullValue(Throwable.class)));
+ exception.expectMessage("Cache reference was not properly configured");
+
+ luceneAccessor.resolveLuceneService(null);
+ }
+
+ @Test
+ @SuppressWarnings("all")
+ public void resolveIndexNameReturnsConfiguredIndexName() {
+ luceneAccessor.setIndexName("TestIndex");
+
+ assertThat(luceneAccessor.getIndexName()).isEqualTo("TestIndex");
+ assertThat(luceneAccessor.resolveIndexName()).isEqualTo("TestIndex");
+
+ verify(luceneAccessor, never()).getLuceneIndex();
+ }
+
+ @Test
+ @SuppressWarnings("all")
+ public void resolveIndexNameReturnsLuceneIndexName() {
+ luceneAccessor.setLuceneIndex(mockLuceneIndex);
+
+ when(mockLuceneIndex.getName()).thenReturn("MockIndex");
+
+ assertThat(luceneAccessor.getIndexName()).isNullOrEmpty();
+ assertThat(luceneAccessor.resolveIndexName()).isEqualTo("MockIndex");
+
+ verify(luceneAccessor, times(1)).getLuceneIndex();
+ verify(mockLuceneIndex, times(1)).getName();
+ }
+
+ @Test
+ @SuppressWarnings("all")
+ public void resolveIndexNameThrowsIllegalStateExceptionWhenIndexNameIsUnresolvable() {
+ assertThat(luceneAccessor.getIndexName()).isNullOrEmpty();
+ assertThat(luceneAccessor.getLuceneIndex()).isNull();
+
+ exception.expect(IllegalStateException.class);
+ exception.expectCause(is(nullValue(Throwable.class)));
+ exception.expectMessage("The name of the Lucene Index could not be resolved");
+
+ luceneAccessor.resolveIndexName();
+ }
+
+ @Test
+ public void resolveRegionPathReturnsConfiguredRegionPath() {
+ luceneAccessor.setRegionPath("/Example");
+
+ assertThat(luceneAccessor.getRegionPath()).isEqualTo("/Example");
+ assertThat(luceneAccessor.resolveRegionPath()).isEqualTo("/Example");
+ }
+
+ @Test
+ @SuppressWarnings("all")
+ public void resolveRegionPathReturnsRegionFullPath() {
+ when(mockRegion.getFullPath()).thenReturn("/Example");
+
+ luceneAccessor.setRegion(mockRegion);
+
+ assertThat(luceneAccessor.resolveRegionPath()).isEqualTo("/Example");
+
+ verify(luceneAccessor, times(1)).getRegionPath();
+ verify(luceneAccessor, times(1)).getRegion();
+ verify(mockRegion, times(1)).getFullPath();
+ }
+
+ @Test
+ public void resolveRegionPathThrowsIllegalStatueExceptionWhenRegionPathIsUnresolvable() {
+ assertThat(luceneAccessor.getRegion()).isNull();
+ assertThat(luceneAccessor.getRegionPath()).isNullOrEmpty();
+
+ exception.expect(IllegalStateException.class);
+ exception.expectCause(is(nullValue(Throwable.class)));
+ exception.expectMessage("Region path could not be resolved");
+
+ luceneAccessor.resolveRegionPath();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void doFind() throws LuceneQueryException {
+ LuceneQueryExecutor mockQueryExecutor = mock(LuceneQueryExecutor.class);
+
+ when(mockQueryExecutor.execute()).thenReturn("test");
+
+ assertThat(luceneAccessor.doFind(mockQueryExecutor, "title : 'Up Shit Creek Without a Paddle",
+ "/Example", "ExampleIndex")).isEqualTo("test");
+
+ verify(mockQueryExecutor, times(1)).execute();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void doFindHandlesLuceneQueryException() throws LuceneQueryException {
+ LuceneQueryExecutor mockQueryExecutor = mock(LuceneQueryExecutor.class);
+
+ when(mockQueryExecutor.execute()).thenThrow(new LuceneQueryException("test"));
+
+ try {
+ exception.expect(DataRetrievalFailureException.class);
+ exception.expectCause(isA(LuceneQueryException.class));
+ exception.expectMessage(containsString(
+ "Failed to execute Lucene Query [title : Up Shit Creek Without a Paddle] on Region [/Example] with Lucene Index [ExampleIndex]"));
+
+ luceneAccessor.doFind(mockQueryExecutor, "title : Up Shit Creek Without a Paddle",
+ "/Example", "ExampleIndex");
+ }
+ finally {
+ verify(mockQueryExecutor, times(1)).execute();
+ }
+ }
+
+ @Test
+ public void luceneAccessorInitializedCorrectly() {
+ luceneAccessor.setCache(mockCache);
+ luceneAccessor.setIndexName("ExampleIndex");
+ luceneAccessor.setLuceneIndex(mockLuceneIndex);
+ luceneAccessor.setLuceneService(mockLuceneService);
+ luceneAccessor.setRegion(mockRegion);
+ luceneAccessor.setRegionPath("/Example");
+
+ assertThat(luceneAccessor.getCache()).isSameAs(mockCache);
+ assertThat(luceneAccessor.getIndexName()).isEqualTo("ExampleIndex");
+ assertThat(luceneAccessor.getLuceneIndex()).isSameAs(mockLuceneIndex);
+ assertThat(luceneAccessor.getLuceneService()).isSameAs(mockLuceneService);
+ assertThat(luceneAccessor.getRegion()).isSameAs(mockRegion);
+ assertThat(luceneAccessor.getRegionPath()).isEqualTo("/Example");
+ }
+}
diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java
new file mode 100644
index 00000000..f31de4a3
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright 2016 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.search.lucene;
+
+import static org.assertj.core.api.Java6Assertions.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+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.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.lucene.LuceneIndex;
+import org.apache.geode.cache.lucene.LuceneService;
+import org.apache.lucene.analysis.Analyzer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+
+/**
+ * Unit tests for {@link LuceneIndexFactoryBean}.
+ *
+ * @author John Blum
+ * @see org.junit.Rule
+ * @see org.junit.Test
+ * @see org.junit.runner.RunWith
+ * @see org.mockito.Mock
+ * @see org.mockito.Mockito
+ * @see org.mockito.Spy
+ * @see org.mockito.runners.MockitoJUnitRunner
+ * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean
+ * @since 1.1.0
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class LuceneIndexFactoryBeanUnitTests {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Mock
+ private Analyzer mockAnalyzer;
+
+ @Mock
+ private BeanFactory mockBeanFactory;
+
+ @Mock
+ private GemFireCache mockCache;
+
+ @Mock
+ private LuceneIndex mockLuceneIndex;
+
+ @Mock
+ private LuceneService mockLuceneService;
+
+ @Mock
+ private Region