/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.processors.cache.query.continuous;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Factory;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
import javax.cache.event.CacheEntryCreatedListener;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryExpiredListener;
import javax.cache.event.CacheEntryListenerException;
import javax.cache.event.CacheEntryRemovedListener;
import javax.cache.event.CacheEntryUpdatedListener;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheEntryEventSerializableFilter;
import org.apache.ignite.cache.affinity.Affinity;
import org.apache.ignite.cache.query.CacheQueryEntryEvent;
import org.apache.ignite.cache.query.ContinuousQuery;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.transactions.Transaction;
import org.apache.ignite.transactions.TransactionConcurrency;
import org.apache.ignite.transactions.TransactionIsolation;
import org.jetbrains.annotations.NotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
import static org.apache.ignite.cache.CacheMode.REPLICATED;
import static org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryFactoryFilterRandomOperationTest.NonSerializableFilter.isAccepted;
import static org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryRandomOperationsTest.ContinuousDeploy.CLIENT;
import static org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryRandomOperationsTest.ContinuousDeploy.SERVER;
import static org.apache.ignite.transactions.TransactionIsolation.READ_COMMITTED;
import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ;
import static org.apache.ignite.transactions.TransactionIsolation.SERIALIZABLE;
/**
*
*/
public class CacheContinuousQueryFactoryFilterRandomOperationTest extends CacheContinuousQueryRandomOperationsTest {
/** */
private static final int NODES = 5;
/** */
private static final int KEYS = 50;
/** */
private static final int VALS = 10;
/** */
public static final int ITERATION_CNT = 40;
/**
* @throws Exception If failed.
*/
public void testInternalQuery() throws Exception {
CacheConfiguration<Object, Object> ccfg = cacheConfiguration(REPLICATED,
1,
ATOMIC,
false);
final IgniteCache<Object, Object> cache = grid(0).createCache(ccfg);
UUID uuid = null;
try {
for (int i = 0; i < 10; i++)
cache.put(i, i);
final CountDownLatch latch = new CountDownLatch(5);
CacheEntryUpdatedListener lsnr = new CacheEntryUpdatedListener() {
@Override public void onUpdated(Iterable iterable) throws CacheEntryListenerException {
for (Object evt : iterable) {
latch.countDown();
log.info("Received event: " + evt);
}
}
};
uuid = grid(0).context().cache().cache(cache.getName()).context().continuousQueries()
.executeInternalQuery(lsnr, new SerializableFilter(), false, true, true);
for (int i = 10; i < 20; i++)
cache.put(i, i);
assertTrue(latch.await(3, SECONDS));
}
finally {
if (uuid != null)
grid(0).context().cache().cache(cache.getName()).context().continuousQueries()
.cancelInternalQuery(uuid);
grid(0).destroyCache(ccfg.getName());
}
}
/** {@inheritDoc} */
@Override protected void doTestContinuousQuery(CacheConfiguration<Object, Object> ccfg, ContinuousDeploy deploy)
throws Exception {
ignite(0).createCache(ccfg);
try {
long seed = System.currentTimeMillis();
Random rnd = new Random(seed);
log.info("Random seed: " + seed);
List<BlockingQueue<CacheEntryEvent<?, ?>>> evtsQueues = new ArrayList<>();
Collection<QueryCursor<?>> curs = new ArrayList<>();
Collection<T2<Integer, MutableCacheEntryListenerConfiguration>> lsnrCfgs = new ArrayList<>();
if (deploy == CLIENT)
evtsQueues.add(registerListener(ccfg.getName(), NODES - 1, curs, lsnrCfgs, rnd.nextBoolean()));
else if (deploy == SERVER)
evtsQueues.add(registerListener(ccfg.getName(), rnd.nextInt(NODES - 1), curs, lsnrCfgs,
rnd.nextBoolean()));
else {
boolean isSync = rnd.nextBoolean();
for (int i = 0; i < NODES - 1; i++)
evtsQueues.add(registerListener(ccfg.getName(), i, curs, lsnrCfgs, isSync));
}
ConcurrentMap<Object, Object> expData = new ConcurrentHashMap<>();
Map<Integer, Long> partCntr = new ConcurrentHashMap<>();
try {
for (int i = 0; i < ITERATION_CNT; i++) {
if (i % 10 == 0)
log.info("Iteration: " + i);
for (int idx = 0; idx < NODES; idx++)
randomUpdate(rnd, evtsQueues, expData, partCntr, grid(idx).cache(ccfg.getName()));
}
}
finally {
for (QueryCursor<?> cur : curs)
cur.close();
for (T2<Integer, MutableCacheEntryListenerConfiguration> e : lsnrCfgs)
grid(e.get1()).cache(ccfg.getName()).deregisterCacheEntryListener(e.get2());
}
}
finally {
ignite(0).destroyCache(ccfg.getName());
}
}
/**
* @param cacheName Cache name.
* @param nodeIdx Node index.
* @param curs Cursors.
* @param lsnrCfgs Listener configurations.
* @return Event queue
*/
private BlockingQueue<CacheEntryEvent<?, ?>> registerListener(String cacheName,
int nodeIdx,
Collection<QueryCursor<?>> curs,
Collection<T2<Integer, MutableCacheEntryListenerConfiguration>> lsnrCfgs,
boolean sync) {
final BlockingQueue<CacheEntryEvent<?, ?>> evtsQueue = new ArrayBlockingQueue<>(50_000);
if (ThreadLocalRandom.current().nextBoolean()) {
MutableCacheEntryListenerConfiguration<QueryTestKey, QueryTestValue> lsnrCfg =
new MutableCacheEntryListenerConfiguration<>(
FactoryBuilder.factoryOf(new LocalNonSerialiseListener() {
@Override protected void onEvents(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) {
for (CacheEntryEvent<?, ?> evt : evts)
evtsQueue.add(evt);
}
}),
createFilterFactory(),
true,
sync
);
grid(nodeIdx).cache(cacheName).registerCacheEntryListener((CacheEntryListenerConfiguration)lsnrCfg);
lsnrCfgs.add(new T2<Integer, MutableCacheEntryListenerConfiguration>(nodeIdx, lsnrCfg));
}
else {
ContinuousQuery<QueryTestKey, QueryTestValue> qry = new ContinuousQuery<>();
qry.setLocalListener(new CacheEntryUpdatedListener<QueryTestKey, QueryTestValue>() {
@Override public void onUpdated(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) throws CacheEntryListenerException {
for (CacheEntryEvent<?, ?> evt : evts)
evtsQueue.add(evt);
}
});
qry.setRemoteFilterFactory(createFilterFactory());
QueryCursor<?> cur = grid(nodeIdx).cache(cacheName).query(qry);
curs.add(cur);
}
return evtsQueue;
}
/**
* @return Filter factory.
*/
@NotNull protected Factory<? extends CacheEntryEventFilter<QueryTestKey, QueryTestValue>> createFilterFactory() {
return new FilterFactory();
}
/**
* @param rnd Random generator.
* @param evtsQueues Events queue.
* @param expData Expected cache data.
* @param partCntr Partition counter.
* @param cache Cache.
* @throws Exception If failed.
*/
private void randomUpdate(
Random rnd,
List<BlockingQueue<CacheEntryEvent<?, ?>>> evtsQueues,
ConcurrentMap<Object, Object> expData,
Map<Integer, Long> partCntr,
IgniteCache<Object, Object> cache)
throws Exception {
Object key = new QueryTestKey(rnd.nextInt(KEYS));
Object newVal = value(rnd);
Object oldVal = expData.get(key);
int op = rnd.nextInt(11);
Ignite ignite = cache.unwrap(Ignite.class);
Transaction tx = null;
if (cache.getConfiguration(CacheConfiguration.class).getAtomicityMode() == TRANSACTIONAL && rnd.nextBoolean())
tx = ignite.transactions().txStart(txRandomConcurrency(rnd), txRandomIsolation(rnd));
try {
// log.info("Random operation [key=" + key + ", op=" + op + ']');
switch (op) {
case 0: {
cache.put(key, newVal);
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
break;
}
case 1: {
cache.getAndPut(key, newVal);
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
break;
}
case 2: {
cache.remove(key);
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, null, oldVal);
expData.remove(key);
break;
}
case 3: {
cache.getAndRemove(key);
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, null, oldVal);
expData.remove(key);
break;
}
case 4: {
cache.invoke(key, new EntrySetValueProcessor(newVal, rnd.nextBoolean()));
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
break;
}
case 5: {
cache.invoke(key, new EntrySetValueProcessor(null, rnd.nextBoolean()));
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, null, oldVal);
expData.remove(key);
break;
}
case 6: {
cache.putIfAbsent(key, newVal);
if (tx != null)
tx.commit();
if (oldVal == null) {
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, null);
expData.put(key, newVal);
}
else
checkNoEvent(evtsQueues);
break;
}
case 7: {
cache.getAndPutIfAbsent(key, newVal);
if (tx != null)
tx.commit();
if (oldVal == null) {
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, null);
expData.put(key, newVal);
}
else
checkNoEvent(evtsQueues);
break;
}
case 8: {
cache.replace(key, newVal);
if (tx != null)
tx.commit();
if (oldVal != null) {
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
}
else
checkNoEvent(evtsQueues);
break;
}
case 9: {
cache.getAndReplace(key, newVal);
if (tx != null)
tx.commit();
if (oldVal != null) {
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
}
else
checkNoEvent(evtsQueues);
break;
}
case 10: {
if (oldVal != null) {
Object replaceVal = value(rnd);
boolean success = replaceVal.equals(oldVal);
if (success) {
cache.replace(key, replaceVal, newVal);
if (tx != null)
tx.commit();
updatePartitionCounter(cache, key, partCntr);
waitAndCheckEvent(evtsQueues, partCntr, affinity(cache), key, newVal, oldVal);
expData.put(key, newVal);
}
else {
cache.replace(key, replaceVal, newVal);
if (tx != null)
tx.commit();
checkNoEvent(evtsQueues);
}
}
else {
cache.replace(key, value(rnd), newVal);
if (tx != null)
tx.commit();
checkNoEvent(evtsQueues);
}
break;
}
default:
fail("Op:" + op);
}
}
finally {
if (tx != null)
tx.close();
}
}
/**
* @param rnd {@link Random}.
* @return {@link TransactionIsolation}.
*/
private TransactionIsolation txRandomIsolation(Random rnd) {
int val = rnd.nextInt(3);
if (val == 0)
return READ_COMMITTED;
else if (val == 1)
return REPEATABLE_READ;
else
return SERIALIZABLE;
}
/**
* @param rnd {@link Random}.
* @return {@link TransactionConcurrency}.
*/
private TransactionConcurrency txRandomConcurrency(Random rnd) {
return rnd.nextBoolean() ? TransactionConcurrency.OPTIMISTIC : TransactionConcurrency.PESSIMISTIC;
}
/**
* @param cache Cache.
* @param key Key
* @param cntrs Partition counters.
*/
private void updatePartitionCounter(IgniteCache<Object, Object> cache, Object key, Map<Integer, Long> cntrs) {
Affinity<Object> aff = cache.unwrap(Ignite.class).affinity(cache.getName());
int part = aff.partition(key);
Long partCntr = cntrs.get(part);
if (partCntr == null)
partCntr = 0L;
cntrs.put(part, ++partCntr);
}
/**
* @param rnd Random generator.
* @return Cache value.
*/
private static Object value(Random rnd) {
return new QueryTestValue(rnd.nextInt(VALS));
}
/**
* @param evtsQueues Event queue.
* @param partCntrs Partition counters.
* @param aff Affinity function.
* @param key Key.
* @param val Value.
* @param oldVal Old value.
* @throws Exception If failed.
*/
private void waitAndCheckEvent(List<BlockingQueue<CacheEntryEvent<?, ?>>> evtsQueues,
Map<Integer, Long> partCntrs,
Affinity<Object> aff,
Object key,
Object val,
Object oldVal)
throws Exception {
if ((val == null && oldVal == null
|| (val != null && !isAccepted((QueryTestValue)val)))) {
checkNoEvent(evtsQueues);
return;
}
for (BlockingQueue<CacheEntryEvent<?, ?>> evtsQueue : evtsQueues) {
CacheEntryEvent<?, ?> evt = evtsQueue.poll(5, SECONDS);
assertNotNull("Failed to wait for event [key=" + key + ", val=" + val + ", oldVal=" + oldVal + ']', evt);
assertEquals(key, evt.getKey());
assertEquals(val, evt.getValue());
assertEquals(oldVal, evt.getOldValue());
long cntr = partCntrs.get(aff.partition(key));
CacheQueryEntryEvent qryEntryEvt = evt.unwrap(CacheQueryEntryEvent.class);
assertNotNull(cntr);
assertNotNull(qryEntryEvt);
assertEquals(cntr, qryEntryEvt.getPartitionUpdateCounter());
}
}
/**
* @param evtsQueues Event queue.
* @throws Exception If failed.
*/
private void checkNoEvent(List<BlockingQueue<CacheEntryEvent<?, ?>>> evtsQueues) throws Exception {
for (BlockingQueue<CacheEntryEvent<?, ?>> evtsQueue : evtsQueues) {
CacheEntryEvent<?, ?> evt = evtsQueue.poll(50, MILLISECONDS);
assertNull(evt);
}
}
/**
*
*/
protected static class NonSerializableFilter
implements CacheEntryEventSerializableFilter<CacheContinuousQueryRandomOperationsTest.QueryTestKey,
CacheContinuousQueryRandomOperationsTest.QueryTestValue>, Externalizable {
/** */
public NonSerializableFilter() {
// No-op.
}
/** {@inheritDoc} */
@Override public boolean evaluate(CacheEntryEvent<? extends QueryTestKey, ? extends QueryTestValue> evt) {
return isAccepted(evt.getValue());
}
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
fail("Entry filter should not be marshaled.");
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
fail("Entry filter should not be marshaled.");
}
/**
* @param val Value.
* @return {@code True} if value is even.
*/
public static boolean isAccepted(QueryTestValue val) {
return val == null || val.val1 % 2 == 0;
}
}
/**
*
*/
protected static class SerializableFilter implements CacheEntryEventSerializableFilter<Integer, Integer> {
/** */
public SerializableFilter() {
// No-op.
}
/** {@inheritDoc} */
@Override public boolean evaluate(CacheEntryEvent<? extends Integer, ? extends Integer> evt)
throws CacheEntryListenerException {
return isAccepted(evt.getValue());
}
/**
* @return {@code True} if value is even.
*/
public static boolean isAccepted(Integer val) {
return val == null || val % 2 == 0;
}
}
/**
*
*/
protected static class FilterFactory implements Factory<NonSerializableFilter> {
/** {@inheritDoc} */
@Override public NonSerializableFilter create() {
return new NonSerializableFilter();
}
}
/**
*
*/
public abstract class LocalNonSerialiseListener implements
CacheEntryUpdatedListener<QueryTestKey, QueryTestValue>,
CacheEntryCreatedListener<QueryTestKey, QueryTestValue>,
CacheEntryExpiredListener<QueryTestKey, QueryTestValue>,
CacheEntryRemovedListener<QueryTestKey, QueryTestValue>,
Externalizable {
/** */
public LocalNonSerialiseListener() {
// No-op.
}
/** {@inheritDoc} */
@Override public void onCreated(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) throws CacheEntryListenerException {
onEvents(evts);
}
/** {@inheritDoc} */
@Override public void onExpired(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) throws CacheEntryListenerException {
onEvents(evts);
}
/** {@inheritDoc} */
@Override public void onRemoved(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) throws CacheEntryListenerException {
onEvents(evts);
}
/** {@inheritDoc} */
@Override public void onUpdated(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts) throws CacheEntryListenerException {
onEvents(evts);
}
/**
* @param evts Events.
*/
protected abstract void onEvents(Iterable<CacheEntryEvent<? extends QueryTestKey,
? extends QueryTestValue>> evts);
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
throw new UnsupportedOperationException("Failed. Listener should not be marshaled.");
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
throw new UnsupportedOperationException("Failed. Listener should not be unmarshaled.");
}
}
}