/*
* Copyright (c) 2010-2016. Axon Framework
*
* 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.axonframework.eventsourcing.eventstore;
import org.axonframework.common.MockException;
import org.axonframework.eventhandling.EventMessage;
import org.axonframework.eventhandling.TrackedEventMessage;
import org.axonframework.eventsourcing.DomainEventMessage;
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
import org.axonframework.messaging.Message;
import org.axonframework.messaging.unitofwork.CurrentUnitOfWork;
import org.axonframework.messaging.unitofwork.DefaultUnitOfWork;
import org.axonframework.monitoring.NoOpMessageMonitor;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.stream.Collectors.toList;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.fail;
import static org.axonframework.eventsourcing.eventstore.EventStoreTestUtils.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Rene de Waele
*/
public class EmbeddedEventStoreTest {
private static final int CACHED_EVENTS = 10;
private static final long FETCH_DELAY = 1000;
private static final long CLEANUP_DELAY = 10000;
private EmbeddedEventStore testSubject;
private EventStorageEngine storageEngine;
@Before
public void setUp() {
storageEngine = spy(new InMemoryEventStorageEngine());
newTestSubject(CACHED_EVENTS, FETCH_DELAY, CLEANUP_DELAY);
}
private void newTestSubject(int cachedEvents, long fetchDelay, long cleanupDelay) {
Optional.ofNullable(testSubject).ifPresent(EmbeddedEventStore::shutDown);
testSubject = new EmbeddedEventStore(storageEngine, NoOpMessageMonitor.INSTANCE, cachedEvents, fetchDelay,
cleanupDelay, MILLISECONDS);
}
@After
public void tearDown() {
testSubject.shutDown();
}
@Test
public void testExistingEventIsPassedToReader() throws Exception {
DomainEventMessage<?> expected = createEvent();
testSubject.publish(expected);
TrackingEventStream stream = testSubject.openStream(null);
assertTrue(stream.hasNextAvailable());
TrackedEventMessage<?> actual = stream.nextAvailable();
assertEquals(expected.getIdentifier(), actual.getIdentifier());
assertEquals(expected.getPayload(), actual.getPayload());
assertTrue(actual instanceof DomainEventMessage<?>);
assertEquals(expected.getAggregateIdentifier(), ((DomainEventMessage<?>) actual).getAggregateIdentifier());
}
@Test(timeout = FETCH_DELAY / 10)
public void testEventPublishedAfterOpeningStreamIsPassedToReaderImmediately() throws Exception {
TrackingEventStream stream = testSubject.openStream(null);
assertFalse(stream.hasNextAvailable());
DomainEventMessage<?> expected = createEvent();
Thread t = new Thread(() -> {
try {
assertEquals(expected.getIdentifier(), stream.nextAvailable().getIdentifier());
} catch (InterruptedException e) {
fail();
}
});
t.start();
testSubject.publish(expected);
t.join();
}
@Test(timeout = 5000)
public void testReadingIsBlockedWhenStoreIsEmpty() throws Exception {
CountDownLatch lock = new CountDownLatch(1);
TrackingEventStream stream = testSubject.openStream(null);
Thread t = new Thread(() -> stream.asStream().findFirst().ifPresent(event -> lock.countDown()));
t.start();
assertFalse(lock.await(100, MILLISECONDS));
testSubject.publish(createEvent());
t.join();
assertEquals(0, lock.getCount());
}
@Test(timeout = 5000)
public void testReadingIsBlockedWhenEndOfStreamIsReached() throws Exception {
testSubject.publish(createEvent());
CountDownLatch lock = new CountDownLatch(2);
TrackingEventStream stream = testSubject.openStream(null);
Thread t = new Thread(() -> stream.asStream().limit(2).forEach(event -> lock.countDown()));
t.start();
assertFalse(lock.await(100, MILLISECONDS));
assertEquals(1, lock.getCount());
testSubject.publish(createEvent());
t.join();
assertEquals(0, lock.getCount());
}
@Test(timeout = 5000)
public void testReadingCanBeContinuedUsingLastToken() throws Exception {
List<? extends EventMessage<?>> events = createEvents(2);
testSubject.publish(events);
TrackedEventMessage<?> first = testSubject.openStream(null).nextAvailable();
TrackingToken firstToken = first.trackingToken();
TrackedEventMessage<?> second = testSubject.openStream(firstToken).nextAvailable();
assertEquals(events.get(0).getIdentifier(), first.getIdentifier());
assertEquals(events.get(1).getIdentifier(), second.getIdentifier());
}
@Test(timeout = 5000)
public void testEventIsFetchedFromCacheWhenFetchedASecondTime() throws Exception {
CountDownLatch lock = new CountDownLatch(2);
List<TrackedEventMessage<?>> events = new CopyOnWriteArrayList<>();
Thread t = new Thread(() -> testSubject.openStream(null).asStream().limit(2).forEach(event -> {
lock.countDown();
events.add(event);
}));
t.start();
assertFalse(lock.await(100, MILLISECONDS));
testSubject.publish(createEvents(2));
t.join();
TrackedEventMessage<?> second = testSubject.openStream(events.get(0).trackingToken()).nextAvailable();
assertSame(events.get(1), second);
}
@Test(timeout = 5000)
public void testPeriodicPollingWhenEventStorageIsUpdatedIndependently() throws Exception {
newTestSubject(CACHED_EVENTS, 20, CLEANUP_DELAY);
TrackingEventStream stream = testSubject.openStream(null);
CountDownLatch lock = new CountDownLatch(1);
Thread t = new Thread(() -> stream.asStream().findFirst().ifPresent(event -> lock.countDown()));
t.start();
assertFalse(lock.await(100, MILLISECONDS));
storageEngine.appendEvents(createEvent());
t.join();
assertTrue(lock.await(100, MILLISECONDS));
}
@Test(timeout = 5000)
public void testConsumerStopsTailingWhenItFallsBehindTheCache() throws Exception {
newTestSubject(CACHED_EVENTS, FETCH_DELAY, 20);
TrackingEventStream stream = testSubject.openStream(null);
assertFalse(stream.hasNextAvailable()); //now we should be tailing
testSubject.publish(createEvents(CACHED_EVENTS)); //triggers event producer to open a stream
Thread.sleep(100);
reset(storageEngine);
assertTrue(stream.hasNextAvailable());
TrackedEventMessage<?> firstEvent = stream.nextAvailable();
verifyZeroInteractions(storageEngine);
testSubject.publish(createEvent(CACHED_EVENTS), createEvent(CACHED_EVENTS + 1));
Thread.sleep(100); //allow the cleaner thread to evict the consumer
reset(storageEngine);
assertTrue(stream.hasNextAvailable());
verify(storageEngine).readEvents(firstEvent.trackingToken(), false);
}
@Test
public void testLoadWithoutSnapshot() {
testSubject.publish(createEvents(110));
List<DomainEventMessage<?>> eventMessages = testSubject.readEvents(AGGREGATE).asStream().collect(toList());
assertEquals(110, eventMessages.size());
assertEquals(109, eventMessages.get(eventMessages.size() - 1).getSequenceNumber());
}
@Test
public void testLoadWithSnapshot() {
testSubject.publish(createEvents(110));
storageEngine.storeSnapshot(createEvent(30));
List<DomainEventMessage<?>> eventMessages = testSubject.readEvents(AGGREGATE).asStream().collect(toList());
assertEquals(110 - 30, eventMessages.size());
assertEquals(30, eventMessages.get(0).getSequenceNumber());
assertEquals(109, eventMessages.get(eventMessages.size() - 1).getSequenceNumber());
}
@Test
public void testLoadWithFailingSnapshot() {
testSubject.publish(createEvents(110));
storageEngine.storeSnapshot(createEvent(30));
when(storageEngine.readSnapshot(AGGREGATE)).thenThrow(new MockException());
List<DomainEventMessage<?>> eventMessages = testSubject.readEvents(AGGREGATE).asStream().collect(toList());
assertEquals(110, eventMessages.size());
assertEquals(0, eventMessages.get(0).getSequenceNumber());
assertEquals(109, eventMessages.get(eventMessages.size() - 1).getSequenceNumber());
}
@Test
public void testLoadEventsAfterPublishingInSameUnitOfWork() {
List<DomainEventMessage<?>> events = createEvents(10);
testSubject.publish(events.subList(0, 2));
DefaultUnitOfWork.startAndGet(null)
.execute(() -> {
Assert.assertEquals(2, testSubject.readEvents(AGGREGATE).asStream().count());
testSubject.publish(events.subList(2, events.size()));
Assert.assertEquals(10, testSubject.readEvents(AGGREGATE).asStream().count());
});
}
@Test
public void testLoadEventsWithOffsetAfterPublishingInSameUnitOfWork() {
List<DomainEventMessage<?>> events = createEvents(10);
testSubject.publish(events.subList(0, 2));
DefaultUnitOfWork.startAndGet(null)
.execute(() -> {
Assert.assertEquals(2, testSubject.readEvents(AGGREGATE).asStream().count());
testSubject.publish(events.subList(2, events.size()));
Assert.assertEquals(8, testSubject.readEvents(AGGREGATE, 2).asStream().count());
});
}
@Test
public void testEventsAppendedInvisibleUntilUnitOfWorkIsCommitted() {
List<DomainEventMessage<?>> events = createEvents(10);
testSubject.publish(events.subList(0, 2));
DefaultUnitOfWork<Message<?>> unitOfWork = DefaultUnitOfWork.startAndGet(null);
testSubject.publish(events.subList(2, events.size()));
CurrentUnitOfWork.clear(unitOfWork);
// working outside the context of the UoW now
Assert.assertEquals(2, testSubject.readEvents(AGGREGATE).asStream().count());
CurrentUnitOfWork.set(unitOfWork);
// Back in the context
Assert.assertEquals(10, testSubject.readEvents(AGGREGATE).asStream().count());
unitOfWork.rollback();
Assert.assertEquals(2, testSubject.readEvents(AGGREGATE).asStream().count());
}
}