/*
* 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.commandhandling.model;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.GenericCommandMessage;
import org.axonframework.commandhandling.model.inspection.AggregateModel;
import org.axonframework.commandhandling.model.inspection.ModelInspector;
import org.axonframework.eventhandling.EventHandler;
import org.axonframework.eventhandling.GenericEventMessage;
import org.axonframework.messaging.annotation.MessageHandlingMember;
import org.junit.Test;
import javax.persistence.Id;
import java.lang.annotation.*;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import static org.axonframework.commandhandling.GenericCommandMessage.asCommandMessage;
import static org.axonframework.eventhandling.GenericEventMessage.asEventMessage;
import static org.junit.Assert.*;
public class ModelInspectorTest {
@Test
public void testDetectAllAnnotatedHandlers() throws Exception {
AggregateModel<SomeAnnotatedHandlers> inspector = ModelInspector.inspectAggregate(SomeAnnotatedHandlers.class);
CommandMessage<?> message = asCommandMessage("ok");
assertEquals(true, inspector.commandHandler(message.getCommandName()).handle(message, new SomeAnnotatedHandlers()));
assertEquals(false, inspector.commandHandler(message.getCommandName()).handle(asCommandMessage("ko"), new SomeAnnotatedHandlers()));
}
@Test
public void testDetectAllAnnotatedHandlersInHierarchy() throws Exception {
AggregateModel<SomeSubclass> inspector = ModelInspector.inspectAggregate(SomeSubclass.class);
SomeSubclass target = new SomeSubclass();
CommandMessage<?> message = asCommandMessage("sub");
assertEquals(true, inspector.commandHandler(message.getCommandName()).handle(message, target));
assertEquals(false, inspector.commandHandler(message.getCommandName()).handle(asCommandMessage("ok"), target));
}
@Test
public void testEventIsPublishedThroughoutRecursiveHierarchy() throws Exception {
// Note that if the inspector does not support recursive entities this will throw an StackOverflowError.
AggregateModel<SomeRecursiveEntity> inspector = ModelInspector.inspectAggregate(SomeRecursiveEntity.class);
// Create a hierarchy that we will use in this test.
// The resulting hierarchy will look as follows:
// root
// child1
// child2
// child3
// child4
String rootId = "root";
String childId1 = "child1";
String childId2 = "child2";
String childId3 = "child3";
String childId4 = "child4";
SomeRecursiveEntity root = new SomeRecursiveEntity(LinkedList::new, null, rootId);
// Assert root: it should not have any children (yet)
assertEquals(0, root.children.size());
// Publish an event that is picked up by root. It should have 1 child afterwards.
inspector.publish(asEventMessage(new CreateChild(rootId, childId1)), root);
assertEquals(1, root.children.size());
// Assert child1: it should not have any children (yet)
SomeRecursiveEntity child1 = root.getChild(childId1);
assertNotNull(child1);
assertEquals(0, child1.children.size());
// Publish 2 events that are picked up by child1. It should have 2 children afterwards.
inspector.publish(asEventMessage(new CreateChild(childId1, childId2)), root);
inspector.publish(asEventMessage(new CreateChild(childId1, childId3)), root);
assertEquals(2, child1.children.size());
// Assert child2: it should not have any children
SomeRecursiveEntity child2 = child1.getChild(childId2);
assertNotNull(child2);
assertEquals(0, child2.children.size());
// Assert child3: it should not have any children
SomeRecursiveEntity child3 = child1.getChild(childId3);
assertNotNull(child3);
assertEquals(0, child3.children.size());
// Publish an event that is picked up by child3. It should have 1 child afterwards.
inspector.publish(asEventMessage(new CreateChild(childId3, childId4)), root);
assertEquals(1, child3.children.size());
// Assert child4: it should not have any children
SomeRecursiveEntity child4 = child3.getChild(childId4);
assertNotNull(child4);
assertEquals(0, child4.children.size());
}
@Test
public void testLinkedListIsModifiedDuringIterationInRecursiveHierarchy() {
testCollectionIsModifiedDuringIterationInRecursiveHierarchy(LinkedList::new);
}
@Test
public void testHashSetIsModifiedDuringIterationInRecursiveHierarchy() {
testCollectionIsModifiedDuringIterationInRecursiveHierarchy(HashSet::new);
}
@Test
public void testCopyOnWriteArrayListIsModifiedDuringIterationInRecursiveHierarchy() {
testCollectionIsModifiedDuringIterationInRecursiveHierarchy(CopyOnWriteArrayList::new);
}
@Test
public void testConcurrentLinkedQueueIsModifiedDuringIterationInRecursiveHierarchy() {
testCollectionIsModifiedDuringIterationInRecursiveHierarchy(ConcurrentLinkedQueue::new);
}
private void testCollectionIsModifiedDuringIterationInRecursiveHierarchy(Supplier<Collection<SomeRecursiveEntity>> supplier) {
// Note that if the inspector does not support recursive entities this will throw an StackOverflowError.
AggregateModel<SomeRecursiveEntity> inspector = ModelInspector.inspectAggregate(SomeRecursiveEntity.class);
// Create a hierarchy that we will use in this test.
// The resulting hierarchy will look as follows:
// root
// child1
// child2
// child3
String rootId = "root";
String childId1 = "child1";
String childId2 = "child2";
String childId3 = "child3";
SomeRecursiveEntity root = new SomeRecursiveEntity(supplier, null, rootId);
inspector.publish(asEventMessage(new CreateChild(rootId, childId1)), root);
inspector.publish(asEventMessage(new CreateChild(childId1, childId2)), root);
inspector.publish(asEventMessage(new CreateChild(childId2, childId3)), root);
SomeRecursiveEntity child1 = root.getChild(childId1);
// Assert child1: it should have 1 child
assertEquals(1, child1.children.size());
// Now move child3 up one level so it is a child of child1.
// The resulting hierarchy will look as follows:
// root
// child1
// child2
// child3
// Note that if the inspector does not use copy-iterators this will throw an ConcurrentModificationException.
inspector.publish(asEventMessage(new MoveChildUp(childId2, childId3)), root);
assertEquals(2, child1.children.size());
}
@Test
public void testEventIsPublishedThroughoutHierarchy() throws Exception {
AggregateModel<SomeSubclass> inspector = ModelInspector.inspectAggregate(SomeSubclass.class);
AtomicLong payload = new AtomicLong();
inspector.publish(new GenericEventMessage<>(payload), new SomeSubclass());
assertEquals(2L, payload.get());
}
@Test
public void testExpectCommandToBeForwardedToEntity() throws Exception {
AggregateModel<SomeSubclass> inspector = ModelInspector.inspectAggregate(SomeSubclass.class);
GenericCommandMessage<?> message = new GenericCommandMessage<>(BigDecimal.ONE);
SomeSubclass target = new SomeSubclass();
MessageHandlingMember<? super SomeSubclass> handler = inspector.commandHandler(message.getCommandName());
assertEquals("1", handler.handle(message, target));
}
@Test
public void testFindIdentifier() throws Exception {
AggregateModel<SomeAnnotatedHandlers> inspector = ModelInspector.inspectAggregate(SomeAnnotatedHandlers.class);
assertEquals("SomeAnnotatedHandlers", inspector.type());
assertEquals("id", inspector.getIdentifier(new SomeAnnotatedHandlers()));
assertEquals("id", inspector.routingKey());
}
@Test
public void testFindJavaxPersistenceIdentifier() throws Exception {
AggregateModel<JavaxPersistenceAnnotatedHandlers> inspector = ModelInspector.inspectAggregate(JavaxPersistenceAnnotatedHandlers.class);
assertEquals("id", inspector.getIdentifier(new JavaxPersistenceAnnotatedHandlers()));
assertEquals("id", inspector.routingKey());
}
@Test
public void testFindIdentifierInSuperClass() throws Exception {
AggregateModel<SomeSubclass> inspector = ModelInspector.inspectAggregate(SomeSubclass.class);
assertEquals("SomeOtherName", inspector.type());
assertEquals("id", inspector.getIdentifier(new SomeSubclass()));
}
private static class JavaxPersistenceAnnotatedHandlers {
@Id
private String id = "id";
@CommandHandler(commandName = "java.lang.String")
public boolean handle(CharSequence test) {
return test.equals("ok");
}
@CommandHandler
public boolean testInt(Integer test) {
return test > 0;
}
}
private static class SomeAnnotatedHandlers {
@AggregateIdentifier
private String id = "id";
@CommandHandler(commandName = "java.lang.String")
public boolean handle(CharSequence test) {
return test.equals("ok");
}
@CommandHandler
public boolean testInt(Integer test) {
return test > 0;
}
}
@AggregateRoot(type = "SomeOtherName")
private static class SomeSubclass extends SomeAnnotatedHandlers {
@AggregateMember
private SomeOtherEntity entity = new SomeOtherEntity();
@MyCustomCommandHandler
public boolean handleInSubclass(String test) {
return test.contains("sub");
}
@MyCustomEventHandler
public void handle(AtomicLong value) {
value.incrementAndGet();
}
}
private static class SomeOtherEntity {
@CommandHandler
public String handle(BigDecimal cmd) {
return cmd.toPlainString();
}
@EventHandler
public void handle(AtomicLong value) {
value.incrementAndGet();
}
}
private static class CreateChild {
private final String parentId;
private final String childId;
public CreateChild(String parentId, String childId) {
this.parentId = parentId;
this.childId = childId;
}
}
private static class MoveChildUp {
private final String parentId;
private final String childId;
private MoveChildUp(String parentId, String childId) {
this.parentId = parentId;
this.childId = childId;
}
}
private static class SomeRecursiveEntity {
private final Supplier<Collection<SomeRecursiveEntity>> supplier;
private final SomeRecursiveEntity parent;
private final String entityId;
@AggregateMember
private final Collection<SomeRecursiveEntity> children;
public SomeRecursiveEntity(Supplier<Collection<SomeRecursiveEntity>> supplier, SomeRecursiveEntity parent, String entityId) {
this.supplier = supplier;
this.parent = parent;
this.entityId = entityId;
this.children = supplier.get();
}
public SomeRecursiveEntity getChild(String childId) {
for(SomeRecursiveEntity c : children) {
if(Objects.equals(c.entityId, childId)) {
return c;
}
}
return null;
}
@EventHandler
public void handle(CreateChild event) {
if (Objects.equals(this.entityId, event.parentId)) {
children.add(new SomeRecursiveEntity(supplier, this, event.childId));
}
}
@EventHandler
public void handle(MoveChildUp event) {
if (Objects.equals(this.entityId, event.parentId)) {
SomeRecursiveEntity child = getChild(event.childId);
assertNotNull(child);
assertTrue(this.children.remove(child));
assertTrue(parent.children.add(child));
}
}
}
@Documented
@EventHandler
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomEventHandler {
}
@Documented
@CommandHandler
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomCommandHandler {
}
}