Add Mock Object support for Apache Geode Lucene integration.

This commit is contained in:
John Blum
2018-04-30 15:45:16 -07:00
parent 47288f39f1
commit dfeb09c13e
3 changed files with 364 additions and 27 deletions

View File

@@ -29,8 +29,12 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.tests.util.IOUtils.doSafeIo;
import static org.springframework.data.gemfire.tests.util.ObjectUtils.doSafeOperation;
import static org.springframework.data.gemfire.tests.util.ObjectUtils.rethrowAsRuntimeException;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.asSet;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.NOT_SUPPORTED;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
@@ -38,7 +42,6 @@ import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newI
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -53,8 +56,10 @@ import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -97,6 +102,13 @@ import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.client.PoolFactory;
import org.apache.geode.cache.control.ResourceManager;
import org.apache.geode.cache.execute.RegionFunctionContext;
import org.apache.geode.cache.lucene.LuceneIndex;
import org.apache.geode.cache.lucene.LuceneIndexFactory;
import org.apache.geode.cache.lucene.LuceneQuery;
import org.apache.geode.cache.lucene.LuceneQueryFactory;
import org.apache.geode.cache.lucene.LuceneQueryProvider;
import org.apache.geode.cache.lucene.LuceneSerializer;
import org.apache.geode.cache.lucene.LuceneService;
import org.apache.geode.cache.query.CqAttributes;
import org.apache.geode.cache.query.CqQuery;
import org.apache.geode.cache.query.Index;
@@ -116,8 +128,8 @@ import org.apache.geode.cache.wan.GatewayTransportFilter;
import org.apache.geode.compression.Compressor;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.concurrent.ConcurrentHashSet;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.lucene.analysis.Analyzer;
import org.mockito.ArgumentMatchers;
import org.mockito.stubbing.Answer;
import org.springframework.data.gemfire.IndexType;
@@ -131,24 +143,44 @@ import org.springframework.util.Assert;
* Mock GemFire Objects (e.g. {@link Cache}, {@link ClientCache}, {@link Region}, etc).
*
* @author John Blum
* @see java.util.Properties
* @see org.apache.geode.cache.AttributesMutator
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.CacheFactory
* @see org.apache.geode.cache.CustomExpiry
* @see org.apache.geode.cache.DiskStore
* @see org.apache.geode.cache.DiskStoreFactory
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.EvictionAttributes
* @see org.apache.geode.cache.EvictionAttributesMutator
* @see org.apache.geode.cache.ExpirationAttributes
* @see org.apache.geode.cache.PartitionAttributes
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.RegionAttributes
* @see org.apache.geode.cache.RegionFactory
* @see org.apache.geode.cache.RegionService
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.ClientCacheFactory
* @see org.apache.geode.cache.client.ClientRegionFactory
* @see org.apache.geode.cache.client.Pool
* @see org.apache.geode.cache.client.PoolFactory
* @see org.apache.geode.cache.control.ResourceManager
* @see org.apache.geode.cache.lucene.LuceneIndex
* @see org.apache.geode.cache.lucene.LuceneIndexFactory
* @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.LuceneSerializer
* @see org.apache.geode.cache.lucene.LuceneService
* @see org.apache.geode.cache.query.CqAttributes
* @see org.apache.geode.cache.query.CqQuery
* @see org.apache.geode.cache.query.Index
* @see org.apache.geode.cache.query.IndexStatistics
* @see org.apache.geode.cache.query.Query
* @see org.apache.geode.cache.query.QueryService
* @see org.apache.geode.cache.query.QueryStatistics
* @see org.apache.geode.cache.server.CacheServer
* @see org.apache.geode.cache.server.ClientSubscriptionConfig
* @see org.apache.geode.cache.wan.GatewayReceiver
@@ -157,6 +189,7 @@ import org.springframework.util.Assert;
* @see org.apache.geode.cache.wan.GatewaySenderFactory
* @see org.apache.geode.distributed.DistributedMember
* @see org.apache.geode.distributed.DistributedSystem
* @see org.apache.geode.pdx.PdxSerializer
* @see org.mockito.Mockito
* @see org.springframework.data.gemfire.tests.mock.MockObjectsSupport
* @since 0.0.1
@@ -272,26 +305,6 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
}).orElse(DataPolicy.DEFAULT);
}
/**
* Executes the given {@link IoExceptionThrowingOperation}, handling any {@link IOException IOExceptions} thrown
* during normal IO processing.
*
* @param operation {@link IoExceptionThrowingOperation} to execute.
* @return a boolean indicating whether the IO operation was successful, or {@literal false} if the IO operation
* threw an {@link IOException}.
* @see IOException
*/
private static boolean doSafeIo(IoExceptionThrowingOperation operation) {
try {
operation.doIo();
return true;
}
catch (IOException cause) {
return false;
}
}
/**
* Determines whether the given {@link Region} is a root {@link Region}.
*
@@ -1548,13 +1561,13 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
return regionService;
}
// TODO write more mocking logic for the QueryService interface
// TODO write additional mocking logic for the QueryService interface
public static QueryService mockQueryService() {
QueryService mockQueryService = mock(QueryService.class);
Set<CqQuery> cqQueries = new ConcurrentHashSet<>();
Set<Index> indexes = new ConcurrentHashSet<>();
Set<CqQuery> cqQueries = new ConcurrentSkipListSet<>();
Set<Index> indexes = new ConcurrentSkipListSet<>();
try {
when(mockQueryService.getCqs()).thenAnswer(invocation -> cqQueries.toArray(new CqQuery[cqQueries.size()]));
@@ -1787,6 +1800,200 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
return mock(IndexStatistics.class, mockObjectIdentifier(name));
}
public static LuceneIndexFactory mockLuceneIndexFactory() {
return mockLuceneIndexFactory(null);
}
private static LuceneIndexFactory mockLuceneIndexFactory(Map<LuceneIndexKey, LuceneIndex> luceneIndexes) {
LuceneIndexFactory mockLuceneIndexFactory = mock(LuceneIndexFactory.class);
AtomicReference<LuceneSerializer> luceneSerializerReference = new AtomicReference<>(null);
Map<String, Analyzer> fieldAnalyzers = new ConcurrentHashMap<>();
Set<String> fields = new CopyOnWriteArraySet<>();
when(mockLuceneIndexFactory.addField(anyString())).thenAnswer(invocation -> {
String fieldName = invocation.getArgument(0);
fields.add(fieldName);
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.addField(anyString(), any(Analyzer.class))).thenAnswer(invocation -> {
String fieldName = invocation.getArgument(0);
Analyzer analyzer = invocation.getArgument(1);
fieldAnalyzers.put(fieldName, analyzer);
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setFields(ArgumentMatchers.<String[]>any())).thenAnswer(invocation -> {
String[] fieldsArgument = invocation.getArgument(0);
fields.clear();
fields.addAll(Arrays.asList(nullSafeArray(fieldsArgument, String.class)));
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setFields(any(Map.class))).thenAnswer(invocation -> {
Map<String, Analyzer> fieldAnalyzersArgument = invocation.getArgument(0);
fieldAnalyzers.clear();
fieldAnalyzers.putAll(nullSafeMap(fieldAnalyzers));
return mockLuceneIndexFactory;
});
when(mockLuceneIndexFactory.setLuceneSerializer(any(LuceneSerializer.class))).thenAnswer(invocation -> {
Optional.ofNullable(invocation.<LuceneSerializer>getArgument(0))
.map(luceneSerializer -> {
luceneSerializerReference.set(luceneSerializer);
return luceneSerializer;
})
.orElseGet(() -> {
luceneSerializerReference.set(null);
return null;
});
return mockLuceneIndexFactory;
});
doAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionPath = invocation.getArgument(1);
LuceneIndexKey key = LuceneIndexKey.of(indexName, regionPath);
LuceneIndex mockLuceneIndex = mock(LuceneIndex.class, key.toString());
when(mockLuceneIndex.getFieldAnalyzers()).thenReturn(Collections.unmodifiableMap(fieldAnalyzers));
when(mockLuceneIndex.getFieldNames()).thenAnswer(in -> fields.toArray(new String[fields.size()]));
when(mockLuceneIndex.getLuceneSerializer()).thenAnswer(in -> luceneSerializerReference.get());
when(mockLuceneIndex.getName()).thenReturn(indexName);
when(mockLuceneIndex.getRegionPath()).thenReturn(regionPath);
Optional.ofNullable(luceneIndexes).ifPresent(it -> it.put(key, mockLuceneIndex));
return null;
}).when(mockLuceneIndexFactory).create(anyString(), anyString());
return mockLuceneIndexFactory;
}
public static LuceneQueryFactory mockLuceneQueryFactory() {
LuceneQueryFactory mockLuceneQueryFactory = mock(LuceneQueryFactory.class);
AtomicInteger limit = new AtomicInteger(LuceneQueryFactory.DEFAULT_LIMIT);
AtomicInteger pageSize = new AtomicInteger(LuceneQueryFactory.DEFAULT_PAGESIZE);
when(mockLuceneQueryFactory.setLimit(anyInt())).thenAnswer(invocation -> {
limit.set(invocation.getArgument(0));
return mockLuceneQueryFactory;
});
when(mockLuceneQueryFactory.setPageSize(anyInt())).thenAnswer(invocation -> {
pageSize.set(invocation.getArgument(0));
return mockLuceneQueryFactory;
});
when(mockLuceneQueryFactory.create(anyString(), anyString(), any(LuceneQueryProvider.class)))
.thenAnswer(invocation -> mockLuceneQuery(limit.get(), pageSize.get()));
when(mockLuceneQueryFactory.create(anyString(), anyString(), anyString(), anyString()))
.thenAnswer(invocation -> mockLuceneQuery(limit.get(), pageSize.get()));
return mockLuceneQueryFactory;
}
private static LuceneQuery mockLuceneQuery(int limit, int pageSize) {
LuceneQuery mockLuceneQuery = mock(LuceneQuery.class);
when(mockLuceneQuery.getLimit()).thenReturn(limit);
when(mockLuceneQuery.getPageSize()).thenReturn(pageSize);
doSafeOperation(() -> when(mockLuceneQuery.findKeys())
.thenReturn(Collections.emptySet()), Collections.emptySet());
rethrowAsRuntimeException(() -> when(mockLuceneQuery.findPages())
.thenThrow(newUnsupportedOperationException("Operation Not Supported!")));
doSafeOperation(() -> when(mockLuceneQuery.findResults())
.thenReturn(Collections.emptyList()), Collections.emptyList());
doSafeOperation(() -> when(mockLuceneQuery.findValues())
.thenReturn(Collections.emptyList()), Collections.emptyList());
return mockLuceneQuery;
}
public static LuceneService mockLuceneService(Cache mockCache) {
LuceneService mockLuceneService = mock(LuceneService.class);
Map<LuceneIndexKey, LuceneIndex> luceneIndexes = new ConcurrentHashMap<>();
when(mockLuceneService.createIndexFactory())
.thenAnswer(invocation -> mockLuceneIndexFactory(luceneIndexes));
when(mockLuceneService.createLuceneQueryFactory())
.thenAnswer(invocation -> mockLuceneQueryFactory());
doAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionName = invocation .getArgument(1);
luceneIndexes.remove(LuceneIndexKey.of(indexName, regionName));
return null;
}).when(mockLuceneService).destroyIndex(anyString(), anyString());
doAnswer(invocation -> {
String regionPath = invocation.getArgument(0);
luceneIndexes.keySet().stream().filter(key -> key.getRegionPath().equals(regionPath))
.collect(Collectors.toSet()).forEach(key -> luceneIndexes.remove(key));
return null;
}).when(mockLuceneService).destroyIndexes(anyString());
when(mockLuceneService.getAllIndexes()).thenAnswer(invocation ->
Collections.unmodifiableCollection(luceneIndexes.values()));
when(mockLuceneService.getCache()).thenReturn(mockCache);
when(mockLuceneService.getIndex(anyString(), anyString())).thenAnswer(invocation -> {
String indexName = invocation.getArgument(0);
String regionPath = invocation.getArgument(1);
return luceneIndexes.get(LuceneIndexKey.of(indexName, regionPath));
});
doSafeOperation(() ->
when(mockLuceneService.waitUntilFlushed(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
.thenReturn(true));
return mockLuceneService;
}
@SuppressWarnings("unchecked")
public static <K, V> Region<K, V> mockRegion(RegionService regionService, String name,
RegionAttributes<K, V> regionAttributes) {
@@ -2565,7 +2772,70 @@ public abstract class GemFireMockObjectsSupport extends MockObjectsSupport {
return clientCacheFactorySpy;
}
protected interface IoExceptionThrowingOperation {
void doIo() throws IOException;
public static class LuceneIndexKey {
private final String indexName;
private final String regionPath;
public static LuceneIndexKey of(String indexName, Region<?, ?> region) {
Assert.notNull(region, "Region is required");
return of(indexName, region.getFullPath());
}
public static LuceneIndexKey of(String indexName, String regionPath) {
return new LuceneIndexKey(indexName, regionPath);
}
private LuceneIndexKey(String indexName, String regionPath) {
Assert.hasText(indexName, String.format("LuceneIndex name [%s] is required", indexName));
Assert.hasText(regionPath, String.format("Region path [%s] is required", regionPath));
this.indexName = indexName;
this.regionPath = regionPath;
}
protected String getIndexName() {
return this.indexName;
}
protected String getRegionPath() {
return this.regionPath;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof LuceneIndexKey)) {
return false;
}
LuceneIndexKey that = (LuceneIndexKey) obj;
return this.getIndexName().equals(that.getIndexName())
&& this.getRegionPath().equals(that.getRegionPath());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + getIndexName().hashCode();
hashValue = 37 * hashValue + getRegionPath().hashCode();
return hashValue;
}
@Override
public String toString() {
return String.format("%1$s.%2$s", getRegionPath(), getIndexName());
}
}
}

View File

@@ -58,6 +58,26 @@ public abstract class IOUtils {
return false;
}
/**
* Executes the given {@link IoExceptionThrowingOperation}, handling any {@link IOException IOExceptions} thrown
* during normal IO processing.
*
* @param operation {@link IoExceptionThrowingOperation} to execute.
* @return a boolean indicating whether the IO operation was successful, or {@literal false} if the IO operation
* threw an {@link IOException}.
* @see IOException
*/
public static boolean doSafeIo(IoExceptionThrowingOperation operation) {
try {
operation.doIo();
return true;
}
catch (IOException cause) {
return false;
}
}
@SuppressWarnings("unchecked")
public static <T> T deserializeObject(byte[] objectBytes) throws IOException, ClassNotFoundException {
@@ -92,4 +112,8 @@ public abstract class IOUtils {
IOUtils.close(objectOutputStream);
}
}
public interface IoExceptionThrowingOperation {
void doIo() throws IOException;
}
}

View File

@@ -0,0 +1,43 @@
package org.springframework.data.gemfire.tests.util;
/**
* {@link ObjectUtils} is a utility class for working with {@link Object objects}.
*
* @author John Blum
* @see java.lang.Object
* @since 1.0.0
*/
@SuppressWarnings("all")
public abstract class ObjectUtils {
public static <T> T doSafeOperation(ExceptionThrowingOperation<T> operation) {
return doSafeOperation(operation, null);
}
public static <T> T doSafeOperation(ExceptionThrowingOperation<T> operation, T defaultValue) {
try {
return operation.doExceptionThrowingOperation();
}
catch (Exception ignore) {
return defaultValue;
}
}
public static <T> T rethrowAsRuntimeException(ExceptionThrowingOperation<T> operation) {
try {
return operation.doExceptionThrowingOperation();
}
catch (RuntimeException cause) {
throw cause;
}
catch (Exception cause) {
throw new RuntimeException(cause);
}
}
public interface ExceptionThrowingOperation<T> {
T doExceptionThrowingOperation() throws Exception;
}
}