/*
* Copyright (c) 2010-2012. 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.commandhandling.disruptor;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.dsl.ProducerType;
import org.axonframework.commandhandling.CommandCallback;
import org.axonframework.commandhandling.CommandDispatchInterceptor;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.commandhandling.CommandHandlerInterceptor;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.GenericCommandMessage;
import org.axonframework.commandhandling.InterceptorChain;
import org.axonframework.commandhandling.RollbackOnAllExceptionsConfiguration;
import org.axonframework.commandhandling.annotation.TargetAggregateIdentifier;
import org.axonframework.commandhandling.callbacks.NoOpCallback;
import org.axonframework.domain.DomainEventMessage;
import org.axonframework.domain.DomainEventStream;
import org.axonframework.domain.EventMessage;
import org.axonframework.domain.GenericDomainEventMessage;
import org.axonframework.domain.MetaData;
import org.axonframework.domain.SimpleDomainEventStream;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventhandling.EventListener;
import org.axonframework.eventsourcing.AbstractEventSourcedAggregateRoot;
import org.axonframework.eventsourcing.EventSourcedAggregateRoot;
import org.axonframework.eventsourcing.EventSourcedEntity;
import org.axonframework.eventsourcing.EventStreamDecorator;
import org.axonframework.eventsourcing.GenericAggregateFactory;
import org.axonframework.eventstore.EventStore;
import org.axonframework.eventstore.EventStreamNotFoundException;
import org.axonframework.repository.Repository;
import org.axonframework.serializer.SerializationAware;
import org.axonframework.serializer.Serializer;
import org.axonframework.unitofwork.TransactionManager;
import org.axonframework.unitofwork.UnitOfWork;
import org.axonframework.unitofwork.UnitOfWorkListener;
import org.dom4j.Document;
import org.hamcrest.Description;
import org.junit.*;
import org.junit.internal.matchers.*;
import org.mockito.*;
import org.mockito.internal.stubbing.answers.*;
import org.mockito.invocation.*;
import org.mockito.stubbing.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.*;
import static org.hamcrest.core.IsNot.not;
import static org.mockito.Mockito.*;
/**
* @author Allard Buijze
*/
public class DisruptorCommandBusTest {
private static final int COMMAND_COUNT = 100 * 1000;
private CountingEventBus eventBus;
private StubHandler stubHandler;
private InMemoryEventStore inMemoryEventStore;
private DisruptorCommandBus testSubject;
private String aggregateIdentifier;
private TransactionManager mockTransactionManager;
@Before
public void setUp() throws Exception {
aggregateIdentifier = UUID.randomUUID().toString();
eventBus = new CountingEventBus();
stubHandler = new StubHandler();
inMemoryEventStore = new InMemoryEventStore();
inMemoryEventStore.appendEvents(StubAggregate.class.getSimpleName(), new SimpleDomainEventStream(
new GenericDomainEventMessage<StubDomainEvent>(aggregateIdentifier, 0, new StubDomainEvent())));
}
@After
public void tearDown() {
testSubject.stop();
}
@SuppressWarnings("unchecked")
@Test
public void testCallbackInvokedBeforeUnitOfWorkCleanup() throws Throwable {
CommandHandlerInterceptor mockHandlerInterceptor = mock(CommandHandlerInterceptor.class);
CommandDispatchInterceptor mockDispatchInterceptor = mock(CommandDispatchInterceptor.class);
when(mockDispatchInterceptor.handle(isA(CommandMessage.class))).thenAnswer(new Parameter(0));
ExecutorService customExecutor = Executors.newCachedThreadPool();
testSubject = new DisruptorCommandBus(
inMemoryEventStore, eventBus,
new DisruptorConfiguration().setInvokerInterceptors(Arrays.asList(mockHandlerInterceptor))
.setDispatchInterceptors(Arrays.asList(mockDispatchInterceptor))
.setBufferSize(8)
.setProducerType(ProducerType.SINGLE)
.setWaitStrategy(new SleepingWaitStrategy())
.setExecutor(customExecutor)
.setInvokerThreadCount(2)
.setPublisherThreadCount(3)
);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
final UnitOfWorkListener mockUnitOfWorkListener = mock(UnitOfWorkListener.class);
when(mockUnitOfWorkListener.onEventRegistered(isA(UnitOfWork.class), any(EventMessage.class)))
.thenAnswer(new ReturnsArgumentAt(1));
when(mockHandlerInterceptor.handle(any(CommandMessage.class),
any(UnitOfWork.class),
any(InterceptorChain.class)))
.thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((UnitOfWork) invocation.getArguments()[1]).registerListener(mockUnitOfWorkListener);
return ((InterceptorChain) invocation.getArguments()[2]).proceed();
}
});
CommandMessage<StubCommand> command = new GenericCommandMessage<StubCommand>(
new StubCommand(aggregateIdentifier));
CommandCallback mockCallback = mock(CommandCallback.class);
testSubject.dispatch(command, mockCallback);
testSubject.stop();
assertFalse(customExecutor.awaitTermination(250, TimeUnit.MILLISECONDS));
customExecutor.shutdown();
assertTrue(customExecutor.awaitTermination(5, TimeUnit.SECONDS));
InOrder inOrder = inOrder(mockDispatchInterceptor,
mockHandlerInterceptor,
mockUnitOfWorkListener,
mockCallback);
inOrder.verify(mockDispatchInterceptor).handle(isA(CommandMessage.class));
inOrder.verify(mockHandlerInterceptor).handle(any(CommandMessage.class),
any(UnitOfWork.class),
any(InterceptorChain.class));
inOrder.verify(mockUnitOfWorkListener).onPrepareCommit(any(UnitOfWork.class), any(Set.class), any(List.class));
inOrder.verify(mockUnitOfWorkListener).afterCommit(isA(UnitOfWork.class));
inOrder.verify(mockUnitOfWorkListener).onCleanup(isA(UnitOfWork.class));
verify(mockCallback).onSuccess(any());
}
@Test
public void testEventStreamsDecoratedOnReadAndWrite() throws InterruptedException {
ExecutorService customExecutor = Executors.newCachedThreadPool();
testSubject = new DisruptorCommandBus(
inMemoryEventStore, eventBus,
new DisruptorConfiguration().setBufferSize(8)
.setProducerType(ProducerType.SINGLE)
.setWaitStrategy(new SleepingWaitStrategy())
.setExecutor(customExecutor)
.setInvokerThreadCount(2)
.setPublisherThreadCount(3)
);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
final EventStreamDecorator mockDecorator = mock(EventStreamDecorator.class);
when(mockDecorator.decorateForAppend(anyString(),
any(EventSourcedAggregateRoot.class),
any(DomainEventStream.class)))
.thenAnswer(new ReturnsArgumentAt(2));
when(mockDecorator.decorateForRead(anyString(), any(), any(DomainEventStream.class)))
.thenAnswer(new ReturnsArgumentAt(2));
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class),
mockDecorator));
CommandMessage<StubCommand> command = new GenericCommandMessage<StubCommand>(
new StubCommand(aggregateIdentifier));
CommandCallback mockCallback = mock(CommandCallback.class);
testSubject.dispatch(command, mockCallback);
testSubject.dispatch(command);
testSubject.stop();
assertFalse(customExecutor.awaitTermination(250, TimeUnit.MILLISECONDS));
customExecutor.shutdown();
assertTrue(customExecutor.awaitTermination(5, TimeUnit.SECONDS));
// invoked only once, because the second time, the aggregate comes from the 1st level cache
verify(mockDecorator).decorateForRead(eq("StubAggregate"), eq(aggregateIdentifier),
isA(DomainEventStream.class));
verify(mockDecorator, times(2)).decorateForAppend(eq("StubAggregate"), isA(EventSourcedAggregateRoot.class),
isA(DomainEventStream.class));
}
@Test
public void testEventPublicationExecutedWithinTransaction() throws Throwable {
CommandHandlerInterceptor mockInterceptor = mock(CommandHandlerInterceptor.class);
ExecutorService customExecutor = Executors.newCachedThreadPool();
mockTransactionManager = mock(TransactionManager.class);
when(mockTransactionManager.startTransaction()).thenReturn(new Object());
dispatchCommands(mockInterceptor, customExecutor, new GenericCommandMessage<ErrorCommand>(
new ErrorCommand(aggregateIdentifier)));
assertFalse(customExecutor.awaitTermination(250, TimeUnit.MILLISECONDS));
customExecutor.shutdown();
assertTrue(customExecutor.awaitTermination(5, TimeUnit.SECONDS));
verify(mockTransactionManager, times(1001)).startTransaction();
verify(mockTransactionManager, times(991)).commitTransaction(any());
verify(mockTransactionManager, times(10)).rollbackTransaction(any());
}
@SuppressWarnings("unchecked")
@Test(timeout = 10000)
public void testAggregatesBlacklistedAndRecoveredOnError_WithAutoReschedule() throws Throwable {
CommandHandlerInterceptor mockInterceptor = mock(CommandHandlerInterceptor.class);
ExecutorService customExecutor = Executors.newCachedThreadPool();
CommandCallback mockCallback = dispatchCommands(mockInterceptor,
customExecutor,
new GenericCommandMessage<ErrorCommand>(
new ErrorCommand(aggregateIdentifier))
);
assertFalse(customExecutor.awaitTermination(250, TimeUnit.MILLISECONDS));
customExecutor.shutdown();
assertTrue(customExecutor.awaitTermination(5, TimeUnit.SECONDS));
verify(mockCallback, times(990)).onSuccess(any());
verify(mockCallback, times(10)).onFailure(isA(RuntimeException.class));
}
@SuppressWarnings("unchecked")
@Test(timeout = 10000)
public void testAggregatesBlacklistedAndRecoveredOnError_WithoutReschedule() throws Throwable {
CommandHandlerInterceptor mockInterceptor = mock(CommandHandlerInterceptor.class);
ExecutorService customExecutor = Executors.newCachedThreadPool();
CommandCallback mockCallback = dispatchCommands(mockInterceptor,
customExecutor,
new GenericCommandMessage<ErrorCommand>(
new ErrorCommand(aggregateIdentifier))
);
assertFalse(customExecutor.awaitTermination(250, TimeUnit.MILLISECONDS));
customExecutor.shutdown();
assertTrue(customExecutor.awaitTermination(5, TimeUnit.SECONDS));
verify(mockCallback, times(990)).onSuccess(any());
verify(mockCallback, times(10)).onFailure(isA(RuntimeException.class));
}
@Test
public void testSerializationOptimization() {
final DisruptorConfiguration configuration = new DisruptorConfiguration();
final Serializer serializer = mock(Serializer.class);
configuration.setSerializer(serializer);
configuration.setSerializerThreadCount(3);
configuration.setSerializedRepresentation(Document.class);
EventBus mockEventBus = mock(EventBus.class);
EventStore mockEventStore = mock(EventStore.class);
testSubject = new DisruptorCommandBus(mockEventStore, mockEventBus, configuration);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
testSubject.subscribe(CreateCommand.class.getName(), stubHandler);
testSubject.subscribe(ErrorCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
testSubject.dispatch(new GenericCommandMessage<CreateCommand>(new CreateCommand(aggregateIdentifier)));
for (int t = 0; t < 10; t++) {
testSubject.dispatch(new GenericCommandMessage<StubCommand>(new StubCommand(aggregateIdentifier)));
}
testSubject.stop();
verify(mockEventBus, times(1)).publish(argThat(new IsSerializationAware()),
argThat(new IsSerializationAware()));
verify(mockEventBus, times(10)).publish(argThat(new IsSerializationAware()));
verify(mockEventStore, times(11)).appendEvents(isA(String.class),
argThat(new StreamWithSerializationAwareEvents()));
// make sure the pre-serializer kicked in and serialized all the messages
verify(serializer, times(12)).serialize(isA(SomethingDoneEvent.class), eq(Document.class));
verify(serializer, times(12)).serialize(isA(MetaData.class), eq(Document.class));
}
@Test
public void testSerializationOptimization_DisabledByDefault() {
final DisruptorConfiguration configuration = new DisruptorConfiguration();
configuration.setSerializerThreadCount(3);
EventBus mockEventBus = mock(EventBus.class);
EventStore mockEventStore = mock(EventStore.class);
testSubject = new DisruptorCommandBus(mockEventStore, mockEventBus, configuration);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
testSubject.subscribe(CreateCommand.class.getName(), stubHandler);
testSubject.subscribe(ErrorCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
testSubject.dispatch(new GenericCommandMessage<CreateCommand>(new CreateCommand(aggregateIdentifier)));
for (int t = 0; t < 10; t++) {
testSubject.dispatch(new GenericCommandMessage<StubCommand>(new StubCommand(aggregateIdentifier)));
}
testSubject.stop();
verify(mockEventBus, times(1)).publish(argThat(not(new IsSerializationAware())),
argThat(not(new IsSerializationAware())));
verify(mockEventBus, times(10)).publish(argThat(not(new IsSerializationAware())));
}
@Test
public void testSerializationOptimization_DisabledOnZeroSerializerThreads() {
final DisruptorConfiguration configuration = new DisruptorConfiguration();
final Serializer serializer = mock(Serializer.class);
configuration.setSerializer(serializer);
configuration.setSerializerThreadCount(0);
EventBus mockEventBus = mock(EventBus.class);
EventStore mockEventStore = mock(EventStore.class);
testSubject = new DisruptorCommandBus(mockEventStore, mockEventBus, configuration);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
testSubject.subscribe(CreateCommand.class.getName(), stubHandler);
testSubject.subscribe(ErrorCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
testSubject.dispatch(new GenericCommandMessage<CreateCommand>(new CreateCommand(aggregateIdentifier)));
for (int t = 0; t < 10; t++) {
testSubject.dispatch(new GenericCommandMessage<StubCommand>(new StubCommand(aggregateIdentifier)));
}
testSubject.stop();
verify(serializer, never()).serialize(isA(SomethingDoneEvent.class), eq(byte[].class));
verify(serializer, never()).serialize(isA(MetaData.class), eq(byte[].class));
verify(mockEventBus, times(1)).publish(argThat(not(new IsSerializationAware())),
argThat(not(new IsSerializationAware())));
verify(mockEventBus, times(10)).publish(argThat(not(new IsSerializationAware())));
}
private CommandCallback dispatchCommands(CommandHandlerInterceptor mockInterceptor, ExecutorService customExecutor,
GenericCommandMessage<ErrorCommand> errorCommand)
throws Throwable {
inMemoryEventStore.storedEvents.clear();
testSubject = new DisruptorCommandBus(
inMemoryEventStore, eventBus,
new DisruptorConfiguration().setInvokerInterceptors(Arrays.asList(mockInterceptor))
.setBufferSize(8)
.setProducerType(ProducerType.MULTI)
.setWaitStrategy(new SleepingWaitStrategy())
.setExecutor(customExecutor)
.setRollbackConfiguration(new RollbackOnAllExceptionsConfiguration())
.setInvokerThreadCount(2)
.setPublisherThreadCount(3)
.setTransactionManager(mockTransactionManager)
);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
testSubject.subscribe(CreateCommand.class.getName(), stubHandler);
testSubject.subscribe(ErrorCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
final UnitOfWorkListener mockUnitOfWorkListener = mock(UnitOfWorkListener.class);
when(mockUnitOfWorkListener.onEventRegistered(isA(UnitOfWork.class), any(EventMessage.class)))
.thenAnswer(new Parameter(1));
when(mockInterceptor.handle(any(CommandMessage.class), any(UnitOfWork.class), any(InterceptorChain.class)))
.thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((UnitOfWork) invocation.getArguments()[1]).registerListener(mockUnitOfWorkListener);
return ((InterceptorChain) invocation.getArguments()[2]).proceed();
}
});
testSubject.dispatch(new GenericCommandMessage<CreateCommand>(new CreateCommand(aggregateIdentifier)));
CommandCallback mockCallback = mock(CommandCallback.class);
for (int t = 0; t < 1000; t++) {
CommandMessage command;
if (t % 100 == 10) {
command = errorCommand;
} else {
command = new GenericCommandMessage<StubCommand>(new StubCommand(aggregateIdentifier));
}
testSubject.dispatch(command, mockCallback);
}
testSubject.stop();
return mockCallback;
}
@Test
public void testCreateAggregate() {
inMemoryEventStore.storedEvents.clear();
eventBus = mock(CountingEventBus.class);
testSubject = new DisruptorCommandBus(inMemoryEventStore, eventBus,
new DisruptorConfiguration()
.setBufferSize(8)
.setProducerType(ProducerType.SINGLE)
.setWaitStrategy(new SleepingWaitStrategy())
.setInvokerThreadCount(2)
.setPublisherThreadCount(3)
);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
testSubject.subscribe(CreateCommand.class.getName(), stubHandler);
testSubject.subscribe(ErrorCommand.class.getName(), stubHandler);
stubHandler.setRepository(
testSubject.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
testSubject.dispatch(new GenericCommandMessage<Object>(new CreateCommand(aggregateIdentifier)));
testSubject.stop();
DomainEventMessage lastEvent = inMemoryEventStore.storedEvents.get(aggregateIdentifier);
// we expect 2 events, 1 from aggregate constructor, one from doSomething method invocation
assertEquals(1, lastEvent.getSequenceNumber());
// check that both events are published in a single call
verify(eventBus).publish(isA(EventMessage.class), isA(EventMessage.class));
assertEquals(aggregateIdentifier, lastEvent.getAggregateIdentifier());
}
@Test(expected = IllegalStateException.class)
public void testCommandRejectedAfterShutdown() throws InterruptedException {
testSubject = new DisruptorCommandBus(inMemoryEventStore, eventBus);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
testSubject.stop();
testSubject.dispatch(new GenericCommandMessage<Object>(new Object()));
}
@Test
public void testCommandProcessedAndEventsStored() throws InterruptedException {
testSubject = new DisruptorCommandBus(inMemoryEventStore, eventBus);
testSubject.subscribe(StubCommand.class.getName(), stubHandler);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
for (int i = 0; i < COMMAND_COUNT; i++) {
CommandMessage<StubCommand> command = new GenericCommandMessage<StubCommand>(
new StubCommand(aggregateIdentifier));
testSubject.dispatch(command);
}
inMemoryEventStore.countDownLatch.await(5, TimeUnit.SECONDS);
eventBus.publisherCountDown.await(1, TimeUnit.SECONDS);
assertEquals("Seems that some events are not published", 0, eventBus.publisherCountDown.getCount());
assertEquals("Seems that some events are not stored", 0, inMemoryEventStore.countDownLatch.getCount());
}
@Test(timeout = 10000)
public void testCommandsAgainstInexistentAggregatesOnlyRetriedOnce() throws Throwable {
inMemoryEventStore.storedEvents.clear();
testSubject = new DisruptorCommandBus(inMemoryEventStore, eventBus);
stubHandler.setRepository(testSubject
.createRepository(new GenericAggregateFactory<StubAggregate>(StubAggregate.class)));
StubHandler spy = spy(stubHandler);
testSubject.subscribe(StubCommand.class.getName(), spy);
List<CommandMessage<StubCommand>> dispatchedCommands = new ArrayList<CommandMessage<StubCommand>>();
for (int i = 0; i < 10; i++) {
CommandMessage<StubCommand> subsequentCommand = new GenericCommandMessage<StubCommand>(
new StubCommand(aggregateIdentifier));
testSubject.dispatch(subsequentCommand, NoOpCallback.INSTANCE);
dispatchedCommands.add(subsequentCommand);
}
testSubject.stop();
assertTrue(inMemoryEventStore.storedEvents.isEmpty());
for (CommandMessage<StubCommand> command : dispatchedCommands) {
// subsequent commands could be retried more that once, as the aggregate they work in is blacklisted
verify(spy, times(2)).handle(same(command), isA(UnitOfWork.class));
}
}
private static class StubAggregate extends AbstractEventSourcedAggregateRoot {
private static final long serialVersionUID = 8192033940704210095L;
private String identifier;
private StubAggregate(String identifier) {
this.identifier = identifier;
apply(new SomethingDoneEvent());
}
@SuppressWarnings("UnusedDeclaration")
public StubAggregate() {
}
@Override
public Object getIdentifier() {
return identifier;
}
public void doSomething() {
apply(new SomethingDoneEvent());
}
public void createFailingEvent() {
apply(new FailingEvent());
}
@Override
protected void handle(DomainEventMessage event) {
identifier = (String) event.getAggregateIdentifier();
}
@Override
protected Collection<EventSourcedEntity> getChildEntities() {
return Collections.emptyList();
}
}
private static class InMemoryEventStore implements EventStore {
private final Map<String, DomainEventMessage> storedEvents = new ConcurrentHashMap<String, DomainEventMessage>();
private final CountDownLatch countDownLatch = new CountDownLatch((int) (COMMAND_COUNT + 1L));
@Override
public void appendEvents(String type, DomainEventStream events) {
if (!events.hasNext()) {
return;
}
String key = events.peek().getAggregateIdentifier().toString();
DomainEventMessage<?> lastEvent = null;
while (events.hasNext()) {
countDownLatch.countDown();
lastEvent = events.next();
if (FailingEvent.class.isAssignableFrom(lastEvent.getPayloadType())) {
throw new RuntimeException("This is a failing event. EventStore refuses to store that");
}
}
storedEvents.put(key, lastEvent);
}
@Override
public DomainEventStream readEvents(String type, Object identifier) {
DomainEventMessage message = storedEvents.get(identifier.toString());
if (message == null) {
throw new EventStreamNotFoundException(type, identifier);
}
return new SimpleDomainEventStream(Collections.singletonList(message));
}
}
private static class StubCommand {
@TargetAggregateIdentifier
private Object aggregateIdentifier;
public StubCommand(Object aggregateIdentifier) {
this.aggregateIdentifier = aggregateIdentifier;
}
public Object getAggregateIdentifier() {
return aggregateIdentifier;
}
}
private static class ErrorCommand extends StubCommand {
public ErrorCommand(Object aggregateIdentifier) {
super(aggregateIdentifier);
}
}
private static class ExceptionCommand extends StubCommand {
private final Exception exception;
public ExceptionCommand(Object aggregateIdentifier, Exception exception) {
super(aggregateIdentifier);
this.exception = exception;
}
public Exception getException() {
return exception;
}
}
private static class CreateCommand extends StubCommand {
public CreateCommand(Object aggregateIdentifier) {
super(aggregateIdentifier);
}
}
private static class StubHandler implements CommandHandler<StubCommand> {
private Repository<StubAggregate> repository;
private StubHandler() {
}
@Override
public Object handle(CommandMessage<StubCommand> command, UnitOfWork unitOfWork) throws Throwable {
if (ExceptionCommand.class.isAssignableFrom(command.getPayloadType())) {
throw ((ExceptionCommand) command.getPayload()).getException();
} else if (CreateCommand.class.isAssignableFrom(command.getPayloadType())) {
StubAggregate aggregate = new StubAggregate(command.getPayload().getAggregateIdentifier().toString());
repository.add(aggregate);
aggregate.doSomething();
} else {
StubAggregate aggregate = repository.load(command.getPayload().getAggregateIdentifier());
if (ErrorCommand.class.isAssignableFrom(command.getPayloadType())) {
aggregate.createFailingEvent();
} else {
aggregate.doSomething();
}
}
return Void.TYPE;
}
public void setRepository(Repository<StubAggregate> repository) {
this.repository = repository;
}
}
private static class StubDomainEvent {
}
private static class CountingEventBus implements EventBus {
private final CountDownLatch publisherCountDown = new CountDownLatch(COMMAND_COUNT);
@Override
public void publish(EventMessage... events) {
publisherCountDown.countDown();
}
@Override
public void subscribe(EventListener eventListener) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void unsubscribe(EventListener eventListener) {
throw new UnsupportedOperationException("Not implemented yet");
}
}
/**
* @author Allard Buijze
*/
static class FailingEvent {
}
private static class Parameter implements Answer<Object> {
private final int index;
private Parameter(int index) {
this.index = index;
}
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArguments()[index];
}
}
private static class IsSerializationAware extends TypeSafeMatcher<EventMessage> {
@Override
public boolean matchesSafely(EventMessage item) {
return item instanceof SerializationAware;
}
@Override
public void describeTo(Description description) {
description.appendText("Serialization aware messages");
}
}
private static class StreamWithSerializationAwareEvents extends TypeSafeMatcher<DomainEventStream> {
@Override
public boolean matchesSafely(DomainEventStream item) {
while (item.hasNext()) {
if (!(item.next() instanceof SerializationAware)) {
return false;
}
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText("Stream with Serialization Aware events");
}
}
}