/*
* Copyright (c) 2010-2014. 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.contextsupport.spring;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.ProducerType;
import org.axonframework.cache.WeakReferenceCache;
import org.axonframework.commandhandling.disruptor.DisruptorCommandBus;
import org.axonframework.commandhandling.disruptor.DisruptorConfiguration;
import org.axonframework.common.Assert;
import org.axonframework.eventsourcing.AggregateFactory;
import org.axonframework.eventsourcing.CompositeEventStreamDecorator;
import org.axonframework.eventsourcing.EventSourcedAggregateRoot;
import org.axonframework.eventsourcing.EventStreamDecorator;
import org.axonframework.eventsourcing.GenericAggregateFactory;
import org.axonframework.eventsourcing.SnapshotterTrigger;
import org.axonframework.repository.Repository;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
/**
* BeanDefinitionParser that parses <code><disruptor-command-bus></code> elements in the Spring context
*
* @author Allard Buijze
* @since 2.0
*/
public class DisruptorCommandBusBeanDefinitionParser extends AbstractBeanDefinitionParser {
private static final String ATTRIBUTE_EVENT_STORE = "event-store";
private static final String ATTRIBUTE_EVENT_BUS = "event-bus";
private static final String ELEMENT_REPOSITORY = "repository";
private static final String ATTRIBUTE_ID = "id";
private static final String ATTRIBUTE_AGGREGATE_FACTORY = "aggregate-factory";
private static final String ATTRIBUTE_AGGREGATE_TYPE = "aggregate-type";
private static final String PROPERTY_WAIT_STRATEGY = "waitStrategy";
private static final String ATTRIBUTE_WAIT_STRATEGY = "wait-strategy";
private static final String ATTRIBUTE_CLAIM_STRATEGY = "claim-strategy";
private static final String PROPERTY_PRODUCER_TYPE = "producerType";
private static final String ATTRIBUTE_PRODUCER_TYPE = "producer-type";
private static final String ELEMENT_REPOSITORIES = "repositories";
private static final String EVENT_PROCESSORS_ELEMENT = "event-processors";
private static final String SNAPSHOT_TRIGGER_ELEMENT = "snapshotter-trigger";
private static final String EVENT_STREAM_DECORATORS_PROPERTY = "eventStreamDecorators";
private static final String SNAPSHOTTER_TRIGGER_PROPERTY = "snapshotterTrigger";
private static final String ATTRIBUTE_TRANSACTION_MANAGER = "transaction-manager";
private static final String PROPERTY_TRANSACTION_MANAGER = "transactionManager";
private static final Map<String, String> VALUE_PROPERTY_MAPPING = new HashMap<String, String>();
private static final Map<String, String> REF_PROPERTY_MAPPING = new HashMap<String, String>();
private static final Map<String, String> LIST_PROPERTY_MAPPING = new HashMap<String, String>();
private final SnapshotterTriggerBeanDefinitionParser snapshotterTriggerParser =
new SnapshotterTriggerBeanDefinitionParser();
static {
REF_PROPERTY_MAPPING.put("cache", "cache");
REF_PROPERTY_MAPPING.put("executor", "executor");
REF_PROPERTY_MAPPING.put("rollback-configuration", "rollbackConfiguration");
REF_PROPERTY_MAPPING.put("serializer", "serializer");
REF_PROPERTY_MAPPING.put("command-target-resolver", "commandTargetResolver");
VALUE_PROPERTY_MAPPING.put("cooling-down-period", "coolingDownPeriod");
VALUE_PROPERTY_MAPPING.put("invoker-threads", "invokerThreadCount");
VALUE_PROPERTY_MAPPING.put("serializer-threads", "serializerThreadCount");
VALUE_PROPERTY_MAPPING.put("publisher-threads", "publisherThreadCount");
VALUE_PROPERTY_MAPPING.put("reschedule-commands-on-corrupt-state", "rescheduleCommandsOnCorruptState");
VALUE_PROPERTY_MAPPING.put("serialized-representation", "serializedRepresentation");
VALUE_PROPERTY_MAPPING.put("buffer-size", "bufferSize");
LIST_PROPERTY_MAPPING.put("invoker-interceptors", "invokerInterceptors");
LIST_PROPERTY_MAPPING.put("publisher-interceptors", "publisherInterceptors");
LIST_PROPERTY_MAPPING.put("dispatcher-interceptors", "dispatchInterceptors");
}
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
final BeanDefinition configurationDefinition = createConfiguration(element, parserContext);
final AbstractBeanDefinition definition =
genericBeanDefinition(DisruptorCommandBus.class)
.addConstructorArgReference(element.getAttribute(ATTRIBUTE_EVENT_STORE))
.addConstructorArgReference(element.getAttribute(ATTRIBUTE_EVENT_BUS))
.addConstructorArgValue(configurationDefinition)
.getBeanDefinition();
Element repoElement = DomUtils.getChildElementByTagName(element, ELEMENT_REPOSITORIES);
List<Element> repositories = DomUtils.getChildElementsByTagName(repoElement, ELEMENT_REPOSITORY);
String id = super.resolveId(element, definition, parserContext);
for (Element repository : repositories) {
parseRepository(repository, id, parserContext,
configurationDefinition.getPropertyValues().getPropertyValue("cache"));
}
definition.setDestroyMethodName("stop");
return definition;
}
private BeanDefinition createConfiguration(Element element, ParserContext parserContext) {
final BeanDefinitionBuilder builder = genericBeanDefinition(DisruptorConfiguration.class);
for (Map.Entry<String, String> entry : REF_PROPERTY_MAPPING.entrySet()) {
if (element.hasAttribute(entry.getKey())) {
builder.addPropertyReference(entry.getValue(), element.getAttribute(entry.getKey()));
}
}
for (Map.Entry<String, String> entry : VALUE_PROPERTY_MAPPING.entrySet()) {
if (element.hasAttribute(entry.getKey())) {
builder.addPropertyValue(entry.getValue(), element.getAttribute(entry.getKey()));
}
}
parseClaimStrategy(element, builder);
parseProducerType(element, builder);
parseWaitStrategy(element, builder);
parseTransactionManager(element, builder);
for (Map.Entry<String, String> entry : LIST_PROPERTY_MAPPING.entrySet()) {
final Element interceptorsElement = DomUtils.getChildElementByTagName(element, entry.getKey());
if (interceptorsElement != null) {
builder.addPropertyValue(entry.getValue(),
parserContext.getDelegate().parseListElement(interceptorsElement,
builder.getBeanDefinition())
);
}
}
return builder.getBeanDefinition();
}
private void parseTransactionManager(Element element, BeanDefinitionBuilder builder) {
if (element.hasAttribute(ATTRIBUTE_TRANSACTION_MANAGER)) {
final String txManagerId = element.getAttribute(ATTRIBUTE_TRANSACTION_MANAGER);
builder.addPropertyValue(PROPERTY_TRANSACTION_MANAGER,
BeanDefinitionBuilder.genericBeanDefinition(TransactionManagerFactoryBean.class)
.addPropertyReference(PROPERTY_TRANSACTION_MANAGER,
txManagerId)
.getBeanDefinition()
);
}
}
@Deprecated
private void parseClaimStrategy(Element element, BeanDefinitionBuilder builder) {
final BeanDefinitionBuilder producerType = BeanDefinitionBuilder
.genericBeanDefinition(ProducerTypeFactoryBean.class);
if (element.hasAttribute(ATTRIBUTE_CLAIM_STRATEGY)) {
producerType.addPropertyValue("type", element.getAttribute(ATTRIBUTE_CLAIM_STRATEGY));
}
builder.addPropertyValue(PROPERTY_PRODUCER_TYPE, producerType.getBeanDefinition());
}
private void parseProducerType(Element element, BeanDefinitionBuilder builder) {
final BeanDefinitionBuilder producerType = BeanDefinitionBuilder
.genericBeanDefinition(ProducerTypeFactoryBean.class);
if (element.hasAttribute(ATTRIBUTE_PRODUCER_TYPE)) {
producerType.addPropertyValue("type", element.getAttribute(ATTRIBUTE_PRODUCER_TYPE));
}
builder.addPropertyValue(PROPERTY_PRODUCER_TYPE, producerType.getBeanDefinition());
}
private void parseWaitStrategy(Element element, BeanDefinitionBuilder builder) {
if (element.hasAttribute(ATTRIBUTE_WAIT_STRATEGY)) {
String waitStrategy = element.getAttribute(ATTRIBUTE_WAIT_STRATEGY);
builder.addPropertyValue(PROPERTY_WAIT_STRATEGY,
BeanDefinitionBuilder.genericBeanDefinition(WaitStrategyFactoryBean.class)
.addConstructorArgValue(waitStrategy)
.getBeanDefinition()
);
}
}
private void parseRepository(Element repository, String commandBusId, ParserContext parserContext,
PropertyValue aggregateCache) {
String id = repository.getAttribute(ATTRIBUTE_ID);
BeanDefinitionBuilder definitionBuilder =
genericBeanDefinition(RepositoryFactoryBean.class)
.addPropertyReference("commandBus", commandBusId);
if (repository.hasAttribute(ATTRIBUTE_AGGREGATE_FACTORY)) {
definitionBuilder = definitionBuilder
.addPropertyReference("aggregateFactory", repository.getAttribute(ATTRIBUTE_AGGREGATE_FACTORY));
} else {
final String aggregateType = repository.getAttribute(ATTRIBUTE_AGGREGATE_TYPE);
Assert.notEmpty(aggregateType, "Either one of 'aggregate-type' or 'aggregate-factory' attributes must be "
+ "set on repository elements in <disruptor-command-bus>");
definitionBuilder = definitionBuilder
.addPropertyValue("aggregateFactory", genericBeanDefinition(GenericAggregateFactory.class)
.addConstructorArgValue(aggregateType)
.addConstructorArgValue(SpringContextParameterResolverFactoryBuilder
.getBeanReference(parserContext.getRegistry()))
.getBeanDefinition());
}
Element processorsElement = DomUtils.getChildElementByTagName(repository, EVENT_PROCESSORS_ELEMENT);
Element snapshotTriggerElement = DomUtils.getChildElementByTagName(repository, SNAPSHOT_TRIGGER_ELEMENT);
if (snapshotTriggerElement != null) {
BeanDefinition triggerDefinition = snapshotterTriggerParser.parse(snapshotTriggerElement, parserContext);
if (aggregateCache != null) {
triggerDefinition.getPropertyValues().add("aggregateCache", aggregateCache.getValue());
} else {
// the DisruptorCommandBus uses an internal cache. Not defining any cache on the snapshotter trigger
// would lead to undesired side-effects (mainly missing triggers).
triggerDefinition.getPropertyValues().add("aggregateCache", BeanDefinitionBuilder
.genericBeanDefinition(WeakReferenceCache.class).getBeanDefinition());
}
definitionBuilder = definitionBuilder.addPropertyValue(SNAPSHOTTER_TRIGGER_PROPERTY, triggerDefinition);
}
final AbstractBeanDefinition definition = definitionBuilder.getBeanDefinition();
if (processorsElement != null) {
//noinspection unchecked
List<Object> processorsList = parserContext.getDelegate().parseListElement(processorsElement,
definition);
if (!processorsList.isEmpty()) {
definition.getPropertyValues().add(EVENT_STREAM_DECORATORS_PROPERTY, processorsList);
}
}
parserContext.getRegistry().registerBeanDefinition(id, definition);
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
/**
* Factory bean that creates a ProducerType instance.
*/
private static final class ProducerTypeFactoryBean implements FactoryBean<ProducerType>, InitializingBean {
private ProducerType producerType;
private String type;
@Override
public ProducerType getObject() throws Exception {
return producerType;
}
@Override
public Class<?> getObjectType() {
return ProducerType.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
if ("single-threaded".equals(type)) {
producerType = ProducerType.SINGLE;
} else {
producerType = ProducerType.MULTI;
}
}
/**
* Sets the name of the producer type to use.
*
* @param type the name of the producer type to use
*/
@SuppressWarnings("UnusedDeclaration")
public void setType(String type) {
Assert.isTrue("single-threaded".equals(type) || "multi-threaded".equals(type),
"The given value for producer type (" + type
+ ") is not valid. It must either be 'single-threaded' or 'multi-threaded'."
);
this.type = type;
}
}
private static final class WaitStrategyFactoryBean implements FactoryBean<WaitStrategy> {
private final WaitStrategy waitStrategy;
@SuppressWarnings("UnusedDeclaration")
private WaitStrategyFactoryBean(String strategyName) {
if ("busy-spin".equals(strategyName)) {
waitStrategy = new BusySpinWaitStrategy();
} else if ("yield".equals(strategyName)) {
waitStrategy = new YieldingWaitStrategy();
} else if ("sleep".equals(strategyName)) {
waitStrategy = new SleepingWaitStrategy();
} else if ("block".equals(strategyName)) {
waitStrategy = new BlockingWaitStrategy();
} else {
throw new IllegalArgumentException("WaitStrategy is not one of the allowed values: "
+ "busy-spin, yield, sleep or block.");
}
}
@Override
public WaitStrategy getObject() throws Exception {
return waitStrategy;
}
@Override
public Class<?> getObjectType() {
return WaitStrategy.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
/**
* FactoryBean to create repository instances for use with the DisruptorCommandBus
*/
public static class RepositoryFactoryBean implements FactoryBean<Repository> {
private DisruptorCommandBus commandBus;
private List<EventStreamDecorator> eventStreamDecorators = new ArrayList<EventStreamDecorator>();
private AggregateFactory<? extends EventSourcedAggregateRoot> factory;
@Override
public Repository getObject() throws Exception {
if (eventStreamDecorators.isEmpty()) {
return commandBus.createRepository(factory);
}
return commandBus.createRepository(factory, new CompositeEventStreamDecorator(eventStreamDecorators));
}
@Override
public Class<?> getObjectType() {
return Repository.class;
}
@Override
public boolean isSingleton() {
return true;
}
/**
* The DisruptorCommandBus instance to create the repository from. This is a required property, but cannot be
* set using constructor injection, as Spring will see it as a circular dependency.
*
* @param commandBus DisruptorCommandBus instance to create the repository from
*/
@Required
public void setCommandBus(DisruptorCommandBus commandBus) {
this.commandBus = commandBus;
}
/**
* Sets the aggregate factory used to create instances for the repository to create. This is a required
* property, but cannot be set using constructor injection, as Spring will see it as a circular dependency.
*
* @param factory the aggregate factory used to create instances for the repository to create
*/
@Required
public void setAggregateFactory(AggregateFactory<? extends EventSourcedAggregateRoot> factory) {
this.factory = factory;
}
/**
* Sets the (additional) decorators to use when loading and storing events from/to the Event Store.
*
* @param decorators the decorators to decorate event streams with
*/
@SuppressWarnings("UnusedDeclaration")
public void setEventStreamDecorators(List<EventStreamDecorator> decorators) {
eventStreamDecorators.addAll(decorators);
}
/**
* The snapshotter trigger instance that will decide when to trigger a snapshot
*
* @param snapshotterTrigger The trigger to configure on the DisruptorCommandBus
*/
@SuppressWarnings("UnusedDeclaration")
public void setSnapshotterTrigger(SnapshotterTrigger snapshotterTrigger) {
eventStreamDecorators.add(snapshotterTrigger);
}
}
}