package org.infinispan.client.hotrod.event;
import static org.infinispan.query.dsl.Expression.max;
import static org.infinispan.query.dsl.Expression.param;
import static org.infinispan.server.hotrod.test.HotRodTestingUtil.hotRodCacheConfiguration;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.Search;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.client.hotrod.marshall.EmbeddedUserMarshaller;
import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller;
import org.infinispan.client.hotrod.query.testdomain.protobuf.UserPB;
import org.infinispan.client.hotrod.query.testdomain.protobuf.marshallers.GenderMarshaller;
import org.infinispan.client.hotrod.query.testdomain.protobuf.marshallers.MarshallerRegistration;
import org.infinispan.client.hotrod.test.MultiHotRodServersTest;
import org.infinispan.commons.util.Util;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.Index;
import org.infinispan.query.api.continuous.ContinuousQuery;
import org.infinispan.query.api.continuous.ContinuousQueryListener;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.dsl.embedded.testdomain.User;
import org.infinispan.query.remote.CompatibilityProtoStreamMarshaller;
import org.infinispan.query.remote.ProtobufMetadataManager;
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
import org.infinispan.query.remote.impl.filter.IckleContinuousQueryProtobufCacheEventFilterConverterFactory;
import org.infinispan.test.TestingUtil;
import org.infinispan.util.ControlledTimeService;
import org.infinispan.util.KeyValuePair;
import org.infinispan.util.TimeService;
import org.testng.annotations.Test;
/**
* Test remote continuous query in compat mode.
*
* @author anistor@redhat.com
* @since 9.0
*/
@Test(groups = "functional", testName = "client.hotrod.event.EmbeddedCompatContinuousQueryTest")
public class EmbeddedCompatContinuousQueryTest extends MultiHotRodServersTest {
private final int NUM_NODES = 5;
private RemoteCache<String, User> remoteCache;
private ControlledTimeService timeService = new ControlledTimeService();
@Override
protected void createCacheManagers() throws Throwable {
ConfigurationBuilder cfgBuilder = getConfigurationBuilder();
createHotRodServers(NUM_NODES, cfgBuilder);
waitForClusterToForm();
// Register the filter/converter factory. This should normally be discovered by the server via class path instead
// of being added manually here, but this is ok in a test.
IckleContinuousQueryProtobufCacheEventFilterConverterFactory factory = new IckleContinuousQueryProtobufCacheEventFilterConverterFactory();
for (int i = 0; i < NUM_NODES; i++) {
server(i).addCacheEventFilterConverterFactory(IckleContinuousQueryProtobufCacheEventFilterConverterFactory.FACTORY_NAME, factory);
TestingUtil.replaceComponent(server(i).getCacheManager(), TimeService.class, timeService, true);
}
remoteCache = client(0).getCache();
//initialize server-side serialization context
RemoteCache<String, String> metadataCache = client(0).getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME);
metadataCache.put("sample_bank_account/bank.proto", Util.read(Util.getResourceAsStream("/sample_bank_account/bank.proto", getClass().getClassLoader())));
assertFalse(metadataCache.containsKey(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX));
for (int i = 0; i < NUM_NODES; i++) {
ProtobufMetadataManager protobufMetadataManager = manager(i).getGlobalComponentRegistry().getComponent(ProtobufMetadataManager.class);
protobufMetadataManager.registerMarshaller(new EmbeddedUserMarshaller());
protobufMetadataManager.registerMarshaller(new GenderMarshaller());
}
//initialize client-side serialization context
MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller.getSerializationContext(client(0)));
}
protected ConfigurationBuilder getConfigurationBuilder() {
ConfigurationBuilder cfgBuilder = hotRodCacheConfiguration(getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false));
cfgBuilder.compatibility().enable().marshaller(new CompatibilityProtoStreamMarshaller());
cfgBuilder.indexing().index(Index.ALL)
.addProperty("default.directory_provider", "ram")
.addProperty("lucene_version", "LUCENE_CURRENT");
cfgBuilder.expiration().disableReaper();
return cfgBuilder;
}
@Override
protected org.infinispan.client.hotrod.configuration.ConfigurationBuilder createHotRodClientConfigurationBuilder(int serverPort) {
return super.createHotRodClientConfigurationBuilder(serverPort)
.marshaller(new ProtoStreamMarshaller());
}
/**
* Using grouping and aggregation with continuous query is not allowed.
*/
@Test(expectedExceptions = HotRodClientException.class, expectedExceptionsMessageRegExp = ".*ISPN028509:.*")
public void testDisallowGroupingAndAggregation() {
Query query = Search.getQueryFactory(remoteCache).from(UserPB.class)
.select(max("age"))
.having("age").gte(20)
.build();
ContinuousQuery<String, User> continuousQuery = Search.getContinuousQuery(remoteCache);
ContinuousQueryListener<String, Object[]> listener = new ContinuousQueryListener<String, Object[]>() {
};
continuousQuery.addContinuousQueryListener(query, listener);
}
@Test(enabled = false, description = "Disabled due to https://issues.jboss.org/browse/ISPN-6730")
public void testContinuousQuery() {
User user1 = new UserPB();
user1.setId(1);
user1.setName("John");
user1.setSurname("Doe");
user1.setGender(User.Gender.MALE);
user1.setAge(22);
user1.setAccountIds(new HashSet<>(Arrays.asList(1, 2)));
user1.setNotes("Lorem ipsum dolor sit amet");
User user2 = new UserPB();
user2.setId(2);
user2.setName("Spider");
user2.setSurname("Man");
user2.setGender(User.Gender.MALE);
user2.setAge(32);
user2.setAccountIds(Collections.singleton(3));
User user3 = new UserPB();
user3.setId(3);
user3.setName("Spider");
user3.setSurname("Woman");
user3.setGender(User.Gender.FEMALE);
user3.setAge(40);
remoteCache.clear();
remoteCache.put("user" + user1.getId(), user1);
remoteCache.put("user" + user2.getId(), user2);
remoteCache.put("user" + user3.getId(), user3);
assertEquals(3, remoteCache.size());
QueryFactory qf = Search.getQueryFactory(remoteCache);
Query query = qf.from(UserPB.class)
.having("age").lte(param("ageParam"))
.build()
.setParameter("ageParam", 32);
final BlockingQueue<KeyValuePair<String, User>> joined = new LinkedBlockingQueue<>();
final BlockingQueue<String> left = new LinkedBlockingQueue<>();
ContinuousQueryListener<String, User> listener = new ContinuousQueryListener<String, User>() {
@Override
public void resultJoining(String key, User value) {
joined.add(new KeyValuePair<>(key, value));
}
@Override
public void resultLeaving(String key) {
left.add(key);
}
};
ContinuousQuery<String, User> continuousQuery = Search.getContinuousQuery(remoteCache);
continuousQuery.addContinuousQueryListener(query, listener);
expectElementsInQueue(joined, 2, (kv) -> kv.getValue().getAge(), 32, 22);
expectElementsInQueue(left, 0);
user3.setAge(30);
remoteCache.put("user" + user3.getId(), user3);
expectElementsInQueue(joined, 1, (kv) -> kv.getValue().getAge(), 30);
expectElementsInQueue(left, 0);
user1.setAge(40);
user2.setAge(40);
user3.setAge(40);
remoteCache.put("user" + user1.getId(), user1);
remoteCache.put("user" + user2.getId(), user2);
remoteCache.put("user" + user3.getId(), user3);
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 3);
remoteCache.clear();
user1.setAge(21);
user2.setAge(22);
remoteCache.put("expiredUser1", user1, 5, TimeUnit.MILLISECONDS);
remoteCache.put("expiredUser2", user2, 5, TimeUnit.MILLISECONDS);
expectElementsInQueue(joined, 2);
expectElementsInQueue(left, 0);
timeService.advance(6);
assertNull(remoteCache.get("expiredUser1"));
assertNull(remoteCache.get("expiredUser2"));
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 2);
continuousQuery.removeContinuousQueryListener(listener);
user2.setAge(22);
remoteCache.put("user" + user2.getId(), user2);
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 0);
}
public void testContinuousQueryWithProjections() {
User user1 = new UserPB();
user1.setId(1);
user1.setName("John");
user1.setSurname("Doe");
user1.setGender(User.Gender.MALE);
user1.setAge(22);
user1.setAccountIds(new HashSet<>(Arrays.asList(1, 2)));
user1.setNotes("Lorem ipsum dolor sit amet");
User user2 = new UserPB();
user2.setId(2);
user2.setName("Spider");
user2.setSurname("Man");
user2.setGender(User.Gender.MALE);
user2.setAge(32);
user2.setAccountIds(Collections.singleton(3));
User user3 = new UserPB();
user3.setId(3);
user3.setName("Spider");
user3.setSurname("Woman");
user3.setGender(User.Gender.FEMALE);
user3.setAge(40);
remoteCache.clear();
remoteCache.put("user" + user1.getId(), user1);
remoteCache.put("user" + user2.getId(), user2);
remoteCache.put("user" + user3.getId(), user3);
assertEquals(3, remoteCache.size());
QueryFactory qf = Search.getQueryFactory(remoteCache);
Query query = qf.from(UserPB.class)
.select("age")
.having("age").lte(param("ageParam"))
.build()
.setParameter("ageParam", 32);
final BlockingQueue<KeyValuePair<String, Object[]>> joined = new LinkedBlockingQueue<>();
final BlockingQueue<String> left = new LinkedBlockingQueue<>();
ContinuousQueryListener<String, Object[]> listener = new ContinuousQueryListener<String, Object[]>() {
@Override
public void resultJoining(String key, Object[] value) {
joined.add(new KeyValuePair<>(key, value));
}
@Override
public void resultLeaving(String key) {
left.add(key);
}
};
ContinuousQuery<String, User> continuousQuery = Search.getContinuousQuery(remoteCache);
continuousQuery.addContinuousQueryListener(query, listener);
expectElementsInQueue(joined, 2, (kv) -> kv.getValue()[0], 32, 22);
expectElementsInQueue(left, 0);
user3.setAge(30);
remoteCache.put("user" + user3.getId(), user3);
expectElementsInQueue(joined, 1, (kv) -> kv.getValue()[0], 30);
expectElementsInQueue(left, 0);
user1.setAge(40);
user2.setAge(40);
user3.setAge(40);
remoteCache.put("user" + user1.getId(), user1);
remoteCache.put("user" + user2.getId(), user2);
remoteCache.put("user" + user3.getId(), user3);
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 3);
remoteCache.clear();
user1.setAge(21);
user2.setAge(22);
remoteCache.put("expiredUser1", user1, 5, TimeUnit.MILLISECONDS);
remoteCache.put("expiredUser2", user2, 5, TimeUnit.MILLISECONDS);
expectElementsInQueue(joined, 2);
expectElementsInQueue(left, 0);
timeService.advance(6);
assertNull(remoteCache.get("expiredUser1"));
assertNull(remoteCache.get("expiredUser2"));
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 2);
continuousQuery.removeContinuousQueryListener(listener);
user2.setAge(22);
remoteCache.put("user" + user2.getId(), user2);
expectElementsInQueue(joined, 0);
expectElementsInQueue(left, 0);
}
public void testContinuousQueryChangingParameter() {
User user1 = new UserPB();
user1.setId(1);
user1.setName("John");
user1.setSurname("Doe");
user1.setGender(User.Gender.MALE);
user1.setAge(22);
user1.setAccountIds(new HashSet<>(Arrays.asList(1, 2)));
user1.setNotes("Lorem ipsum dolor sit amet");
User user2 = new UserPB();
user2.setId(2);
user2.setName("Spider");
user2.setSurname("Man");
user2.setGender(User.Gender.MALE);
user2.setAge(32);
user2.setAccountIds(Collections.singleton(3));
User user3 = new UserPB();
user3.setId(3);
user3.setName("Spider");
user3.setSurname("Woman");
user3.setGender(User.Gender.FEMALE);
user3.setAge(40);
remoteCache.clear();
remoteCache.put("user" + user1.getId(), user1);
remoteCache.put("user" + user2.getId(), user2);
remoteCache.put("user" + user3.getId(), user3);
assertEquals(3, remoteCache.size());
QueryFactory qf = Search.getQueryFactory(remoteCache);
Query query = qf.from(UserPB.class)
.select("age")
.having("age").lte(param("ageParam"))
.build()
.setParameter("ageParam", 32);
final BlockingQueue<KeyValuePair<String, Object[]>> joined = new LinkedBlockingQueue<>();
final BlockingQueue<String> left = new LinkedBlockingQueue<>();
ContinuousQueryListener<String, Object[]> listener = new ContinuousQueryListener<String, Object[]>() {
@Override
public void resultJoining(String key, Object[] value) {
joined.add(new KeyValuePair<>(key, value));
}
@Override
public void resultLeaving(String key) {
left.add(key);
}
};
ContinuousQuery<String, User> cq = Search.getContinuousQuery(remoteCache);
cq.addContinuousQueryListener(query, listener);
expectElementsInQueue(joined, 2, (kv) -> kv.getValue()[0], 32, 22);
expectElementsInQueue(left, 0);
joined.clear();
left.clear();
cq.removeContinuousQueryListener(listener);
query.setParameter("ageParam", 40);
listener = new ContinuousQueryListener<String, Object[]>() {
@Override
public void resultJoining(String key, Object[] value) {
joined.add(new KeyValuePair<>(key, value));
}
@Override
public void resultLeaving(String key) {
left.add(key);
}
};
cq.addContinuousQueryListener(query, listener);
expectElementsInQueue(joined, 3);
expectElementsInQueue(left, 0);
cq.removeContinuousQueryListener(listener);
}
private <T> void expectElementsInQueue(BlockingQueue<T> queue, int numElements) {
expectElementsInQueue(queue, numElements, null);
}
private <T, R> void expectElementsInQueue(BlockingQueue<T> queue, int numElements, Function<T, R> valueTransformer, Object... expectedValue) {
List<Object> expectedValues;
if (expectedValue.length != 0) {
if (expectedValue.length != numElements) {
throw new IllegalArgumentException("The number of expected values must either match the number of expected elements or no expected values should be specified.");
}
expectedValues = new ArrayList<>(expectedValue.length);
Collections.addAll(expectedValues, expectedValue);
} else {
expectedValues = null;
}
for (int i = 0; i < numElements; i++) {
final T o;
try {
o = queue.poll(5, TimeUnit.SECONDS);
assertNotNull("Queue was empty after reading " + i + " elements!", o);
} catch (InterruptedException e) {
throw new AssertionError("Interrupted while waiting for condition", e);
}
if (expectedValues != null) {
Object v = valueTransformer != null ? valueTransformer.apply(o) : o;
boolean found = expectedValues.remove(v);
assertTrue("Expectation failed on element number " + i + ", unexpected value: " + v, found);
}
}
try {
// no more elements expected here
Object o = queue.poll(5, TimeUnit.SECONDS);
assertNull("No more elements expected in queue!", o);
} catch (InterruptedException e) {
throw new AssertionError("Interrupted while waiting for condition", e);
}
}
}