package org.infinispan.client.hotrod.event; import static org.infinispan.test.TestingUtil.assertAnyEquals; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import java.io.Serializable; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated; import org.infinispan.client.hotrod.annotation.ClientCacheEntryExpired; import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified; import org.infinispan.client.hotrod.annotation.ClientCacheEntryRemoved; import org.infinispan.client.hotrod.annotation.ClientListener; import org.infinispan.filter.NamedFactory; import org.infinispan.marshall.core.ExternalPojo; import org.infinispan.metadata.Metadata; import org.infinispan.notifications.cachelistener.filter.AbstractCacheEventFilterConverter; import org.infinispan.notifications.cachelistener.filter.CacheEventConverter; import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory; import org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverter; import org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverterFactory; import org.infinispan.notifications.cachelistener.filter.EventType; @ClientListener(converterFactoryName = "test-converter-factory") public abstract class CustomEventLogListener<K, E> implements RemoteCacheSupplier<K> { BlockingQueue<E> createdCustomEvents = new ArrayBlockingQueue<>(128); BlockingQueue<E> modifiedCustomEvents = new ArrayBlockingQueue<>(128); BlockingQueue<E> removedCustomEvents = new ArrayBlockingQueue<>(128); BlockingQueue<E> expiredCustomEvents = new ArrayBlockingQueue<>(128); private final RemoteCache<K, ?> remote; protected CustomEventLogListener(RemoteCache<K, ?> remote) { this.remote = remote; } @Override @SuppressWarnings("unchecked") public <V> RemoteCache<K, V> get() { return (RemoteCache<K, V>) remote; } public E pollEvent(ClientEvent.Type type) { try { E event = queue(type).poll(10, TimeUnit.SECONDS); assertNotNull(event); return event; } catch (InterruptedException e) { throw new AssertionError(e); } } protected BlockingQueue<E> queue(ClientEvent.Type type) { switch (type) { case CLIENT_CACHE_ENTRY_CREATED: return createdCustomEvents; case CLIENT_CACHE_ENTRY_MODIFIED: return modifiedCustomEvents; case CLIENT_CACHE_ENTRY_REMOVED: return removedCustomEvents; case CLIENT_CACHE_ENTRY_EXPIRED: return expiredCustomEvents; default: throw new IllegalArgumentException("Unknown event type: " + type); } } public void expectNoEvents(ClientEvent.Type type) { assertEquals(0, queue(type).size()); } public void expectNoEvents() { expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectSingleCustomEvent(ClientEvent.Type type, E expected) { E event = pollEvent(type); assertAnyEquals(expected, event); } public void expectCreatedEvent(E expected) { expectSingleCustomEvent(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED, expected); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectModifiedEvent(E expected) { expectSingleCustomEvent(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED, expected); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectRemovedEvent(E expected) { expectSingleCustomEvent(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED, expected); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectExpiredEvent(E expected) { expectSingleCustomEvent(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED, expected); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); } @ClientCacheEntryCreated @SuppressWarnings("unused") public void handleCustomCreatedEvent(ClientCacheEntryCustomEvent<E> e) { assertEquals(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED, e.getType()); createdCustomEvents.add(e.getEventData()); } @ClientCacheEntryModified @SuppressWarnings("unused") public void handleCustomModifiedEvent(ClientCacheEntryCustomEvent<E> e) { assertEquals(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED, e.getType()); modifiedCustomEvents.add(e.getEventData()); } @ClientCacheEntryRemoved @SuppressWarnings("unused") public void handleCustomRemovedEvent(ClientCacheEntryCustomEvent<E> e) { assertEquals(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED, e.getType()); removedCustomEvents.add(e.getEventData()); } @ClientCacheEntryExpired @SuppressWarnings("unused") public void handleCustomExpiredEvent(ClientCacheEntryCustomEvent<E> e) { assertEquals(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED, e.getType()); expiredCustomEvents.add(e.getEventData()); } public static final class CustomEvent implements Serializable { final Integer key; final String value; final long timestamp; final int counter; public CustomEvent(Integer key, String value, int counter) { this.key = key; this.value = value; this.timestamp = System.nanoTime(); this.counter = counter; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomEvent that = (CustomEvent) o; if (counter != that.counter) return false; if (!key.equals(that.key)) return false; return !(value != null ? !value.equals(that.value) : that.value != null); } @Override public int hashCode() { int result = key.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); result = 31 * result + counter; return result; } @Override public String toString() { return "CustomEvent{" + "key=" + key + ", value='" + value + '\'' + ", timestamp=" + timestamp + ", counter=" + counter + '}'; } } @ClientListener(converterFactoryName = "static-converter-factory") public static class StaticCustomEventLogListener<K> extends CustomEventLogListener<K, CustomEvent> { public StaticCustomEventLogListener(RemoteCache<K, ?> r) { super(r); } @Override public void expectSingleCustomEvent(ClientEvent.Type type, CustomEvent expected) { CustomEvent event = pollEvent(type); assertNotNull(event.key); assertNotNull(event.timestamp); // check only custom field, value can be null assertAnyEquals(expected, event); } public void expectOrderedEventQueue(ClientEvent.Type type) { BlockingQueue<CustomEvent> queue = queue(type); if (queue.size() < 2) return; try { CustomEvent before = queue.poll(10, TimeUnit.SECONDS); Iterator<CustomEvent> iter = queue.iterator(); while (iter.hasNext()) { CustomEvent after = iter.next(); expectTimeOrdered(before, after); before = after; } } catch (InterruptedException e) { throw new AssertionError(e); } } private void expectTimeOrdered(CustomEvent before, CustomEvent after) { assertTrue("Before timestamp=" + before.timestamp + ", after timestamp=" + after.timestamp, before.timestamp < after.timestamp); } } @ClientListener(converterFactoryName = "raw-static-converter-factory", useRawData = true) public static class RawStaticCustomEventLogListener<K> extends CustomEventLogListener<K, byte[]> { public RawStaticCustomEventLogListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(converterFactoryName = "static-converter-factory", includeCurrentState = true) public static class StaticCustomEventLogWithStateListener<K> extends CustomEventLogListener<K, CustomEvent> { public StaticCustomEventLogWithStateListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(converterFactoryName = "dynamic-converter-factory") public static class DynamicCustomEventLogListener<K> extends CustomEventLogListener<K, CustomEvent> { public DynamicCustomEventLogListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(converterFactoryName = "dynamic-converter-factory", includeCurrentState = true) public static class DynamicCustomEventWithStateLogListener<K> extends CustomEventLogListener<K, CustomEvent> { public DynamicCustomEventWithStateLogListener(RemoteCache<K, ?> r) { super(r); } } @NamedFactory(name = "static-converter-factory") public static class StaticConverterFactory implements CacheEventConverterFactory { @Override public CacheEventConverter<Integer, String, CustomEvent> getConverter(Object[] params) { return new StaticConverter(); } static class StaticConverter implements CacheEventConverter<Integer, String, CustomEvent>, Serializable, ExternalPojo { @Override public CustomEvent convert(Integer key, String previousValue, Metadata previousMetadata, String value, Metadata metadata, EventType eventType) { return new CustomEvent(key, value, 0); } } } @NamedFactory(name = "dynamic-converter-factory") public static class DynamicConverterFactory implements CacheEventConverterFactory { @Override public CacheEventConverter<Integer, String, CustomEvent> getConverter(final Object[] params) { return new DynamicConverter(params); } static class DynamicConverter implements CacheEventConverter<Integer, String, CustomEvent>, Serializable { private final Object[] params; public DynamicConverter(Object[] params) { this.params = params; } @Override public CustomEvent convert(Integer key, String previousValue, Metadata previousMetadata, String value, Metadata metadata, EventType eventType) { if (params[0].equals(key)) return new CustomEvent(key, null, 0); return new CustomEvent(key, value, 0); } } } @NamedFactory(name = "raw-static-converter-factory") public static class RawStaticConverterFactory implements CacheEventConverterFactory { @Override public CacheEventConverter<byte[], byte[], byte[]> getConverter(Object[] params) { return new RawStaticConverter(); } static class RawStaticConverter implements CacheEventConverter<byte[], byte[], byte[]>, Serializable { @Override public byte[] convert(byte[] key, byte[] previousValue, Metadata previousMetadata, byte[] value, Metadata metadata, EventType eventType) { return value != null ? concat(key, value) : key; } } } public interface CallbackCounter extends Serializable { void incr(); int get(); void reset(); } public static final class NumericCallbackCounter implements CallbackCounter { int count = 0; @Override public void incr() { count++; } @Override public int get() { return count; } @Override public void reset() { count = 0; } } @NamedFactory(name = "filter-converter-factory") public static class FilterConverterFactory implements CacheEventFilterConverterFactory { @Override public CacheEventFilterConverter<Integer, String, CustomEvent> getFilterConverter(Object[] params) { return new FilterConverter(params); } static class FilterConverter extends AbstractCacheEventFilterConverter<Integer, String, CustomEvent> implements Serializable, ExternalPojo { private final Object[] params; private final CallbackCounter counter = new NumericCallbackCounter(); FilterConverter(Object[] params) { this.params = params; } @Override public CustomEvent filterAndConvert(Integer key, String oldValue, Metadata oldMetadata, String newValue, Metadata newMetadata, EventType eventType) { counter.incr(); if (params[0].equals(key)) return new CustomEvent(key, null, counter.get()); return new CustomEvent(key, newValue, counter.get()); } } } @ClientListener(filterFactoryName = "filter-converter-factory", converterFactoryName = "filter-converter-factory") public static class FilterCustomEventLogListener<K> extends CustomEventLogListener<K, CustomEvent> { public FilterCustomEventLogListener(RemoteCache<K, ?> r) { super(r); } } static byte[] concat(byte[] a, byte[] b) { int aLen = a.length; int bLen = b.length; byte[] ret = new byte[aLen + bLen]; System.arraycopy(a, 0, ret, 0, aLen); System.arraycopy(b, 0, ret, aLen, bLen); return ret; } }