package org.infinispan.client.hotrod.impl.iteration;
import static org.testng.Assert.assertTrue;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.infinispan.client.hotrod.MetadataValue;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.test.MultiHotRodServersTest;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.distribution.ch.KeyPartitioner;
import org.infinispan.filter.AbstractKeyValueFilterConverter;
import org.infinispan.filter.KeyValueFilterConverter;
import org.infinispan.filter.KeyValueFilterConverterFactory;
import org.infinispan.filter.ParamKeyValueFilterConverterFactory;
import org.infinispan.marshall.core.ExternalPojo;
import org.infinispan.metadata.Metadata;
import org.infinispan.query.dsl.embedded.testdomain.hsearch.AccountHS;
import org.infinispan.test.TestingUtil;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* @author gustavonalle
* @since 8.0
*/
@Test(groups = "functional")
public abstract class BaseMultiServerRemoteIteratorTest extends MultiHotRodServersTest implements AbstractRemoteIteratorTest {
public static final int CACHE_SIZE = 20;
@BeforeMethod
public void clear() {
clients.forEach(c -> c.getCache().clear());
}
@Test
public void testBatchSizes() {
int maximumBatchSize = 120;
RemoteCache<Integer, AccountHS> cache = clients.get(0).getCache();
populateCache(CACHE_SIZE, this::newAccount, cache);
Set<Integer> expectedKeys = rangeAsSet(0, CACHE_SIZE);
for (int batch = 1; batch < maximumBatchSize; batch += 10) {
Set<Entry<Object, Object>> results = new HashSet<>(CACHE_SIZE);
CloseableIterator<Entry<Object, Object>> iterator = cache.retrieveEntries(null, null, batch);
iterator.forEachRemaining(results::add);
iterator.close();
assertEquals(CACHE_SIZE, results.size());
assertEquals(expectedKeys, extractKeys(results));
}
}
@Test
public void testEmptyCache() {
try (CloseableIterator<Entry<Object, Object>> iterator = client(0).getCache().retrieveEntries(null, null, 100)) {
assertFalse(iterator.hasNext());
assertFalse(iterator.hasNext());
}
}
@Test
public void testFilterBySegmentAndCustomFilter() {
String toHexConverterName = "toHexConverter";
servers.forEach(s -> s.addKeyValueFilterConverterFactory(toHexConverterName, new ToHexConverterFactory()));
RemoteCache<Integer, Integer> numbersCache = clients.get(0).getCache();
populateCache(CACHE_SIZE, i -> i, numbersCache);
Set<Integer> segments = setOf(15, 20, 25);
Set<Entry<Object, Object>> entries = new HashSet<>();
try (CloseableIterator<Entry<Object, Object>> iterator = numbersCache.retrieveEntries(toHexConverterName, segments, 10)) {
while (iterator.hasNext()) {
entries.add(iterator.next());
}
}
Set<String> values = extractValues(entries);
getKeysFromSegments(segments).forEach(i -> assertTrue(values.contains(Integer.toHexString(i))));
}
@Test
public void testFilterByCustomParamFilter() {
String factoryName = "substringConverter";
servers.forEach(s -> s.addKeyValueFilterConverterFactory(factoryName, new SubstringFilterFactory()));
int filterParam = 12;
RemoteCache<String, String> stringCache = clients.get(0).getCache();
IntStream.rangeClosed(0, CACHE_SIZE - 1).forEach(idx -> stringCache.put(String.valueOf(idx), UUID.randomUUID().toString()));
Set<Entry<Object, Object>> entries = extractEntries(stringCache.retrieveEntries(factoryName, new Object[]{filterParam}, null, 10));
Set<String> values = extractValues(entries);
assertForAll(values, s -> s.length() == filterParam);
// Omitting param, filter should use default value
entries = extractEntries(stringCache.retrieveEntries(factoryName, 10));
values = extractValues(entries);
assertForAll(values, s -> s.length() == SubstringFilterFactory.DEFAULT_LENGTH);
}
@Test
public void testFilterBySegment() {
RemoteCache<Integer, AccountHS> cache = clients.get(0).getCache();
populateCache(CACHE_SIZE, this::newAccount, cache);
Set<Integer> filterBySegments = rangeAsSet(30, 40);
Set<Entry<Object, Object>> entries = new HashSet<>();
try (CloseableIterator<Entry<Object, Object>> iterator = cache.retrieveEntries(null, filterBySegments, 10)) {
while (iterator.hasNext()) {
entries.add(iterator.next());
}
}
Marshaller marshaller = clients.get(0).getMarshaller();
KeyPartitioner keyPartitioner = TestingUtil.extractComponent(cache(0), KeyPartitioner.class);
assertKeysInSegment(entries, filterBySegments, marshaller, keyPartitioner::getSegment);
}
@Test
public void testRetrieveMetadata() throws Exception {
RemoteCache<Integer, AccountHS> cache = clients.get(0).getCache();
cache.put(1, newAccount(1), 1, TimeUnit.DAYS);
cache.put(2, newAccount(2), 2, TimeUnit.MINUTES, 30, TimeUnit.SECONDS);
cache.put(3, newAccount(3));
try (CloseableIterator<Entry<Object, MetadataValue<Object>>> iterator = cache.retrieveEntriesWithMetadata(null, 10)) {
Entry<Object, MetadataValue<Object>> entry = iterator.next();
if ((int) entry.getKey() == 1) {
assertEquals(24 * 3600, entry.getValue().getLifespan());
assertEquals(-1, entry.getValue().getMaxIdle());
}
if ((int) entry.getKey() == 2) {
assertEquals(2 * 60, entry.getValue().getLifespan());
assertEquals(30, entry.getValue().getMaxIdle());
}
if ((int) entry.getKey() == 3) {
assertEquals(-1, entry.getValue().getLifespan());
assertEquals(-1, entry.getValue().getMaxIdle());
}
}
}
static final class ToHexConverterFactory implements KeyValueFilterConverterFactory<Integer, Integer, String> {
@Override
public KeyValueFilterConverter<Integer, Integer, String> getFilterConverter() {
return new HexFilterConverter();
}
static class HexFilterConverter extends AbstractKeyValueFilterConverter<Integer, Integer, String> implements Serializable, ExternalPojo {
@Override
public String filterAndConvert(Integer key, Integer value, Metadata metadata) {
return Integer.toHexString(value);
}
}
}
static final class SubstringFilterFactory implements ParamKeyValueFilterConverterFactory<String, String, String> {
public static final int DEFAULT_LENGTH = 20;
@Override
public KeyValueFilterConverter<String, String, String> getFilterConverter(Object[] params) {
return new SubstringFilterConverter(params);
}
static class SubstringFilterConverter extends AbstractKeyValueFilterConverter<String, String, String> implements Serializable, ExternalPojo {
private final int length;
public SubstringFilterConverter(Object[] params) {
this.length = (int) (params == null || params.length == 0 ? DEFAULT_LENGTH : params[0]);
}
@Override
public String filterAndConvert(String key, String value, Metadata metadata) {
return value.substring(0, length);
}
}
}
private Set<Integer> getKeysFromSegments(Set<Integer> segments) {
RemoteCacheManager remoteCacheManager = clients.get(0);
RemoteCache<Integer, ?> cache = remoteCacheManager.getCache();
Marshaller marshaller = clients.get(0).getMarshaller();
KeyPartitioner keyPartitioner = TestingUtil.extractComponent(cache(0), KeyPartitioner.class);
Set<Integer> keys = cache.keySet();
return keys.stream()
.filter(b -> segments.contains(keyPartitioner.getSegment(toByteBuffer(b, marshaller))))
.collect(Collectors.toSet());
}
}