/*
* 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.internal.events;
import org.ehcache.event.EventType;
import org.ehcache.core.events.StoreEventSink;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.core.spi.store.events.StoreEvent;
import org.ehcache.core.spi.store.events.StoreEventFilter;
import org.ehcache.core.spi.store.events.StoreEventListener;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.mockito.Matchers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf;
import static org.ehcache.impl.internal.util.Matchers.eventOfType;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* ScopedStoreEventDispatcherTest
*/
public class ScopedStoreEventDispatcherTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ScopedStoreEventDispatcherTest.class);
@Test
public void testRegistersOrderingChange() {
ScopedStoreEventDispatcher<Object, Object> dispatcher = new ScopedStoreEventDispatcher<Object, Object>(1);
assertThat(dispatcher.isEventOrdering(), is(false));
dispatcher.setEventOrdering(true);
assertThat(dispatcher.isEventOrdering(), is(true));
dispatcher.setEventOrdering(false);
assertThat(dispatcher.isEventOrdering(), is(false));
}
@Test
@SuppressWarnings("unchecked")
public void testListenerNotifiedUnordered() {
ScopedStoreEventDispatcher<String, String> dispatcher = new ScopedStoreEventDispatcher<String, String>(1);
@SuppressWarnings("unchecked")
StoreEventListener<String, String> listener = mock(StoreEventListener.class);
dispatcher.addEventListener(listener);
StoreEventSink<String, String> sink = dispatcher.eventSink();
sink.created("test", "test");
dispatcher.releaseEventSink(sink);
verify(listener).onEvent(any(StoreEvent.class));
}
@Test
@SuppressWarnings("unchecked")
public void testListenerNotifiedOrdered() {
ScopedStoreEventDispatcher<String, String> dispatcher = new ScopedStoreEventDispatcher<String, String>(1);
@SuppressWarnings("unchecked")
StoreEventListener<String, String> listener = mock(StoreEventListener.class);
dispatcher.addEventListener(listener);
dispatcher.setEventOrdering(true);
StoreEventSink<String, String> sink = dispatcher.eventSink();
sink.created("test", "test");
dispatcher.releaseEventSink(sink);
verify(listener).onEvent(any(StoreEvent.class));
}
@Test
public void testEventFiltering() {
ScopedStoreEventDispatcher<String, String> dispatcher = new ScopedStoreEventDispatcher<String, String>(1);
@SuppressWarnings("unchecked")
StoreEventListener<String, String> listener = mock(StoreEventListener.class);
dispatcher.addEventListener(listener);
@SuppressWarnings("unchecked")
StoreEventFilter<String, String> filter = mock(StoreEventFilter.class);
when(filter.acceptEvent(eq(EventType.CREATED), anyString(), anyString(), anyString())).thenReturn(true);
when(filter.acceptEvent(eq(EventType.REMOVED), anyString(), anyString(), anyString())).thenReturn(false);
dispatcher.addEventFilter(filter);
StoreEventSink<String, String> sink = dispatcher.eventSink();
sink.removed("gone", supplierOf("really gone"));
sink.created("new", "and shiny");
dispatcher.releaseEventSink(sink);
Matcher<StoreEvent<String, String>> matcher = eventOfType(EventType.CREATED);
verify(listener).onEvent(argThat(matcher));
verifyNoMoreInteractions(listener);
}
@Test
public void testOrderedEventDelivery() throws Exception {
final ScopedStoreEventDispatcher<Long, Boolean> dispatcher = new ScopedStoreEventDispatcher<Long, Boolean>(4);
dispatcher.setEventOrdering(true);
final ConcurrentHashMap<Long, Long> map = new ConcurrentHashMap<Long, Long>();
final long[] keys = new long[] { 1L, 42L, 256L };
map.put(keys[0], 125L);
map.put(keys[1], 42 * 125L);
map.put(keys[2], 256 * 125L);
final ConcurrentHashMap<Long, Long> resultMap = new ConcurrentHashMap<Long, Long>(map);
dispatcher.addEventListener(new StoreEventListener<Long, Boolean>() {
@Override
public void onEvent(StoreEvent<Long, Boolean> event) {
if (event.getNewValue()) {
resultMap.compute(event.getKey(), new BiFunction<Long, Long, Long>() {
@Override
public Long apply(Long key, Long value) {
return value + 10L;
}
});
} else {
resultMap.compute(event.getKey(), new BiFunction<Long, Long, Long>() {
@Override
public Long apply(Long key, Long value) {
return 7L - value;
}
});
}
}
});
final long seed = new Random().nextLong();
LOGGER.info("Starting test with seed {}", seed);
int workers = Runtime.getRuntime().availableProcessors() + 2;
final CountDownLatch latch = new CountDownLatch(workers);
for (int i = 0; i < workers; i++) {
final int index =i;
new Thread(new Runnable() {
@Override
public void run() {
Random random = new Random(seed * index);
for (int j = 0; j < 10000; j++) {
int keyIndex = random.nextInt(3);
final StoreEventSink<Long, Boolean> sink = dispatcher.eventSink();
if (random.nextBoolean()) {
map.compute(keys[keyIndex], new BiFunction<Long, Long, Long>() {
@Override
public Long apply(Long key, Long value) {
long newValue = value + 10L;
sink.created(key, true);
return newValue;
}
});
} else {
map.compute(keys[keyIndex], new BiFunction<Long, Long, Long>() {
@Override
public Long apply(Long key, Long value) {
long newValue = 7L - value;
sink.created(key, false);
return newValue;
}
});
}
dispatcher.releaseEventSink(sink);
}
latch.countDown();
}
}).start();
}
latch.await();
LOGGER.info("\n\tResult map {} \n\tWork map {}", resultMap, map);
assertThat(resultMap, is(map));
}
}