/* * Copyright Terracotta, Inc. * * Licensed 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.ehcache.integration; import org.ehcache.Cache; import org.ehcache.CacheManager; import org.ehcache.core.EhcacheWithLoaderWriter; import org.ehcache.config.CacheConfiguration; import org.ehcache.config.builders.CacheManagerBuilder; import org.ehcache.config.units.EntryUnit; import org.ehcache.config.units.MemoryUnit; import org.ehcache.event.CacheEvent; import org.ehcache.event.CacheEventListener; import org.ehcache.event.EventFiring; import org.ehcache.event.EventOrdering; import org.ehcache.event.EventType; import org.ehcache.expiry.Duration; import org.ehcache.expiry.Expirations; import org.ehcache.impl.internal.TimeSourceConfiguration; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder; import static org.ehcache.config.builders.ResourcePoolsBuilder.heap; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; public class EventNotificationTest { private static final TestTimeSource testTimeSource = new TestTimeSource(); Listener listener1 = new Listener(); Listener listener2 = new Listener(); Listener listener3 = new Listener(); AsynchronousListener asyncListener = new AsynchronousListener(); @Test public void testNotificationForCacheOperations() throws InterruptedException { CacheConfiguration<Long, String> cacheConfiguration = newCacheConfigurationBuilder(Long.class, String.class, heap(5)).build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .build(true); Cache<Long, String> cache = cacheManager.getCache("cache", Long.class, String.class); cache.getRuntimeConfiguration().registerCacheEventListener(listener1, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.EVICTED, EventType.CREATED, EventType.UPDATED, EventType.REMOVED)); cache.getRuntimeConfiguration().registerCacheEventListener(listener2, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.EVICTED, EventType.CREATED, EventType.UPDATED, EventType.REMOVED)); cache.put(1L, "1"); assertEquals(1, listener1.created.get()); assertEquals(0, listener1.updated.get()); assertEquals(0, listener1.removed.get()); assertEquals(1, listener2.created.get()); assertEquals(0, listener2.updated.get()); assertEquals(0, listener2.removed.get()); Map<Long, String> entries = new HashMap<Long, String>(); entries.put(2L, "2"); entries.put(3L, "3"); cache.putAll(entries); assertEquals(3, listener1.created.get()); assertEquals(0, listener1.updated.get()); assertEquals(0, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(0, listener2.updated.get()); assertEquals(0, listener2.removed.get()); cache.put(1L, "01"); assertEquals(3, listener1.created.get()); assertEquals(1, listener1.updated.get()); assertEquals(0, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(1, listener2.updated.get()); assertEquals(0, listener2.removed.get()); cache.remove(2L); assertEquals(3, listener1.created.get()); assertEquals(1, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(1, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.replace(1L, "001"); assertEquals(3, listener1.created.get()); assertEquals(2, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(2, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.replace(3L, "3", "03"); assertEquals(3, listener1.created.get()); assertEquals(3, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(3, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.get(1L); assertEquals(3, listener1.created.get()); assertEquals(3, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(3, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.containsKey(1L); assertEquals(3, listener1.created.get()); assertEquals(3, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(3, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.put(1L, "0001"); assertEquals(3, listener1.created.get()); assertEquals(4, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(4, listener2.updated.get()); assertEquals(1, listener2.removed.get()); Set<Long> keys = new HashSet<Long>(); keys.add(1L); cache.getAll(keys); assertEquals(3, listener1.created.get()); assertEquals(4, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(4, listener2.updated.get()); assertEquals(1, listener2.removed.get()); cache.getRuntimeConfiguration().registerCacheEventListener(listener3, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.EVICTED, EventType.CREATED, EventType.UPDATED, EventType.REMOVED)); cache.replace(1L, "00001"); assertEquals(3, listener1.created.get()); assertEquals(5, listener1.updated.get()); assertEquals(1, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(5, listener2.updated.get()); assertEquals(1, listener2.removed.get()); assertEquals(0, listener3.created.get()); assertEquals(1, listener3.updated.get()); assertEquals(0, listener3.removed.get()); cache.remove(1L); assertEquals(3, listener1.created.get()); assertEquals(5, listener1.updated.get()); assertEquals(2, listener1.removed.get()); assertEquals(3, listener2.created.get()); assertEquals(5, listener2.updated.get()); assertEquals(2, listener2.removed.get()); assertEquals(0, listener3.created.get()); assertEquals(1, listener3.updated.get()); assertEquals(1, listener3.removed.get()); asyncListener.resetLatchCount(10); cache.getRuntimeConfiguration().registerCacheEventListener(asyncListener, EventOrdering.ORDERED, EventFiring.ASYNCHRONOUS, EnumSet .of(EventType.EVICTED, EventType.CREATED, EventType.UPDATED, EventType.REMOVED)); entries.clear(); entries.put(4L, "4"); entries.put(5L, "5"); entries.put(6L, "6"); entries.put(7L, "7"); entries.put(8L, "8"); entries.put(9L, "9"); entries.put(10L, "10"); cache.putAll(entries); asyncListener.latch.await(); cacheManager.close(); assertEquals(10, listener1.created.get()); assertEquals(5, listener1.updated.get()); assertEquals(2, listener1.removed.get()); assertEquals(3, listener1.evicted.get()); assertEquals(10, listener2.created.get()); assertEquals(5, listener2.updated.get()); assertEquals(2, listener2.removed.get()); assertEquals(3, listener2.evicted.get()); assertEquals(7, listener3.created.get()); assertEquals(1, listener3.updated.get()); assertEquals(1, listener3.removed.get()); assertEquals(3, listener3.evicted.get()); assertEquals(7, asyncListener.created.get()); assertEquals(3, asyncListener.evicted.get()); assertEquals(0, asyncListener.removed.get()); assertEquals(0, asyncListener.updated.get()); } @Test public void testEventOrderForUpdateThatTriggersEviction () { CacheConfiguration<Long, SerializableObject> cacheConfiguration = newCacheConfigurationBuilder(Long.class, SerializableObject.class, newResourcePoolsBuilder() .heap(1L, EntryUnit.ENTRIES).offheap(1l, MemoryUnit.MB).build()).build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .build(true); Cache<Long, SerializableObject> cache = cacheManager.getCache("cache", Long.class, SerializableObject.class); cache.getRuntimeConfiguration().registerCacheEventListener(listener1, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.EVICTED, EventType.CREATED, EventType.UPDATED, EventType.REMOVED)); SerializableObject object1 = new SerializableObject(0xAAE60); // 700 KB SerializableObject object2 = new SerializableObject(0xDBBA0); // 900 KB cache.put(1L, object1); cache.put(1L, object2); assertThat(listener1.eventTypeHashMap.get(EventType.EVICTED), lessThan(listener1.eventTypeHashMap.get(EventType.CREATED))); cacheManager.close(); } @Test public void testEventFiringInCacheIterator() { Logger logger = LoggerFactory.getLogger(EhcacheWithLoaderWriter.class + "-" + "EventNotificationTest"); CacheConfiguration<Long, String> cacheConfiguration = newCacheConfigurationBuilder(Long.class, String.class, newResourcePoolsBuilder() .heap(5L, EntryUnit.ENTRIES).build()) .withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS))) .build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .using(new TimeSourceConfiguration(testTimeSource)) .build(true); testTimeSource.setTimeMillis(0); Cache<Long, String> cache = cacheManager.getCache("cache", Long.class, String.class); cache.getRuntimeConfiguration().registerCacheEventListener(listener1, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.EXPIRED)); cache.put(1L, "1"); cache.put(2L, "2"); cache.put(3L, "3"); cache.put(4L, "4"); cache.put(5L, "5"); assertThat(listener1.expired.get(), is(0)); for(Cache.Entry entry : cache) { logger.info("Iterating over key : ", entry.getKey()); } testTimeSource.setTimeMillis(2000); for(Cache.Entry entry : cache) { logger.info("Iterating over key : ", entry.getKey()); } cacheManager.close(); assertThat(listener1.expired.get(), is(5)); } @Test public void testMultiThreadedSyncAsyncNotifications() throws InterruptedException { AsynchronousListener asyncListener = new AsynchronousListener(); asyncListener.resetLatchCount(100); CacheConfiguration<Number, Number> cacheConfiguration = newCacheConfigurationBuilder(Number.class, Number.class, newResourcePoolsBuilder().heap(10L, EntryUnit.ENTRIES)) .withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS))) .build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .using(new TimeSourceConfiguration(testTimeSource)) .build(true); testTimeSource.setTimeMillis(0); Cache<Number, Number> cache = cacheManager.getCache("cache", Number.class, Number.class); cache.getRuntimeConfiguration().registerCacheEventListener(asyncListener, EventOrdering.UNORDERED, EventFiring.ASYNCHRONOUS, EnumSet .of(EventType.CREATED, EventType.EXPIRED)); cache.getRuntimeConfiguration().registerCacheEventListener(listener1, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.CREATED, EventType.EXPIRED)); Thread[] operators = new Thread[10]; for (int i = 0; i < 10; i++) { operators[i] = new Thread(new CachePutOperator(cache, i), "CACHE-PUT-OPERATOR_" + i); operators[i].start(); } for (int i = 0; i < 10; i++) { operators[i].join(); } int entryCount = 0; for (Cache.Entry<Number, Number> entry : cache) { entryCount++; } testTimeSource.setTimeMillis(2000); operators = new Thread[10]; for (int i = 0; i < 10; i++) { operators[i] = new Thread(new CacheGetOperator(cache, i), "CACHE-GET-OPERATOR_" + i); operators[i].start(); } for (int i = 0; i < 10; i++) { operators[i].join(); } cacheManager.close(); assertEquals(100, listener1.created.get()); assertEquals(entryCount, listener1.expired.get()); assertEquals(100, asyncListener.created.get()); assertEquals(entryCount, asyncListener.expired.get()); } @Test public void testMultiThreadedSyncAsyncNotificationsWithOffheap() throws InterruptedException { AsynchronousListener asyncListener = new AsynchronousListener(); asyncListener.resetLatchCount(100); CacheConfiguration<Number, Number> cacheConfiguration = newCacheConfigurationBuilder(Number.class, Number.class, newResourcePoolsBuilder() .heap(10L, EntryUnit.ENTRIES).offheap(10, MemoryUnit.MB)) .build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .build(true); Cache<Number, Number> cache = cacheManager.getCache("cache", Number.class, Number.class); cache.getRuntimeConfiguration().registerCacheEventListener(asyncListener, EventOrdering.UNORDERED, EventFiring.ASYNCHRONOUS, EnumSet .of(EventType.CREATED, EventType.EXPIRED)); cache.getRuntimeConfiguration().registerCacheEventListener(listener1, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.CREATED, EventType.EXPIRED)); Thread[] operators = new Thread[10]; for (int i = 0; i < 10; i++) { operators[i] = new Thread(new CachePutOperator(cache, i), "CACHE-PUT-OPERATOR_" + i); operators[i].start(); } for (int i = 0; i < 10; i++) { operators[i].join(); } cacheManager.close(); assertEquals(100, listener1.created.get()); assertEquals(100, asyncListener.created.get()); } @Test public void testMultiThreadedSyncNotifications() throws InterruptedException { CacheConfiguration<Number, Number> cacheConfiguration = newCacheConfigurationBuilder(Number.class, Number.class, newResourcePoolsBuilder() .heap(10L, EntryUnit.ENTRIES)) .build(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("cache", cacheConfiguration) .build(true); Cache<Number, Number> cache = cacheManager.getCache("cache", Number.class, Number.class); cache.getRuntimeConfiguration() .registerCacheEventListener(listener1, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet .of(EventType.CREATED, EventType.EVICTED)); Thread[] operators = new Thread[10]; for (int i = 0; i < 10; i++) { operators[i] = new Thread(new CachePutOperator(cache, i), "CACHE-PUT-OPERATOR_" + i); operators[i].start(); } for (int i = 0; i < 10; i++) { operators[i].join(); } int entryCount = 0; for (Cache.Entry<Number, Number> entry : cache) { entryCount++; } cacheManager.close(); assertEquals(100, listener1.created.get()); assertEquals(100 - entryCount, listener1.evicted.get()); } public static class Listener implements CacheEventListener<Object, Object> { private AtomicInteger evicted = new AtomicInteger(); private AtomicInteger created = new AtomicInteger(); private AtomicInteger updated = new AtomicInteger(); private AtomicInteger removed = new AtomicInteger(); private AtomicInteger expired = new AtomicInteger(); private AtomicInteger eventCounter = new AtomicInteger(); private HashMap<EventType, Integer> eventTypeHashMap = new HashMap<EventType, Integer>(); @Override public void onEvent(CacheEvent<? extends Object, ? extends Object> event) { Logger logger = LoggerFactory.getLogger(EhcacheWithLoaderWriter.class + "-" + "EventNotificationTest"); logger.info(event.getType().toString()); eventTypeHashMap.put(event.getType(), eventCounter.get()); eventCounter.getAndIncrement(); if(event.getType() == EventType.EVICTED){ evicted.getAndIncrement(); } if(event.getType() == EventType.CREATED){ created.getAndIncrement(); } if(event.getType() == EventType.UPDATED){ updated.getAndIncrement(); } if(event.getType() == EventType.REMOVED){ removed.getAndIncrement(); } if(event.getType() == EventType.EXPIRED){ expired.getAndIncrement(); } } } public static class AsynchronousListener implements CacheEventListener<Object, Object> { private AtomicInteger evicted = new AtomicInteger(); private AtomicInteger created = new AtomicInteger(); private AtomicInteger updated = new AtomicInteger(); private AtomicInteger removed = new AtomicInteger(); private AtomicInteger expired = new AtomicInteger(); private CountDownLatch latch; private void resetLatchCount(int operations) { this.latch = new CountDownLatch(operations); } @Override public void onEvent(final CacheEvent<? extends Object, ? extends Object> event) { Logger logger = LoggerFactory.getLogger(EventNotificationTest.class + "-" + "EventNotificationTest"); logger.info(event.getType().toString()); if(event.getType() == EventType.EVICTED){ evicted.getAndIncrement(); } if(event.getType() == EventType.CREATED){ created.getAndIncrement(); } if(event.getType() == EventType.UPDATED){ updated.getAndIncrement(); } if(event.getType() == EventType.REMOVED){ removed.getAndIncrement(); } if(event.getType() == EventType.EXPIRED){ expired.getAndIncrement(); } latch.countDown(); } } public static class SerializableObject implements Serializable { private int size; private Byte [] data; SerializableObject(int size) { this.size = size; this.data = new Byte[size]; } } private static class CachePutOperator implements Runnable { Logger logger = LoggerFactory.getLogger(EventNotificationTest.class + "-" + "EventNotificationTest"); Cache<Number, Number> cache; int number; CachePutOperator(Cache<Number, Number> cache, int number) { this.cache = cache; this.number = number * 100; } @Override public void run() { for (int i = number; i < number + 10; i++) { cache.put(i , i); logger.info(Thread.currentThread().getName() + " putting " + i); } } } private static class CacheGetOperator implements Runnable { Cache<Number, Number> cache; int number; CacheGetOperator(Cache<Number, Number> cache, int number) { this.cache = cache; this.number = number * 100; } @Override public void run() { for (int i = number; i < number + 10; i++) { cache.get(i); } } } }