package org.infinispan.client.hotrod.event; import static org.infinispan.test.TestingUtil.assertAnyEquals; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; 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.CacheEventFilter; import org.infinispan.notifications.cachelistener.filter.CacheEventFilterFactory; import org.infinispan.notifications.cachelistener.filter.EventType; @ClientListener public class EventLogListener<K> implements RemoteCacheSupplier<K> { public BlockingQueue<ClientCacheEntryCreatedEvent> createdEvents = new ArrayBlockingQueue<>(128); public BlockingQueue<ClientCacheEntryModifiedEvent> modifiedEvents = new ArrayBlockingQueue<>(128); public BlockingQueue<ClientCacheEntryRemovedEvent> removedEvents = new ArrayBlockingQueue<>(128); public BlockingQueue<ClientCacheEntryExpiredEvent> expiredEvents = new ArrayBlockingQueue<>(128); private final RemoteCache<K, ?> remote; public EventLogListener(RemoteCache<K, ?> remote) { this.remote = remote; } @Override @SuppressWarnings("unchecked") public <V> RemoteCache<K, V> get() { return (RemoteCache<K, V>) remote; } @SuppressWarnings("unchecked") public <E extends ClientEvent> E pollEvent(ClientEvent.Type type) { try { E event = (E) queue(type).poll(10, TimeUnit.SECONDS); assertNotNull(event); return event; } catch (InterruptedException e) { throw new AssertionError(e); } } @SuppressWarnings("unchecked") public <E extends ClientEvent> BlockingQueue<E> queue(ClientEvent.Type type) { switch (type) { case CLIENT_CACHE_ENTRY_CREATED: return (BlockingQueue<E>) createdEvents; case CLIENT_CACHE_ENTRY_MODIFIED: return (BlockingQueue<E>) modifiedEvents; case CLIENT_CACHE_ENTRY_REMOVED: return (BlockingQueue<E>) removedEvents; case CLIENT_CACHE_ENTRY_EXPIRED: return (BlockingQueue<E>) expiredEvents; default: throw new IllegalArgumentException("Unknown event type: " + type); } } @ClientCacheEntryCreated @SuppressWarnings("unused") public void handleCreatedEvent(ClientCacheEntryCreatedEvent e) { createdEvents.add(e); } @ClientCacheEntryModified @SuppressWarnings("unused") public void handleModifiedEvent(ClientCacheEntryModifiedEvent e) { modifiedEvents.add(e); } @ClientCacheEntryRemoved @SuppressWarnings("unused") public void handleRemovedEvent(ClientCacheEntryRemovedEvent e) { removedEvents.add(e); } @ClientCacheEntryExpired @SuppressWarnings("unused") public void handleExpiriedEvent(ClientCacheEntryExpiredEvent e) { expiredEvents.add(e); } 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 expectNoEvents(ClientEvent.Type type) { switch (type) { case CLIENT_CACHE_ENTRY_CREATED: assertEquals(0, createdEvents.size()); break; case CLIENT_CACHE_ENTRY_MODIFIED: assertEquals(0, modifiedEvents.size()); break; case CLIENT_CACHE_ENTRY_REMOVED: assertEquals(0, removedEvents.size()); break; case CLIENT_CACHE_ENTRY_EXPIRED: assertEquals(0, expiredEvents.size()); break; } } public void expectOnlyCreatedEvent(K key) { expectSingleEvent(key, 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 expectOnlyModifiedEvent(K key) { expectSingleEvent(key, ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectOnlyRemovedEvent(K key) { expectSingleEvent(key, ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); } public void expectOnlyExpiredEvent(K key) { expectSingleEvent(key, ClientEvent.Type.CLIENT_CACHE_ENTRY_EXPIRED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_CREATED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_MODIFIED); expectNoEvents(ClientEvent.Type.CLIENT_CACHE_ENTRY_REMOVED); } public void expectSingleEvent(K key, ClientEvent.Type type) { switch (type) { case CLIENT_CACHE_ENTRY_CREATED: ClientCacheEntryCreatedEvent createdEvent = pollEvent(type); assertAnyEquals(key, createdEvent.getKey()); assertAnyEquals(serverDataVersion(remote, key), createdEvent.getVersion()); break; case CLIENT_CACHE_ENTRY_MODIFIED: ClientCacheEntryModifiedEvent modifiedEvent = pollEvent(type); assertAnyEquals(key, modifiedEvent.getKey()); assertAnyEquals(serverDataVersion(remote, key), modifiedEvent.getVersion()); break; case CLIENT_CACHE_ENTRY_REMOVED: ClientCacheEntryRemovedEvent removedEvent = pollEvent(type); assertAnyEquals(key, removedEvent.getKey()); break; case CLIENT_CACHE_ENTRY_EXPIRED: ClientCacheEntryExpiredEvent expiredEvent = pollEvent(type); assertAnyEquals(key, expiredEvent.getKey()); break; } assertEquals(0, queue(type).size()); } private long serverDataVersion(RemoteCache<K, ?> cache, K key) { long v1 = cache.getVersioned(key).getVersion(); long v2 = cache.getWithMetadata(key).getVersion(); assertEquals(v1, v2); return v1; } public void expectUnorderedEvents(ClientEvent.Type type, K... keys) { List<K> assertedKeys = new ArrayList<>(); for (int i = 0; i < keys.length; i++) { ClientEvent event = pollEvent(type); int initialSize = assertedKeys.size(); for (K key : keys) { K eventKey = null; switch (event.getType()) { case CLIENT_CACHE_ENTRY_CREATED: eventKey = ((ClientCacheEntryCreatedEvent<K>) event).getKey(); break; case CLIENT_CACHE_ENTRY_MODIFIED: eventKey = ((ClientCacheEntryModifiedEvent<K>) event).getKey(); break; case CLIENT_CACHE_ENTRY_REMOVED: eventKey = ((ClientCacheEntryRemovedEvent<K>) event).getKey(); break; case CLIENT_CACHE_ENTRY_EXPIRED: eventKey = ((ClientCacheEntryExpiredEvent<K>) event).getKey(); break; } checkUnorderedKeyEvent(assertedKeys, key, eventKey); } int finalSize = assertedKeys.size(); assertEquals(initialSize + 1, finalSize); } } private boolean checkUnorderedKeyEvent(List<K> assertedKeys, K key, K eventKey) { if (key.equals(eventKey)) { assertFalse(assertedKeys.contains(key)); assertedKeys.add(key); return true; } return false; } public void expectFailoverEvent() { pollEvent(ClientEvent.Type.CLIENT_CACHE_FAILOVER); } @ClientListener(filterFactoryName = "static-filter-factory") public static class StaticFilteredEventLogListener<K> extends EventLogListener<K> { public StaticFilteredEventLogListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(filterFactoryName = "raw-static-filter-factory", useRawData = true) public static class RawStaticFilteredEventLogListener<K> extends EventLogListener<K> { public RawStaticFilteredEventLogListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(filterFactoryName = "static-filter-factory", includeCurrentState = true) public static class StaticFilteredEventLogWithStateListener<K> extends EventLogListener<K> { public StaticFilteredEventLogWithStateListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(filterFactoryName = "dynamic-filter-factory") public static class DynamicFilteredEventLogListener<K> extends EventLogListener<K> { public DynamicFilteredEventLogListener(RemoteCache<K, ?> r) { super(r); } } @ClientListener(filterFactoryName = "dynamic-filter-factory", includeCurrentState = true) public static class DynamicFilteredEventLogWithStateListener<K> extends EventLogListener<K> { public DynamicFilteredEventLogWithStateListener(RemoteCache<K, ?> r) { super(r); } } @NamedFactory(name = "static-filter-factory") public static class StaticCacheEventFilterFactory implements CacheEventFilterFactory { private final int staticKey; public StaticCacheEventFilterFactory(int staticKey) { this.staticKey = staticKey; } @Override public CacheEventFilter<Integer, String> getFilter(final Object[] params) { return new StaticCacheEventFilter(staticKey); } static class StaticCacheEventFilter implements CacheEventFilter<Integer, String>, Serializable, ExternalPojo { final Integer staticKey; StaticCacheEventFilter(Integer staticKey) { this.staticKey = staticKey; } @Override public boolean accept(Integer key, String previousValue, Metadata previousMetadata, String value, Metadata metadata, EventType eventType) { return staticKey.equals(key); } } } @NamedFactory(name = "dynamic-filter-factory") public static class DynamicCacheEventFilterFactory implements CacheEventFilterFactory { @Override public CacheEventFilter<Integer, String> getFilter(final Object[] params) { return new DynamicCacheEventFilter(params); } static class DynamicCacheEventFilter implements CacheEventFilter<Integer, String>, Serializable { private final Object[] params; public DynamicCacheEventFilter(Object[] params) { this.params = params; } @Override public boolean accept(Integer key, String previousValue, Metadata previousMetadata, String value, Metadata metadata, EventType eventType) { return params[0].equals(key); // dynamic } } } @NamedFactory(name = "raw-static-filter-factory") public static class RawStaticCacheEventFilterFactory implements CacheEventFilterFactory { @Override public CacheEventFilter<byte[], byte[]> getFilter(final Object[] params) { return new RawStaticCacheEventFilter(); } static class RawStaticCacheEventFilter implements CacheEventFilter<byte[], byte[]>, Serializable { final byte[] staticKey = new byte[]{3, 75, 0, 0, 0, 2}; // key integer `2`, as marshalled by GenericJBossMarshaller @Override public boolean accept(byte[] key, byte[] previousValue, Metadata previousMetadata, byte[] value, Metadata metadata, EventType eventType) { return Arrays.equals(key, staticKey); } } } }