/*
* 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.impl.events;
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.core.spi.store.events.StoreEventListener;
import org.ehcache.core.spi.store.events.StoreEventSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings({"unchecked", "rawtypes"})
public class CacheEventDispatcherImplTest {
private CacheEventDispatcherImpl<Number, String> eventService;
private CacheEventListener<Number, String> listener;
private ExecutorService orderedExecutor;
private ExecutorService unorderedExecutor;
private StoreEventSource storeEventDispatcher;
@Before
public void setUp() {
orderedExecutor = mock(ExecutorService.class);
unorderedExecutor = mock(ExecutorService.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
EventDispatchTask task = (EventDispatchTask) invocation.getArguments()[0];
task.run();
return null;
}
}).when(unorderedExecutor).submit(any(Runnable.class));
storeEventDispatcher = mock(StoreEventSource.class);
eventService = new CacheEventDispatcherImpl<Number, String>(unorderedExecutor, orderedExecutor);
eventService.setStoreEventSource(storeEventDispatcher);
listener = mock(CacheEventListener.class);
}
@After
public void tearDown() throws Exception {
orderedExecutor.shutdownNow();
unorderedExecutor.shutdownNow();
}
@Test
public void testAsyncEventFiring() throws Exception {
eventService = new CacheEventDispatcherImpl<Number, String>(Executors.newCachedThreadPool(), orderedExecutor);
eventService.setStoreEventSource(storeEventDispatcher);
final CountDownLatch signal = new CountDownLatch(1);
final CountDownLatch signal2 = new CountDownLatch(1);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!signal.await(2, TimeUnit.SECONDS)) {
return null;
} else {
signal2.countDown();
return null;
}
}
}).when(listener).onEvent(any(CacheEvent.class));
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.ASYNCHRONOUS, EnumSet.of(EventType.CREATED));
final CacheEvent<Number, String> create = eventOfType(EventType.CREATED);
eventService.onEvent(create);
signal.countDown();
if (!signal2.await(2, TimeUnit.SECONDS)) {
fail("event handler never triggered latch - are we synchronous?");
}
}
@Test
public void testCheckEventType() {
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
CacheEvent<Number, String> create = eventOfType(EventType.CREATED);
eventService.onEvent(create);
verify(listener, Mockito.never()).onEvent(any(CacheEvent.class));
CacheEvent<Number, String> evict = eventOfType(EventType.EVICTED);
eventService.onEvent(evict);
verify(listener).onEvent(evict);
}
@Test
public void testListenerRegistrationEnablesStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.ASYNCHRONOUS, EnumSet.allOf(EventType.class));
verify(storeEventDispatcher).addEventListener(any(StoreEventListener.class));
}
@Test
public void testOrderedListenerRegistrationTogglesOrderedOnStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.ORDERED, EventFiring.ASYNCHRONOUS, EnumSet.allOf(EventType.class));
verify(storeEventDispatcher).setEventOrdering(true);
}
@Test(expected=IllegalStateException.class)
public void testDuplicateRegistration() {
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.registerCacheEventListener(listener, EventOrdering.ORDERED, EventFiring.ASYNCHRONOUS, EnumSet.of(EventType.EXPIRED));
}
@Test(expected=IllegalStateException.class)
public void testUnknownListenerDeregistration() {
eventService.deregisterCacheEventListener(listener);
}
@Test
public void testDeregisterLastListenerStopsStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.deregisterCacheEventListener(listener);
verify(storeEventDispatcher).removeEventListener(any(StoreEventListener.class));
}
@Test
public void testDeregisterLastOrderedListenerTogglesOffOrderedStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.deregisterCacheEventListener(listener);
verify(storeEventDispatcher).setEventOrdering(false);
}
@Test
public void testDeregisterNotLastOrderedListenerDoesNotToggleOffOrderedStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.registerCacheEventListener(mock(CacheEventListener.class), EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.deregisterCacheEventListener(listener);
verify(storeEventDispatcher, never()).setEventOrdering(false);
}
@Test
public void testDeregisterNotLastListenerDoesNotStopStoreEvents() {
eventService.registerCacheEventListener(listener, EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.registerCacheEventListener(mock(CacheEventListener.class), EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.EVICTED));
eventService.deregisterCacheEventListener(listener);
verify(storeEventDispatcher, never()).removeEventListener(any(StoreEventListener.class));
}
@Test
public void testShutdownDisableStoreEventsAndShutsDownOrderedExecutor() {
eventService.registerCacheEventListener(listener,
EventOrdering.UNORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.CREATED));
CacheEventListener<Number, String> otherLsnr = mock(CacheEventListener.class);
eventService.registerCacheEventListener(otherLsnr,
EventOrdering.ORDERED, EventFiring.SYNCHRONOUS, EnumSet.of(EventType.REMOVED));
eventService.shutdown();
InOrder inOrder = inOrder(storeEventDispatcher, orderedExecutor);
inOrder.verify(storeEventDispatcher).removeEventListener(any(StoreEventListener.class));
inOrder.verify(storeEventDispatcher).setEventOrdering(false);
inOrder.verify(orderedExecutor).shutdown();
inOrder.verifyNoMoreInteractions();
}
private static <K, V> CacheEvent<K, V> eventOfType(EventType type) {
CacheEvent<K, V> event = mock(CacheEvent.class, type.name());
when(event.getType()).thenReturn(type);
when(event.getKey()).thenReturn((K)new Object());
return event;
}
}