From f104d5752725469837254e7dda773539505a26c4 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 8 Mar 2017 15:17:36 -0800 Subject: [PATCH] SGF-402 - Add Lucene Integration support. --- build.gradle | 7 +- gradle.properties | 4 +- pom.xml | 7 +- .../data/gemfire/GemfireOperations.java | 4 +- .../data/gemfire/IndexFactoryBean.java | 21 +- .../gemfire/search/lucene/LuceneAccessor.java | 425 ++++++++++++ .../search/lucene/LuceneIndexFactoryBean.java | 522 +++++++++++++++ .../search/lucene/LuceneOperations.java | 280 ++++++++ .../lucene/LuceneServiceFactoryBean.java | 116 ++++ .../gemfire/search/lucene/LuceneTemplate.java | 217 +++++++ .../lucene/MappingLuceneOperations.java | 62 ++ .../lucene/support/LuceneAccessorSupport.java | 155 +++++ .../support/LuceneOperationsSupport.java | 113 ++++ .../data/gemfire/util/CacheUtils.java | 4 + .../gemfire/util/RuntimeExceptionFactory.java | 164 +++++ .../data/gemfire/util/SpringUtils.java | 32 +- .../lucene/LuceneAccessorUnitTests.java | 356 ++++++++++ .../LuceneIndexFactoryBeanUnitTests.java | 606 ++++++++++++++++++ .../lucene/LuceneOperationsUnitTests.java | 121 ++++ .../LuceneServiceFactoryBeanUnitTests.java | 128 ++++ .../lucene/LuceneTemplateUnitTests.java | 337 ++++++++++ .../RuntimeExceptionFactoryUnitTests.java | 103 +++ .../gemfire/util/SpringUtilsUnitTests.java | 98 ++- 23 files changed, 3851 insertions(+), 31 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/LuceneAccessor.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/LuceneOperations.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/LuceneTemplate.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/MappingLuceneOperations.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneAccessorSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/search/lucene/support/LuceneOperationsSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java create mode 100644 src/test/java/org/springframework/data/gemfire/search/lucene/LuceneAccessorUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBeanUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/search/lucene/LuceneTemplateUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/util/RuntimeExceptionFactoryUnitTests.java 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 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 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 mockRegion; + + private LuceneIndexFactoryBean factoryBean; + + @Before + public void setup() { + factoryBean = spy(new LuceneIndexFactoryBean()); + doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(eq(mockCache)); + } + + @Test + public void afterPropertiesSetResolvesCacheLuceneServiceRegionPathAndCreatesLuceneIndex() throws Exception { + doReturn(mockCache).when(factoryBean).resolveCache(); + doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(); + doReturn("/Example").when(factoryBean).resolveRegionPath(); + doReturn(mockLuceneIndex).when(factoryBean).createIndex(eq("ExampleIndex"), eq("/Example")); + + factoryBean.setIndexName("ExampleIndex"); + + assertThat(factoryBean.getIndexName()).isEqualTo("ExampleIndex"); + + factoryBean.afterPropertiesSet(); + + assertThat(factoryBean.getObject()).isEqualTo(mockLuceneIndex); + + verify(factoryBean, times(1)).resolveCache(); + verify(factoryBean, times(1)).resolveLuceneService(); + verify(factoryBean, times(1)).resolveRegionPath(); + verify(factoryBean, times(1)) + .createIndex(eq("ExampleIndex"), eq("/Example")); + } + + @Test + public void afterPropertiesSetThrowsIllegalStateExceptionIndexNameNotSet() throws Exception { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("indexName was not properly initialized"); + + factoryBean.afterPropertiesSet(); + } + + @Test + public void createIndexWithAllFields() { + factoryBean.setLuceneService(mockLuceneService); + + when(mockLuceneService.getIndex(eq("ExampleIndex"), eq("/Example"))).thenReturn(mockLuceneIndex); + + assertThat(factoryBean.getFieldAnalyzers()).isEmpty(); + assertThat(factoryBean.getFields()).isEmpty(); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + + verify(mockLuceneService, times(1)) + .createIndex(eq("ExampleIndex"), eq("/Example"), eq(LuceneService.REGION_VALUE_FIELD)); + verify(mockLuceneService, times(1)) + .getIndex(eq("ExampleIndex"), eq("/Example")); + } + + @Test + public void createIndexWithFieldAnalyzers() { + Map fieldAnalyzers = Collections.singletonMap("fieldOne", mockAnalyzer); + + factoryBean.setFieldAnalyzers(fieldAnalyzers); + factoryBean.setLuceneService(mockLuceneService); + + when(mockLuceneService.getIndex(eq("ExampleIndex"), eq("/Example"))).thenReturn(mockLuceneIndex); + + assertThat(factoryBean.getFieldAnalyzers()).isEqualTo(fieldAnalyzers); + assertThat(factoryBean.getFields()).isEmpty(); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + + verify(mockLuceneService, times(1)) + .createIndex(eq("ExampleIndex"), eq("/Example"), eq(fieldAnalyzers)); + verify(mockLuceneService, times(1)) + .getIndex(eq("ExampleIndex"), eq("/Example")); + } + + @Test + public void createIndexWithTargetedFields() { + factoryBean.setFields("fieldOne", "fieldTwo"); + factoryBean.setLuceneService(mockLuceneService); + + when(mockLuceneService.getIndex(eq("ExampleIndex"), eq("/Example"))).thenReturn(mockLuceneIndex); + + assertThat(factoryBean.getFieldAnalyzers()).isEmpty(); + assertThat(factoryBean.getFields()).containsAll(Arrays.asList("fieldOne", "fieldTwo")); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + + verify(mockLuceneService, times(1)) + .createIndex(eq("ExampleIndex"), eq("/Example"), eq("fieldOne"), eq("fieldTwo")); + verify(mockLuceneService, times(1)) + .getIndex(eq("ExampleIndex"), eq("/Example")); + } + + @Test + @SuppressWarnings("deprecation") + public void destroyIsSuccessful() throws Exception { + factoryBean.setDestroy(true); + factoryBean.setLuceneService(mockLuceneService); + + assertThat(factoryBean.isDestroy()).isTrue(); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + + doReturn(mockLuceneIndex).when(factoryBean).getObject(); + + factoryBean.destroy(); + + verify(factoryBean, times(1)).getObject(); + verify(mockLuceneService, times(1)).destroyIndex(eq(mockLuceneIndex)); + } + + @Test + @SuppressWarnings("deprecation") + public void destroyDoesNothingWhenDestroyIsFalse() throws Exception { + factoryBean.setDestroy(false); + factoryBean.setLuceneService(mockLuceneService); + + assertThat(factoryBean.isDestroy()).isFalse(); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + + doReturn(mockLuceneIndex).when(factoryBean).getObject(); + + factoryBean.destroy(); + + verify(factoryBean, times(1)).getObject(); + verify(mockLuceneService, never()).destroyIndex(any(LuceneIndex.class)); + } + + @Test + @SuppressWarnings("deprecation") + public void destroyDoesNothingWhenLuceneIndexIsNull() throws Exception { + factoryBean.setDestroy(true); + factoryBean.setLuceneService(mockLuceneService); + + assertThat(factoryBean.isDestroy()).isTrue(); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + + doReturn(null).when(factoryBean).getObject(); + + factoryBean.destroy(); + + verify(factoryBean, times(1)).getObject(); + verify(mockLuceneService, never()).destroyIndex(isNull(LuceneIndex.class)); + } + + @Test + public void isLuceneIndexDestroyableReturnsTrue() { + factoryBean.setDestroy(true); + + assertThat(factoryBean.isDestroy()).isTrue(); + assertThat(factoryBean.isLuceneIndexDestroyable(mockLuceneIndex)).isTrue(); + } + + @Test + public void isLuceneIndexDestroyableWhenDestroyIsFalseReturnsFalse() { + factoryBean.setDestroy(false); + + assertThat(factoryBean.isDestroy()).isFalse(); + assertThat(factoryBean.isLuceneIndexDestroyable(mockLuceneIndex)).isFalse(); + } + + @Test + public void isLuceneIndexDestroyableWhenLuceneIndexIsNullReturnsFalse() { + factoryBean.setDestroy(true); + + assertThat(factoryBean.isDestroy()).isTrue(); + assertThat(factoryBean.isLuceneIndexDestroyable(null)).isFalse(); + } + + @Test + public void getObjectReturnsLuceneIndexFromLuceneService() throws Exception { + factoryBean.setCache(mockCache); + factoryBean.setIndexName("ExampleIndex"); + factoryBean.setLuceneService(mockLuceneService); + factoryBean.setRegionPath("/Example"); + + when(mockLuceneService.getIndex(eq("ExampleIndex"), eq("/Example"))).thenReturn(mockLuceneIndex); + + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getIndexName()).isEqualTo("ExampleIndex"); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.getRegionPath()).isEqualTo("/Example"); + assertThat(factoryBean.getObject()).isEqualTo(mockLuceneIndex); + + verify(mockLuceneService, times(1)) + .getIndex(eq("ExampleIndex"), eq("/Example")); + } + + @Test + public void getObjectReturnsExistingLuceneIndex() throws Exception { + factoryBean.setLuceneIndex(mockLuceneIndex); + factoryBean.setLuceneService(mockLuceneService); + + assertThat(factoryBean.getObject()).isSameAs(mockLuceneIndex); + + verify(mockLuceneService, never()).getIndex(anyString(), anyString()); + } + + @Test + public void getObjectReturnsNullWhenLuceneServiceIsNull() throws Exception { + factoryBean.setCache(mockCache); + factoryBean.setRegionPath("/Example"); + + doReturn(null).when(factoryBean).resolveLuceneService(eq(mockCache)); + + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getLuceneService()).isNull(); + assertThat(factoryBean.getRegionPath()).isEqualTo("/Example"); + assertThat(factoryBean.getObject()).isNull(); + + verify(factoryBean, times(1)).resolveLuceneService(); + verify(factoryBean, times(1)).resolveLuceneService(eq(mockCache)); + } + + @Test + public void getObjectTypeBeforeInitialization() { + assertThat(factoryBean.getObjectType()).isEqualTo(LuceneIndex.class); + } + + @Test + public void getObjectTypeAfterInitialization() { + assertThat(factoryBean.setLuceneIndex(mockLuceneIndex).getObjectType()).isEqualTo(mockLuceneIndex.getClass()); + } + + @Test + public void isSingletonReturnsTrue() { + assertThat(factoryBean.isSingleton()).isTrue(); + } + + @Test + public void resolveCacheReturnsConfiguredCache() { + factoryBean.setCache(mockCache); + + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.resolveCache()).isSameAs(mockCache); + } + + @Test + public void resolveFieldsReturnsGivenFields() { + List expectedFields = Arrays.asList("fieldOne", "fieldTwo"); + + assertThat(factoryBean.resolveFields(expectedFields)).isSameAs(expectedFields); + } + + @Test + public void resolveFieldsWithEmptyListReturnsAllFields() { + assertThat(factoryBean.resolveFields(Collections.emptyList())) + .isEqualTo(Collections.singletonList(LuceneService.REGION_VALUE_FIELD)); + } + + @Test + public void resolveFieldsWithNullReturnsAllFields() { + assertThat(factoryBean.resolveFields(null)) + .isEqualTo(Collections.singletonList(LuceneService.REGION_VALUE_FIELD)); + } + + @Test + public void resolveLuceneServiceReturnsConfiguredLuceneService() { + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockCache); + factoryBean.setLuceneService(mockLuceneService); + + assertThat(factoryBean.getBeanFactory()).isSameAs(mockBeanFactory); + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.resolveLuceneService()).isSameAs(mockLuceneService); + + verifyZeroInteractions(mockBeanFactory); + verifyZeroInteractions(mockCache); + verify(factoryBean, never()).resolveLuceneService(any(GemFireCache.class)); + } + + @Test + public void resolveLuceneServiceFromBeanFactory() { + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockCache); + + when(mockBeanFactory.getBean(eq(LuceneService.class))).thenReturn(mockLuceneService); + + assertThat(factoryBean.getBeanFactory()).isSameAs(mockBeanFactory); + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getLuceneService()).isNull(); + assertThat(factoryBean.resolveLuceneService()).isSameAs(mockLuceneService); + + verify(mockBeanFactory, times(1)).getBean(eq(LuceneService.class)); + verifyZeroInteractions(mockCache); + verify(factoryBean, never()).resolveLuceneService(any(GemFireCache.class)); + } + + @Test + public void resolveLuceneServiceFromGemFireCache() { + factoryBean.setCache(mockCache); + + doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(eq(mockCache)); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getLuceneService()).isNull(); + assertThat(factoryBean.resolveLuceneService()).isSameAs(mockLuceneService); + + verifyZeroInteractions(mockCache); + verify(factoryBean, times(1)).resolveLuceneService(eq(mockCache)); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveLuceneServiceFromGemFireCacheWithBeanFactoryThrowinBeansException() { + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockCache); + + when(mockBeanFactory.getBean(any(Class.class))).thenThrow(new NoSuchBeanDefinitionException("test")); + doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(eq(mockCache)); + + assertThat(factoryBean.getBeanFactory()).isSameAs(mockBeanFactory); + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.getLuceneService()).isNull(); + assertThat(factoryBean.resolveLuceneService()).isSameAs(mockLuceneService); + + verify(mockBeanFactory, times(1)).getBean(eq(LuceneService.class)); + verifyZeroInteractions(mockCache); + verify(factoryBean, times(1)).resolveLuceneService(eq(mockCache)); + } + + @Test + public void resolveLuceneServiceThrowsIllegalArgumentExceptionWhenGemFireCacheIsNotConfigured() { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("A reference to the GemFireCache was not properly configured"); + + factoryBean.resolveLuceneService(null); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionReturnsConfiguredRegion() { + factoryBean.setRegion(mockRegion); + + assertThat(factoryBean.resolveRegion()).isSameAs(mockRegion); + + verify(factoryBean, times(1)).getRegion(); + verify(factoryBean, never()).resolveCache(); + verify(factoryBean, never()).getRegionPath(); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionReturnsRegionFromCache() { + factoryBean.setRegionPath("/Example"); + + doReturn(mockCache).when(factoryBean).resolveCache(); + when(mockCache.getRegion(eq("/Example"))).thenReturn(mockRegion); + + assertThat(factoryBean.resolveRegion()).isEqualTo(mockRegion); + + verify(factoryBean, times(1)).getRegion(); + verify(factoryBean, times(1)).resolveCache(); + verify(factoryBean, times(1)).getRegionPath(); + verify(mockCache, times(1)).getRegion(eq("/Example")); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionReturnsNullWhenCacheNotConfigured() { + factoryBean.setRegionPath("/Example"); + + doReturn(null).when(factoryBean).resolveCache(); + + assertThat(factoryBean.resolveRegion()).isNull(); + + verify(factoryBean, times(1)).getRegion(); + verify(factoryBean, times(1)).resolveCache(); + verify(factoryBean, times(1)).getRegionPath(); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionReturnsNullWhenRegionPathNotConfigured() { + factoryBean.setCache(mockCache); + factoryBean.setRegionPath(" "); + + assertThat(factoryBean.resolveRegion()).isNull(); + + verify(factoryBean, times(1)).getRegion(); + verify(factoryBean, times(1)).resolveCache(); + verify(factoryBean, times(1)).getRegionPath(); + verify(mockCache, never()).getRegion(anyString()); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionPathReturnsConfiguredRegionPath() { + factoryBean.setRegionPath("/Example"); + + doReturn(null).when(factoryBean).resolveRegion(); + + assertThat(factoryBean.resolveRegionPath()).isEqualTo("/Example"); + + verify(factoryBean, times(1)).resolveRegion(); + verify(factoryBean, times(1)).getRegionPath(); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionPathReturnsRegionFullPath() { + doReturn(mockRegion).when(factoryBean).resolveRegion(); + when(mockRegion.getFullPath()).thenReturn("/Example"); + + assertThat(factoryBean.resolveRegionPath()).isEqualTo("/Example"); + + verify(factoryBean, times(1)).resolveRegion(); + verify(factoryBean, never()).getRegionPath(); + verify(mockRegion, times(1)).getFullPath(); + } + + @Test + @SuppressWarnings("all") + public void resolveRegionPathThrowsIllegalStateException() { + doReturn(null).when(factoryBean).resolveRegion(); + + factoryBean.setRegionPath(null); + + try { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("Either Region or regionPath must be specified"); + + factoryBean.resolveRegionPath(); + } + finally { + verify(factoryBean, times(1)).resolveRegion(); + verify(factoryBean, times(1)).getRegionPath(); + } + } + + @Test + public void setAndGetFieldAnalyzers() { + Map fieldAnalyzers = factoryBean.getFieldAnalyzers(); + + assertThat(fieldAnalyzers).isNotNull(); + assertThat(fieldAnalyzers).isEmpty(); + + fieldAnalyzers = Collections.singletonMap("mockField", mockAnalyzer); + factoryBean.setFieldAnalyzers(fieldAnalyzers); + + assertThat(factoryBean.getFieldAnalyzers()).isEqualTo(fieldAnalyzers); + + factoryBean.setFieldAnalyzers(null); + fieldAnalyzers = factoryBean.getFieldAnalyzers(); + + assertThat(fieldAnalyzers).isNotNull(); + assertThat(fieldAnalyzers).isEmpty(); + } + + @Test + public void setAndGetFields() { + List fields = factoryBean.getFields(); + + assertThat(fields).isNotNull(); + assertThat(fields).isEmpty(); + + factoryBean.setFields(Arrays.asList("fieldOne", "fieldTwo")); + + assertThat(factoryBean.getFields()).containsAll(Arrays.asList("fieldOne", "fieldTwo")); + + factoryBean.setFields((String[]) null); + fields = factoryBean.getFields(); + + assertThat(fields).isNotNull(); + assertThat(fields).isEmpty(); + } + + @Test + public void setAndGetIndexName() { + factoryBean.setBeanName("IndexOne"); + + assertThat(factoryBean.getIndexName()).isEqualTo("IndexOne"); + + factoryBean.setIndexName("IndexTwo"); + + assertThat(factoryBean.getIndexName()).isEqualTo("IndexTwo"); + + factoryBean.setIndexName(null); + } + + @Test + public void getUninitializedIndexName() { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("indexName was not properly initialized"); + + factoryBean.getIndexName(); + } + + @Test + public void factoryBeanInitializationIsSuccessful() { + factoryBean.setCache(mockCache); + factoryBean.setDestroy(true); + factoryBean.setFieldAnalyzers(Collections.singletonMap("fieldThree", mockAnalyzer)); + factoryBean.setFields("fieldOne", "fieldTwo"); + factoryBean.setIndexName("TestIndex"); + factoryBean.setLuceneService(mockLuceneService); + factoryBean.setRegion(mockRegion); + factoryBean.setRegionPath("/Grandparent/Parent/Child"); + + assertThat(factoryBean.getCache()).isSameAs(mockCache); + assertThat(factoryBean.isDestroy()).isTrue(); + assertThat(factoryBean.getFieldAnalyzers()).isEqualTo(Collections.singletonMap("fieldThree", mockAnalyzer)); + assertThat(factoryBean.getFields()).containsAll(Arrays.asList("fieldOne", "fieldTwo")); + assertThat(factoryBean.getIndexName()).isEqualTo("TestIndex"); + assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); + assertThat(factoryBean.getRegion()).isSameAs(mockRegion); + assertThat(factoryBean.getRegionPath()).isEqualTo("/Grandparent/Parent/Child"); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsUnitTests.java new file mode 100644 index 00000000..2c042905 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneOperationsUnitTests.java @@ -0,0 +1,121 @@ +/* + * 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.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.geode.cache.lucene.LuceneQueryProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Unit tests for the {@link LuceneOperations} interface. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.data.gemfire.search.lucene.LuceneOperations + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class LuceneOperationsUnitTests { + + @Mock + private LuceneQueryProvider mockLuceneQueryProvider; + + @Mock + private TestLuceneOperations mockLuceneOperations; + + @Test + public void stringQueryCallsQueryWithResultLimit() { + when(mockLuceneOperations.query(anyString(), anyString(), anyVararg())).thenCallRealMethod(); + + mockLuceneOperations.query("title : Up Shit Creek Without a Paddle", "title", + "content"); + + verify(mockLuceneOperations, times(1)).query( + eq("title : Up Shit Creek Without a Paddle"), eq("title"), + eq(LuceneOperations.DEFAULT_RESULT_LIMIT), eq("content")); + } + + @Test + public void queryProviderQueryCallsQueryWithResultLimit() { + when(mockLuceneOperations.query(any(LuceneQueryProvider.class), anyVararg())).thenCallRealMethod(); + + mockLuceneOperations.query(mockLuceneQueryProvider, "content"); + + verify(mockLuceneOperations, times(1)).query(eq(mockLuceneQueryProvider), + eq(LuceneOperations.DEFAULT_RESULT_LIMIT), eq("content")); + } + + @Test + public void stringQueryForKeysCallsQueryForKeysWithResultLimit() { + when(mockLuceneOperations.queryForKeys(anyString(), anyString())).thenCallRealMethod(); + + mockLuceneOperations.queryForKeys("title : Up Shit Creek Without a Paddle", "title"); + + verify(mockLuceneOperations, times(1)).queryForKeys( + eq("title : Up Shit Creek Without a Paddle"), eq("title"), + eq(LuceneOperations.DEFAULT_RESULT_LIMIT)); + } + + @Test + public void queryProviderQueryForKeysCallsQueryForKeysWithResultLimit() { + when(mockLuceneOperations.queryForKeys(any(LuceneQueryProvider.class))).thenCallRealMethod(); + + mockLuceneOperations.queryForKeys(mockLuceneQueryProvider); + + verify(mockLuceneOperations, times(1)).queryForKeys( + eq(mockLuceneQueryProvider), eq(LuceneOperations.DEFAULT_RESULT_LIMIT)); + } + + @Test + public void stringQueryForValuesCallsQueryForValuesWithResultLimit() { + when(mockLuceneOperations.queryForValues(anyString(), anyString())).thenCallRealMethod(); + + mockLuceneOperations.queryForValues("title : Up Shit Creek Without a Paddle", "title"); + + verify(mockLuceneOperations, times(1)).queryForValues( + eq("title : Up Shit Creek Without a Paddle"), eq("title"), + eq(LuceneOperations.DEFAULT_RESULT_LIMIT)); + } + + @Test + public void queryProviderQueryForValuesCallsQueryForValuesWithResultLimit() { + when(mockLuceneOperations.queryForValues(any(LuceneQueryProvider.class))).thenCallRealMethod(); + + mockLuceneOperations.queryForValues(mockLuceneQueryProvider); + + verify(mockLuceneOperations, times(1)).queryForValues( + eq(mockLuceneQueryProvider), eq(LuceneOperations.DEFAULT_RESULT_LIMIT)); + } + + abstract class TestLuceneOperations implements LuceneOperations { + } +} diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBeanUnitTests.java new file mode 100644 index 00000000..60e9eca5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneServiceFactoryBeanUnitTests.java @@ -0,0 +1,128 @@ +/* + * 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.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.apache.geode.cache.GemFireCache; +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; + +/** + * Unit tests for {@link LuceneServiceFactoryBean}. + * + * @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.LuceneServiceFactoryBean + * @since 1.1.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class LuceneServiceFactoryBeanUnitTests { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Mock + private GemFireCache mockCache; + + @Mock + private LuceneService mockLuceneService; + + private LuceneServiceFactoryBean factoryBean; + + @Before + public void setup() { + factoryBean = spy(new LuceneServiceFactoryBean()); + doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(eq(mockCache)); + } + + @Test + public void setAndGetCache() { + assertThat(factoryBean.getCache()).isNull(); + + factoryBean.setCache(mockCache); + + assertThat(factoryBean.getCache()).isSameAs(mockCache); + + factoryBean.setCache(null); + + assertThat(factoryBean.getCache()).isNull(); + } + + @Test + public void afterPropertiesSetInitializesLuceneService() throws Exception { + assertThat(factoryBean.getObject()).isNull(); + + factoryBean.setCache(mockCache); + factoryBean.afterPropertiesSet(); + + assertThat(factoryBean.getObject()).isEqualTo(mockLuceneService); + + verify(factoryBean, times(1)).resolveLuceneService(eq(mockCache)); + } + + @Test + public void afterPropertiesSetThrowsIllegalStateExceptionWhenGemFireCacheIsNull() throws Exception { + assertThat(factoryBean.getCache()).isNull(); + + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("A reference to the GemFireCache was not properly configured"); + + factoryBean.afterPropertiesSet(); + } + + @Test + public void getObjectTypeBeforeInitialization() throws Exception { + assertThat(factoryBean.getObject()).isNull(); + assertThat(factoryBean.getObjectType()).isEqualTo(LuceneService.class); + } + + @Test + public void getObjectTypeAfterInitialization() throws Exception { + factoryBean.setCache(mockCache); + factoryBean.afterPropertiesSet(); + + assertThat(factoryBean.getObject()).isSameAs(mockLuceneService); + assertThat(factoryBean.getObjectType()).isEqualTo(mockLuceneService.getClass()); + } + + @Test + public void isSingletonReturnsTrue() { + assertThat(factoryBean.isSingleton()).isTrue(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneTemplateUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneTemplateUnitTests.java new file mode 100644 index 00000000..e183e888 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneTemplateUnitTests.java @@ -0,0 +1,337 @@ +/* + * 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 java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.doReturn; +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 java.util.Collection; +import java.util.List; + +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.LuceneQueryProvider; +import org.apache.geode.cache.lucene.LuceneResultStruct; +import org.apache.geode.cache.lucene.LuceneService; +import org.apache.geode.cache.lucene.PageableLuceneQueryResults; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Unit tests for {@link LuceneTemplate}. + * + * @author John Blum + * @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.LuceneTemplate + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class LuceneTemplateUnitTests { + + @Mock + private LuceneQuery mockLuceneQuery; + + @Mock + private LuceneQueryFactory mockLuceneQueryFactory; + + @Mock + private LuceneQueryProvider mockLuceneQueryProvider; + + @Mock + private LuceneResultStruct mockLuceneResultStructOne; + + @Mock + private LuceneResultStruct mockLuceneResultStructTwo; + + @Mock + private LuceneService mockLuceneService; + + @Mock + private PageableLuceneQueryResults mockPageableLuceneQueryResults; + + @Spy + private LuceneTemplate luceneTemplate; + + @Before + @SuppressWarnings("deprecation") + public void setup() { + luceneTemplate.setLuceneService(mockLuceneService); + + when(mockLuceneService.createLuceneQueryFactory()).thenReturn(mockLuceneQueryFactory); + when(mockLuceneQueryFactory.setPageSize(anyInt())).thenReturn(mockLuceneQueryFactory); + when(mockLuceneQueryFactory.setProjectionFields(anyVararg())).thenReturn(mockLuceneQueryFactory); + when(mockLuceneQueryFactory.setResultLimit(anyInt())).thenReturn(mockLuceneQueryFactory); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void stringQueryReturnsList() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), anyString(), anyString())) + .thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findResults()).thenReturn(asList(mockLuceneResultStructOne, mockLuceneResultStructTwo)); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + List> results = luceneTemplate.query( + "title : Up Shit Creek Without a Paddle", "title", 100, + "content"); + + assertThat(results).isNotNull(); + assertThat(results).hasSize(2); + assertThat(results).containsAll(asList(mockLuceneResultStructOne, mockLuceneResultStructTwo)); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq("title : Up Shit Creek Without a Paddle"), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(eq("content")); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq("title : Up Shit Creek Without a Paddle"), eq("title")); + verify(mockLuceneQuery, times(1)).findResults(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void stringQueryWithPageSizeReturnsPageableResults() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), anyString(), anyString())) + .thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findPages()).thenReturn(mockPageableLuceneQueryResults); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + PageableLuceneQueryResults results = luceneTemplate.query( + "title : Up Shit Creek Without a Paddle", "title", 100, 20, + "content"); + + assertThat(results).isSameAs(mockPageableLuceneQueryResults); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq("title : Up Shit Creek Without a Paddle"), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(20)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(eq("content")); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq("title : Up Shit Creek Without a Paddle"), eq("title")); + verify(mockLuceneQuery, times(1)).findPages(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void queryProviderQueryReturnsList() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), + any(LuceneQueryProvider.class))).thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findResults()).thenReturn(asList(mockLuceneResultStructOne, mockLuceneResultStructTwo)); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + List> results = luceneTemplate.query(mockLuceneQueryProvider, + 100, "content"); + + assertThat(results).isNotNull(); + assertThat(results).hasSize(2); + assertThat(results).containsAll(asList(mockLuceneResultStructOne, mockLuceneResultStructTwo)); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq(mockLuceneQueryProvider), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(eq("content")); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq(mockLuceneQueryProvider)); + verify(mockLuceneQuery, times(1)).findResults(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void queryProviderQueryWithPageSizeReturnsPageableResults() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), + any(LuceneQueryProvider.class))).thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findPages()).thenReturn(mockPageableLuceneQueryResults); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + PageableLuceneQueryResults results = luceneTemplate.query(mockLuceneQueryProvider, + 100, 20, "content"); + + assertThat(results).isSameAs(mockPageableLuceneQueryResults); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq(mockLuceneQueryProvider), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(20)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(eq("content")); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq(mockLuceneQueryProvider)); + verify(mockLuceneQuery, times(1)).findPages(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void stringQueryForKeysReturnsKeys() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), anyString(), anyString())) + .thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findKeys()).thenReturn(asList("keyOne", "keyTwo")); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + Collection keys = luceneTemplate.queryForKeys( + "title : Up Shit Creek Without a Paddle", "title", 100); + + assertThat(keys).isNotNull(); + assertThat(keys).hasSize(2); + assertThat(keys).containsAll(asList("keyOne", "keyTwo")); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq("title : Up Shit Creek Without a Paddle"), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq("title : Up Shit Creek Without a Paddle"), eq("title")); + verify(mockLuceneQuery, times(1)).findKeys(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void queryProviderQueryForKeysReturnsKeys() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), + any(LuceneQueryProvider.class))).thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findKeys()).thenReturn(asList("keyOne", "keyTwo")); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + Collection keys = luceneTemplate.queryForKeys(mockLuceneQueryProvider, 100); + + assertThat(keys).isNotNull(); + assertThat(keys).hasSize(2); + assertThat(keys).containsAll(asList("keyOne", "keyTwo")); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq(mockLuceneQueryProvider), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq(mockLuceneQueryProvider)); + verify(mockLuceneQuery, times(1)).findKeys(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void stringQueryForValuesReturnsValues() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), anyString(), anyString())) + .thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findValues()).thenReturn(asList("valueOne", "valueTwo")); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + Collection keys = luceneTemplate.queryForValues( + "title : Up Shit Creek Without a Paddle", "title", 100); + + assertThat(keys).isNotNull(); + assertThat(keys).hasSize(2); + assertThat(keys).containsAll(asList("valueOne", "valueTwo")); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq("title : Up Shit Creek Without a Paddle"), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq("title : Up Shit Creek Without a Paddle"), eq("title")); + verify(mockLuceneQuery, times(1)).findValues(); + } + + @Test + @SuppressWarnings({ "deprecation", "unchecked" }) + public void queryProviderQueryForValuesReturnsValues() throws LuceneQueryException { + when(mockLuceneQueryFactory.create(eq("TestIndex"), eq("/Example"), + any(LuceneQueryProvider.class))).thenReturn(mockLuceneQuery); + when(mockLuceneQuery.findValues()).thenReturn(asList("valueOne", "valueTwo")); + + doReturn("TestIndex").when(luceneTemplate).resolveIndexName(); + doReturn("/Example").when(luceneTemplate).resolveRegionPath(); + + Collection keys = luceneTemplate.queryForValues(mockLuceneQueryProvider, 100); + + assertThat(keys).isNotNull(); + assertThat(keys).hasSize(2); + assertThat(keys).containsAll(asList("valueOne", "valueTwo")); + + verify(luceneTemplate, times(1)).resolveIndexName(); + verify(luceneTemplate, times(1)).resolveRegionPath(); + verify(luceneTemplate, times(1)).doFind(isA(LuceneQueryExecutor.class), + eq(mockLuceneQueryProvider), eq("/Example"), eq("TestIndex")); + verify(mockLuceneService, times(1)).createLuceneQueryFactory(); + verify(mockLuceneQueryFactory, times(1)).setResultLimit(eq(100)); + verify(mockLuceneQueryFactory, times(1)).setPageSize(eq(LuceneOperations.DEFAULT_PAGE_SIZE)); + verify(mockLuceneQueryFactory, times(1)).setProjectionFields(); + verify(mockLuceneQueryFactory, times(1)).create(eq("TestIndex"), + eq("/Example"), eq(mockLuceneQueryProvider)); + verify(mockLuceneQuery, times(1)).findValues(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/util/RuntimeExceptionFactoryUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/RuntimeExceptionFactoryUnitTests.java new file mode 100644 index 00000000..9a94c58c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/util/RuntimeExceptionFactoryUnitTests.java @@ -0,0 +1,103 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Unit tests for {@link RuntimeExceptionFactory}. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.data.gemfire.util.RuntimeExceptionFactory + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RuntimeExceptionFactoryUnitTests { + + @Mock + private Throwable mockCause; + + protected void assertThrowable(Throwable actual, Class type, String message) { + assertThrowable(actual, type, message, null); + } + + protected void assertThrowable(Throwable actual, Class type, String message, Throwable cause) { + assertThat(actual).isNotNull(); + assertThat(actual).isInstanceOf(type); + assertThat(actual).hasMessage(message); + assertThat(actual).hasCause(cause); + } + + @Test + public void newIllegalArgumentExceptionWithMessage() { + assertThrowable(newIllegalArgumentException("test"), IllegalArgumentException.class, "test"); + } + + @Test + public void newIllegalArgumentExceptionWithFormattedMessageAndCause() { + assertThrowable(newIllegalArgumentException(mockCause, "%1$s is a {1}", "This", "test"), + IllegalArgumentException.class, "This is a test", mockCause); + } + + @Test + public void newIllegalStateExceptionWithMessage() { + assertThrowable(newIllegalStateException("test"), IllegalStateException.class, "test"); + } + + @Test + public void newIllegalStateExceptionWithFormattedMessageAndCause() { + assertThrowable(newIllegalStateException(mockCause, "%1$s is a {1}", "This", "test"), + IllegalStateException.class, "This is a test", mockCause); + } + + @Test + public void newRuntimeExceptionWithMessage() { + assertThrowable(newRuntimeException("test"), RuntimeException.class, "test"); + } + + @Test + public void newRuntimeExceptionWithFormattedMessageAndCause() { + assertThrowable(newRuntimeException(mockCause, "%1$s is a {1}", "This", "test"), + RuntimeException.class, "This is a test", mockCause); + } + + @Test + public void newUnsupportedOperationExceptionWithMessage() { + assertThrowable(newUnsupportedOperationException("test"), UnsupportedOperationException.class, "test"); + } + + @Test + public void newUnsupportedOperationExceptionWithFormattedMessageAndCause() { + assertThrowable(newUnsupportedOperationException(mockCause, "%1$s is a {1}", "This", "test"), + UnsupportedOperationException.class, "This is a test", mockCause); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java index 73d3742f..a32f70c6 100644 --- a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java @@ -19,16 +19,21 @@ package org.springframework.data.gemfire.util; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.test.support.MockitoMatchers.stringArrayMatcher; +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; + +import java.util.function.Supplier; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.data.gemfire.test.support.MockitoMatchers; /** * Unit tests for {@link SpringUtils}. @@ -49,19 +54,36 @@ public class SpringUtilsUnitTests { @Test public void addDependsOnToExistingDependencies() { - when(mockBeanDefinition.getDependsOn()).thenReturn(ArrayUtils.asArray("testBeanNameOne", "testBeanNameTwo")); + when(mockBeanDefinition.getDependsOn()).thenReturn(asArray("testBeanNameOne", "testBeanNameTwo")); + assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree")).isSameAs(mockBeanDefinition); + verify(mockBeanDefinition, times(1)).getDependsOn(); verify(mockBeanDefinition, times(1)).setDependsOn(argThat( - MockitoMatchers.stringArrayMatcher("testBeanNameOne", "testBeanNameTwo", "testBeanNameThree"))); + stringArrayMatcher("testBeanNameOne", "testBeanNameTwo", "testBeanNameThree"))); } @Test public void addDependsOnToNonExistingDependencies() { when(mockBeanDefinition.getDependsOn()).thenReturn(null); + assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanName")).isSameAs(mockBeanDefinition); + verify(mockBeanDefinition, times(1)).getDependsOn(); - verify(mockBeanDefinition, times(1)).setDependsOn(argThat(MockitoMatchers.stringArrayMatcher("testBeanName"))); + verify(mockBeanDefinition, times(1)).setDependsOn(argThat(stringArrayMatcher("testBeanName"))); + } + + @Test + public void addDependsOnWithMultipleDependenciesWithExistingDependencies() { + when(mockBeanDefinition.getDependsOn()).thenReturn(asArray("testBeanNameOne", "testBeanNameTwo")); + + assertThat(SpringUtils.addDependsOn(mockBeanDefinition, "testBeanNameThree", "testBeanNameFour")) + .isSameAs(mockBeanDefinition); + + verify(mockBeanDefinition, times(1)).getDependsOn(); + verify(mockBeanDefinition, times(1)) + .setDependsOn(argThat(stringArrayMatcher("testBeanNameOne", "testBeanNameTwo", + "testBeanNameThree", "testBeanNameFour"))); } @Test @@ -101,6 +123,35 @@ public class SpringUtilsUnitTests { assertThat(SpringUtils.defaultIfNull(null, "DEFAULT")).isEqualTo("DEFAULT"); } + @Test + @SuppressWarnings("unchecked") + public void defaultIfNullWithSupplierReturnsValue() { + Supplier mockSupplier = mock(Supplier.class); + + when(mockSupplier.get()).thenReturn("supplier"); + + assertThat(SpringUtils.defaultIfNull("value", mockSupplier)).isEqualTo("value"); + + verify(mockSupplier, never()).get(); + } + + @Test + @SuppressWarnings("unchecked") + public void defaultIfNullWithSupplierReturnsSupplierValue() { + Supplier mockSupplier = mock(Supplier.class); + + when(mockSupplier.get()).thenReturn("supplier"); + + assertThat(SpringUtils.defaultIfNull(null, mockSupplier)).isEqualTo("supplier"); + + verify(mockSupplier, times(1)).get(); + } + + @Test + public void dereferenceBean() { + assertThat(SpringUtils.dereferenceBean("example")).isEqualTo("&example"); + } + @Test public void equalsIgnoreNullIsTrue() { assertThat(SpringUtils.equalsIgnoreNull(null, null)).isTrue(); @@ -123,13 +174,18 @@ public class SpringUtilsUnitTests { } @Test - public void nullOrEqualsWithNullIsTrue() { - assertThat(SpringUtils.nullOrEquals(null, "test")).isTrue(); + public void nullOrEqualsWithEqualObjectsIsTrue() { + assertThat(SpringUtils.nullOrEquals("test", "test")).isTrue(); } @Test - public void nullOrEqualsWithEqualObjectsIsTrue() { - assertThat(SpringUtils.nullOrEquals("test", "test")).isTrue(); + public void nullOrEqualsWithNonNullObjectAndNullIsFalse() { + assertThat(SpringUtils.nullOrEquals("test", null)).isFalse(); + } + + @Test + public void nullOrEqualsWithNullIsTrue() { + assertThat(SpringUtils.nullOrEquals(null, "test")).isTrue(); } @Test @@ -142,11 +198,6 @@ public class SpringUtilsUnitTests { assertThat(SpringUtils.nullSafeEquals("test", "test")).isTrue(); } - @Test - public void nullSafeEqualsWithUnequalObjectsIsFalse() { - assertThat(SpringUtils.nullSafeEquals("test", "mock")).isFalse(); - } - @Test public void nullSafeEqualsWithNullObjectsIsFalse() { assertThat(SpringUtils.nullSafeEquals(null, "test")).isFalse(); @@ -154,7 +205,24 @@ public class SpringUtilsUnitTests { } @Test - public void dereferenceBean() { - assertThat(SpringUtils.dereferenceBean("example")).isEqualTo("&example"); + public void nullSafeEqualsWithUnequalObjectsIsFalse() { + assertThat(SpringUtils.nullSafeEquals("test", "mock")).isFalse(); + } + + @Test + public void safeGetValueReturnsSupplierValue() { + assertThat(SpringUtils.safeGetValue(() -> "test", null)).isEqualTo("test"); + } + + @Test + public void safeGetValueReturnsDefaultValue() { + assertThat(SpringUtils.safeGetValue(() -> { throw new RuntimeException("error"); }, "test")) + .isEqualTo("test"); + } + + @Test + public void safeGetValueReturnsNull() { + assertThat(SpringUtils.safeGetValue(() -> { throw new RuntimeException("error"); })) + .isEqualTo("test"); } }