package org.axonframework.cdi; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Destroyed; import javax.enterprise.context.Initialized; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.AfterBeanDiscovery; import javax.enterprise.inject.spi.AfterDeploymentValidation; import javax.enterprise.inject.spi.AnnotatedMember; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeShutdown; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.inject.spi.ProcessBean; import javax.enterprise.inject.spi.ProcessProducer; import javax.enterprise.inject.spi.Producer; import javax.enterprise.inject.spi.WithAnnotations; import javax.inject.Named; import org.axonframework.cdi.stereotype.Aggregate; import org.axonframework.cdi.stereotype.Saga; import org.axonframework.commandhandling.CommandBus; import org.axonframework.commandhandling.CommandTargetResolver; import org.axonframework.commandhandling.gateway.CommandGateway; import org.axonframework.commandhandling.model.GenericJpaRepository; import org.axonframework.commandhandling.model.Repository; import org.axonframework.common.jpa.EntityManagerProvider; import org.axonframework.common.lock.LockFactory; import org.axonframework.common.lock.NullLockFactory; import org.axonframework.common.transaction.TransactionManager; import org.axonframework.config.AggregateConfigurer; import org.axonframework.config.Component; import org.axonframework.config.Configuration; import org.axonframework.config.Configurer; import org.axonframework.config.ConfigurerModule; import org.axonframework.config.DefaultConfigurer; import org.axonframework.config.EventHandlingConfiguration; import org.axonframework.config.EventProcessingConfiguration; import org.axonframework.config.ModuleConfiguration; import org.axonframework.config.SagaConfiguration; import org.axonframework.deadline.DeadlineManager; import org.axonframework.eventhandling.ErrorHandler; import org.axonframework.eventhandling.EventBus; import org.axonframework.eventhandling.ListenerInvocationErrorHandler; import org.axonframework.eventhandling.saga.repository.SagaStore; import org.axonframework.eventhandling.tokenstore.TokenStore; import org.axonframework.eventsourcing.SnapshotTriggerDefinition; import org.axonframework.eventsourcing.eventstore.EventStorageEngine; import org.axonframework.messaging.correlation.CorrelationDataProvider; import org.axonframework.queryhandling.QueryBus; import org.axonframework.queryhandling.QueryGateway; import org.axonframework.queryhandling.QueryUpdateEmitter; import org.axonframework.serialization.Serializer; import org.axonframework.serialization.upcasting.event.EventUpcaster; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Main CDI extension class responsible for collecting CDI beans and setting up * Axon configuration. */ public class AxonCdiExtension implements Extension { private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass()); private final List<AggregateDefinition> aggregates = new ArrayList<>(); private final Map<String, Producer<Repository>> aggregateRepositoryProducerMap = new HashMap<>(); private final Map<String, Producer<SnapshotTriggerDefinition>> snapshotTriggerDefinitionProducerMap = new HashMap<>(); private final Map<String, Producer<CommandTargetResolver>> commandTargetResolverProducerMap = new HashMap<>(); private final List<SagaDefinition> sagas = new ArrayList<>(); private final Map<String, Producer<SagaStore>> sagaStoreProducerMap = new HashMap<>(); private final Map<String, Producer<SagaConfiguration<?>>> sagaConfigurationProducerMap = new HashMap<>(); private final List<MessageHandlingBeanDefinition> messageHandlers = new ArrayList<>(); private Producer<EventStorageEngine> eventStorageEngineProducer; private Producer<Serializer> serializerProducer; private Producer<Serializer> eventSerializerProducer; private Producer<Serializer> messageSerializerProducer; private Producer<EventBus> eventBusProducer; private Producer<CommandBus> commandBusProducer; // private Producer<CommandGateway> commandGatewayProducer; private Producer<Configurer> configurerProducer; private Producer<TransactionManager> transactionManagerProducer; private Producer<EntityManagerProvider> entityManagerProviderProducer; private Producer<TokenStore> tokenStoreProducer; private Producer<ListenerInvocationErrorHandler> listenerInvocationErrorHandlerProducer; private Producer<ErrorHandler> errorHandlerProducer; private final List<Producer<CorrelationDataProvider>> correlationDataProviderProducers = new ArrayList<>(); private Producer<QueryBus> queryBusProducer; private Producer<QueryGateway> queryGatewayProducer; private Producer<QueryUpdateEmitter> queryUpdateEmitterProducer; private final List<Producer<ModuleConfiguration>> moduleConfigurationProducers = new ArrayList<>(); private final List<Producer<ConfigurerModule>> configurerModuleProducers = new ArrayList<>(); private final List<Producer<EventUpcaster>> eventUpcasterProducers = new ArrayList<>(); private Producer<DeadlineManager> deadlineManagerProducer; private Producer<EventHandlingConfiguration> eventHandlingConfigurationProducer; private Producer<EventProcessingConfiguration> eventProcessingConfigurationProducer; // Mark: Many of the beans and producers I am processing may use // container resources such as entity managers, etc. I believe this means // I should be handling them as late as possible to avoid initialization // timing issues. Right now things are processed as they make // "semantic" sense. Do you think this could be improved to do the same // processing later? //X @struberg: it will work. But probably might be better to observe ProcessBeanAttributes pba and use pba.getAnnotated //X that way you will also have the ability to pick up modifications of those classes via ProcessAnnotatedType. //X Not sure if this is really needed for your users though. /** * Scans all annotated types with the {@link Aggregate} annotation and * collects them for registration. * * @param processAnnotatedType annotated type processing event. */ // Mark: All I need to do here is look up what the aggregate classes are // and what the value of the @Aggregate annotation is. This feels a little // overkill. That said, currently I do need these values in afterBeanDiscovery // and this might be the most efficient way of collecting these anyway. // Other than being a bit ugly, this is not anything that is causing any // functional issues. <T> void processAggregate(@Observes @WithAnnotations({Aggregate.class}) final ProcessAnnotatedType<T> processAnnotatedType) { // TODO Aggregate classes may need to be vetoed so that CDI does not // actually try to manage them. final Class<?> clazz = processAnnotatedType.getAnnotatedType().getJavaClass(); logger.debug("Found aggregate: {}.", clazz); aggregates.add(new AggregateDefinition(clazz)); } // Mark: Same issues with the stuff below. A bit ugly but functional. I may // be able to get rid of most fo this by doing BeanManager look ups right // after afterBeanDiscovery or later. <T> void processAggregateRepositoryProducer( @Observes final ProcessProducer<T, Repository> processProducer) { logger.debug("Found producer for repository: {}.", processProducer.getProducer()); String repositoryName = CdiUtilities.extractBeanName(processProducer.getAnnotatedMember()); this.aggregateRepositoryProducerMap.put(repositoryName, processProducer.getProducer()); } <T> void processSnapshotTriggerDefinitionProducer( @Observes final ProcessProducer<T, SnapshotTriggerDefinition> processProducer) { logger.debug("Found producer for snapshot trigger definition: {}.", processProducer.getProducer()); String triggerDefinitionName = CdiUtilities.extractBeanName(processProducer.getAnnotatedMember()); this.snapshotTriggerDefinitionProducerMap.put(triggerDefinitionName, processProducer.getProducer()); } <T> void processCommandTargetResolverProducer( @Observes final ProcessProducer<T, CommandTargetResolver> processProducer) { logger.debug("Found producer for command target resolver: {}.", processProducer.getProducer()); String resolverName = CdiUtilities.extractBeanName(processProducer.getAnnotatedMember()); this.commandTargetResolverProducerMap.put(resolverName, processProducer.getProducer()); } <T> void processSaga(@Observes @WithAnnotations({Saga.class}) final ProcessAnnotatedType<T> processAnnotatedType) { // TODO Saga classes may need to be vetoed so that CDI does not // actually try to manage them. final Class<?> clazz = processAnnotatedType.getAnnotatedType().getJavaClass(); logger.debug("Found saga: {}.", clazz); sagas.add(new SagaDefinition(clazz)); } /** * Scans for an event storage engine producer. * * @param processProducer process producer event. */ // Mark: I know these seem especially frivolous and looks like they may be // replaced with post deployment look-ups or injection of some kind. // Unfortunately I don't think it is that straightforwards for reasons // explained a bit later. That said, I do want to discuss these with you. <T> void processEventStorageEngineProducer( @Observes final ProcessProducer<T, EventStorageEngine> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for event storage engine found: {}.", processProducer.getProducer()); this.eventStorageEngineProducer = processProducer.getProducer(); } <T> void processSagaConfigurationProducer( @Observes final ProcessProducer<T, SagaConfiguration<?>> processProducer) { logger.debug("Producer for saga configuration found: {}.", processProducer.getProducer()); String sagaConfigurationName = CdiUtilities.extractBeanName(processProducer.getAnnotatedMember()); sagaConfigurationProducerMap.put(sagaConfigurationName, processProducer.getProducer()); } /** * Scans for a configurer producer. * * @param processProducer process producer event. */ <T> void processConfigurerProducer( @Observes final ProcessProducer<T, Configurer> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for configurer found: {}.", processProducer.getProducer()); this.configurerProducer = processProducer.getProducer(); } /** * Scans for a transaction manager producer. * * @param processProducer process producer event. */ <T> void processTransactionManagerProducer( @Observes final ProcessProducer<T, TransactionManager> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for transaction manager found: {}.", processProducer.getProducer()); this.transactionManagerProducer = processProducer.getProducer(); } /** * Scans for a serializer producer. * * @param processProducer process producer event. */ <T> void processSerializerProducer( @Observes final ProcessProducer<T, Serializer> processProducer) { // TODO Handle multiple serializer definitions of the same type. AnnotatedMember<T> annotatedMember = processProducer.getAnnotatedMember(); Named named = annotatedMember.getAnnotation(Named.class); if (named != null) { String namedValue = named.value(); String serializerName = "".equals(namedValue) ? annotatedMember.getJavaMember().getName() : namedValue; switch (serializerName) { case "eventSerializer": logger.debug("Producer for event serializer found: {}.", processProducer.getProducer()); eventSerializerProducer = processProducer.getProducer(); break; case "messageSerializer": logger.debug("Producer for message serializer found: {}.", processProducer.getProducer()); messageSerializerProducer = processProducer.getProducer(); break; case "serializer": logger.debug("Producer for serializer found: {}.", processProducer.getProducer()); this.serializerProducer = processProducer.getProducer(); break; default: logger.warn("Unknown named serializer configured: " + serializerName); } } else { logger.debug("Producer for serializer found: {}.", processProducer.getProducer()); this.serializerProducer = processProducer.getProducer(); } } /** * Scans for an event bus producer. * * @param processProducer process producer event. */ <T> void processEventBusProducer( @Observes final ProcessProducer<T, EventBus> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for event bus found: {}.", processProducer.getProducer()); this.eventBusProducer = processProducer.getProducer(); } /** * Scans for a command bus producer. * * @param processProducer process producer event. */ <T> void processCommandBusProducer( @Observes final ProcessProducer<T, CommandBus> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for command bus found: {}.", processProducer.getProducer()); this.commandBusProducer = processProducer.getProducer(); } // <T> void processCommandGatewayProducer( // @Observes final ProcessProducer<T, CommandGateway> processProducer) { // // TODO Handle multiple producer definitions. // // logger.debug("Producer for command gateway found: {}.", // processProducer.getProducer()); // // this.commandGatewayProducer = processProducer.getProducer(); // } /** * Scans for an entity manager provider producer. * * @param processProducer process producer event. */ <T> void processEntityManagerProviderProducer( @Observes final ProcessProducer<T, EntityManagerProvider> processProducer) { // TODO Investigate if there is a way to look up the entity manager // from the environment. There likely isn't. // TODO Handle multiple producer definitions. logger.debug("Producer for entity manager provider found: {}.", processProducer.getProducer()); this.entityManagerProviderProducer = processProducer.getProducer(); } /** * Scans for a token store producer. * * @param processProducer process producer event. */ <T> void processTokenStoreProducer( @Observes final ProcessProducer<T, TokenStore> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for token store found: {}.", processProducer.getProducer()); this.tokenStoreProducer = processProducer.getProducer(); } <T> void processErrorHandlerProducer( @Observes final ProcessProducer<T, ErrorHandler> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for error handler found: {}.", processProducer.getProducer()); this.errorHandlerProducer = processProducer.getProducer(); } <T> void processListenerInvocationErrorHandlerProducer( @Observes final ProcessProducer<T, ListenerInvocationErrorHandler> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for listener invocation error handler found: {}.", processProducer.getProducer()); this.listenerInvocationErrorHandlerProducer = processProducer.getProducer(); } <T> void processCorrelationDataProviderProducer( @Observes final ProcessProducer<T, CorrelationDataProvider> processProducer) { logger.debug("Producer for correlation data provider found: {}.", processProducer.getProducer()); this.correlationDataProviderProducers.add(processProducer.getProducer()); } <T> void processQueryBusProducer( @Observes final ProcessProducer<T, QueryBus> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for query bus found: {}.", processProducer.getProducer()); this.queryBusProducer = processProducer.getProducer(); } <T> void processQueryGatewayProducer( @Observes final ProcessProducer<T, QueryGateway> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for query gateway found: {}.", processProducer.getProducer()); this.queryGatewayProducer = processProducer.getProducer(); } <T> void processQueryUpdateEmitterProducer( @Observes final ProcessProducer<T, QueryUpdateEmitter> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for query update emitter found: {}.", processProducer.getProducer()); this.queryUpdateEmitterProducer = processProducer.getProducer(); } <T> void processDeadlineManagerProducer( @Observes final ProcessProducer<T, DeadlineManager> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for deadline manager found: {}.", processProducer.getProducer()); this.deadlineManagerProducer = processProducer.getProducer(); } <T> void processModuleConfigurationProducer( @Observes final ProcessProducer<T, ModuleConfiguration> processProducer) { logger.debug("Producer for module configuration found: {}.", processProducer.getProducer()); this.moduleConfigurationProducers.add(processProducer.getProducer()); } <T> void processEventHandlingConfigurationProducer( @Observes final ProcessProducer<T, EventHandlingConfiguration> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for event handling configuration found: {}.", processProducer.getProducer()); this.eventHandlingConfigurationProducer = processProducer.getProducer(); } <T> void processEventProcessingConfigurationProducer( @Observes final ProcessProducer<T, EventProcessingConfiguration> processProducer) { // TODO Handle multiple producer definitions. logger.debug("Producer for event processing configuration found: {}.", processProducer.getProducer()); this.eventProcessingConfigurationProducer = processProducer.getProducer(); } <T> void processConfigurerModuleProducer( @Observes final ProcessProducer<T, ConfigurerModule> processProducer) { logger.debug("Producer for configurer module found: {}.", processProducer.getProducer()); this.configurerModuleProducers.add(processProducer.getProducer()); } <T> void processSagaStoreProducer( @Observes final ProcessProducer<T, SagaStore> processProducer) { logger.debug("Producer for saga store found: {}.", processProducer.getProducer()); String sagaStoreName = CdiUtilities.extractBeanName(processProducer.getAnnotatedMember()); this.sagaStoreProducerMap.put(sagaStoreName, processProducer.getProducer()); } <T> void processEventUpcasterProducer( @Observes final ProcessProducer<T, EventUpcaster> processProducer) { logger.debug("Producer for event upcaster found: {}.", processProducer.getProducer()); this.eventUpcasterProducers.add(processProducer.getProducer()); } /** * Scans all beans and collects beans with message handlers. * * @param processBean bean processing event. */ // Mark: This one is especally tricky. I am looking for the existance // of annotations and methods and collecting bean definitions. // I suspect this is the most efficient way to do this and I can use // the bean defenitions to look up via BeanManager later. //X @struberg it's fine. Although I would use processBean.getAnnotated() instead of bean.getBeanClass() in the 'inspect' code //X I'fe quickly fixed this method. Please verify if it works as expected. And you still need to change the tests to AnnotatedType! <T> void processBean(@Observes final ProcessBean<T> processBean) { MessageHandlingBeanDefinition.inspect(processBean.getBean(), processBean.getAnnotated()) .ifPresent(bean -> { logger.debug("Found {}.", bean); messageHandlers.add(bean); }); } /** * Registration of Axon components in CDI registry. */ void afterBeanDiscovery(@Observes final AfterBeanDiscovery afterBeanDiscovery, final BeanManager beanManager) { logger.info("Starting Axon Framework configuration."); Configurer configurer; if (this.configurerProducer != null) { // Mark: this is one of the biggest headaches I am facing. What I // need is the actual bean instance that I need to move forward with. // Right now what I am doing is just having the producer create it. // The problem with this is that CDI and the Java EE runtime is just // not fullly ready to do that at this stage. //X @struberg: don't understand why exactly. Miss the context. //X I fear I'd need a way better understanding of Axon // In addition, the // bean may be referencing definitions I have not had a chance // to register with CDI yet since they can only be produced after the // Axon configuration is done. // // The only solution I can think of is register any bean // definitions I've detected I absolutely need and avoid actual // bean reference until later, create the beans like configurer // that I say in the API cannot have circular dependencies and move the // configuration steps itself to be as lazily loaded as possible. // Can you think of a better strategy to deal with this problem? configurer = produce(beanManager, configurerProducer); logger.info("Starting with an application provided configurer: {}.", configurer.getClass().getSimpleName()); } else { logger.info("Starting with the Axon default configuration."); configurer = DefaultConfigurer.defaultConfiguration(); } if (this.entityManagerProviderProducer != null) { final EntityManagerProvider entityManagerProvider = produce(beanManager, entityManagerProviderProducer); logger.info("Registering entity manager provider: {}.", entityManagerProvider.getClass().getSimpleName()); configurer.registerComponent(EntityManagerProvider.class, c -> entityManagerProvider); } if (this.serializerProducer != null) { final Serializer serializer = produce(beanManager, serializerProducer); logger.info("Registering serializer: {}.", serializer.getClass().getSimpleName()); configurer.configureSerializer(c -> serializer); } if (this.eventSerializerProducer != null) { final Serializer serializer = produce(beanManager, eventSerializerProducer); logger.info("Registering event serializer: {}.", serializer.getClass().getSimpleName()); configurer.configureEventSerializer(c -> serializer); } if (this.messageSerializerProducer != null) { final Serializer serializer = produce(beanManager, messageSerializerProducer); logger.info("Registering message serializer: {}.", serializer.getClass().getSimpleName()); configurer.configureMessageSerializer(c -> serializer); } if (this.transactionManagerProducer != null) { final TransactionManager transactionManager = produce(beanManager, transactionManagerProducer); logger.info("Registering transaction manager: {}.", transactionManager.getClass().getSimpleName()); configurer.configureTransactionManager(c -> transactionManager); } if (this.commandBusProducer != null) { final CommandBus commandBus = produce(beanManager, commandBusProducer); logger.info("Registering command bus: {}.", commandBus.getClass().getSimpleName()); configurer.configureCommandBus(c -> commandBus); } // Module configurations registration. // TODO: 8/30/2018 : // There is a possible issue with following construct: // @Produce // public ModuleConfiguration eventHandlingConfiguration() { // return new EventHandlingConfiguration(); // } // It will be registered as module configuration only and not as EventHandlingConfiguration! // This will result in having double EventHandlingConfiguration within Configuration. // The same applies to EventProcessingConfiguration. // Milan: I need to understand this a bit more from you, but I think the solution // is to simply mandate the presence of @Named for EventHandlingConfiguration // and EventProcessingConfiguration that is produced as type ModuleConfiguration // but is not really a module configuration. for (Producer<ModuleConfiguration> producer : moduleConfigurationProducers) { // Milan: This looks very interesting. I would like to understand why // we are doing this and whether we should do this more globally, // perhaps moving the entire Axon configuration to AfterDeploymentValidation // instead of AfterBeanDiscovery. // Antoine: Do you have any thoughts on this? Could this approach // help solve any potential initialization issues? What's the easiest // way to do this if so? configurer.registerModule(new LazyRetrievedModuleConfiguration(() -> { ModuleConfiguration moduleConfiguration = producer.produce(beanManager.createCreationalContext(null)); logger.info("Registering module configuration: {}.", moduleConfiguration.getClass().getSimpleName()); return moduleConfiguration; })); } EventHandlingConfiguration eventHandlingConfiguration; if (eventHandlingConfigurationProducer != null) { eventHandlingConfiguration = produce(beanManager, eventHandlingConfigurationProducer); } else { eventHandlingConfiguration = new EventHandlingConfiguration(); } logger.info("Registering event handling configuration: {}.", eventHandlingConfiguration.getClass().getSimpleName()); configurer.registerModule(eventHandlingConfiguration); if (eventProcessingConfigurationProducer != null) { EventProcessingConfiguration eventProcessingConfiguration = produce(beanManager, eventProcessingConfigurationProducer); logger.info("Registering event processing configuration: {}.", eventProcessingConfiguration.getClass().getSimpleName()); configurer.registerModule(eventProcessingConfiguration); } configurerModuleProducers.forEach(producer -> { ConfigurerModule configurerModule = produce(beanManager, producer); logger.info("Configuring module: {}.", configurerModule.getClass().getSimpleName()); configurerModule.configureModule(configurer); }); if (this.eventBusProducer != null) { final EventBus eventBus = produce(beanManager, eventBusProducer); logger.info("Registering event bus: {}.", eventBus.getClass().getSimpleName()); configurer.configureEventBus(c -> eventBus); } if (this.tokenStoreProducer != null) { final TokenStore tokenStore = produce(beanManager, tokenStoreProducer); logger.info("Registering token store: {}.", tokenStore.getClass().getSimpleName()); configurer.registerComponent(TokenStore.class, c -> tokenStore); } if (this.listenerInvocationErrorHandlerProducer != null) { ListenerInvocationErrorHandler listenerInvocationErrorHandler = produce(beanManager, listenerInvocationErrorHandlerProducer); logger.info("Registering listener invocation error handler: {}.", listenerInvocationErrorHandler.getClass().getSimpleName()); configurer.registerComponent(ListenerInvocationErrorHandler.class, c -> listenerInvocationErrorHandler); } if (this.errorHandlerProducer != null) { ErrorHandler errorHandler = produce(beanManager, errorHandlerProducer); logger.info("Registering error handler: {}.", errorHandler.getClass().getSimpleName()); configurer.registerComponent(ErrorHandler.class, c -> errorHandler); } // Correlation data providers registration List<CorrelationDataProvider> correlationDataProviders = this.correlationDataProviderProducers .stream() .map(producer -> { CorrelationDataProvider correlationDataProvider = produce(beanManager, producer); logger.info("Registering correlation data provider: {}.", correlationDataProvider.getClass().getSimpleName()); return correlationDataProvider; }) .collect(Collectors.toList()); configurer.configureCorrelationDataProviders(c -> correlationDataProviders); this.eventUpcasterProducers .forEach(producer -> { EventUpcaster eventUpcaster = produce(beanManager, producer); logger.info("Registering event upcaster: {}.", eventUpcaster.getClass().getSimpleName()); configurer.registerEventUpcaster(c -> eventUpcaster); }); if (this.queryBusProducer != null) { QueryBus queryBus = produce(beanManager, queryBusProducer); logger.info("Registering query bus: {}.", queryBus.getClass().getSimpleName()); configurer.configureQueryBus(c -> queryBus); } if (this.queryGatewayProducer != null) { QueryGateway queryGateway = produce(beanManager, queryGatewayProducer); logger.info("Registering query gateway: {}.", queryGateway.getClass().getSimpleName()); configurer.registerComponent(QueryGateway.class, c -> queryGateway); } if (this.queryUpdateEmitterProducer != null) { QueryUpdateEmitter queryUpdateEmitter = produce(beanManager, queryUpdateEmitterProducer); logger.info("Registering query update emitter: {}.", queryUpdateEmitter.getClass().getSimpleName()); configurer.configureQueryUpdateEmitter(c -> queryUpdateEmitter); } if (this.deadlineManagerProducer != null) { DeadlineManager deadlineManager = produce(beanManager, deadlineManagerProducer); logger.info("Registering deadline manager: {}.", deadlineManager.getClass().getSimpleName()); configurer.registerComponent(DeadlineManager.class, c -> deadlineManager); } if (this.eventStorageEngineProducer != null) { final EventStorageEngine eventStorageEngine = produce(beanManager, eventStorageEngineProducer); logger.info("Registering event storage: {}.", eventStorageEngine.getClass().getSimpleName()); configurer.configureEmbeddedEventStore(c -> eventStorageEngine); } // Now need to begin registering application components rather than // configuration components. registerAggregates(beanManager, configurer); registerSagaStore(beanManager, configurer); registerSagas(beanManager, afterBeanDiscovery, configurer); registerMessageHandlers(beanManager, configurer, eventHandlingConfiguration); logger.info("Axon Framework configuration complete."); logger.info("Registering Axon APIs with CDI."); // Mark: This is one of the key parts that is forcing me to do anything at all // in the afterBeanDiscovery phase. Once the Axon configuration completes, // including taking into account user defined overrides, I need to bind some // bean definitions with CDI. Aside from these relatively static definitions, // there are and will be loops where I need to register things that look like // Repository<AggregateType1>...Repository<AggregateTypeN>. I think this means // I absolutely need to make sure of and thus finish my Axon configuration somehow very // close to afterBeanDiscovery, in a programmatic fashion. //X @struberg: do you know that you can send CDI events between CDI Extensions already during bootstrap? //X That might help here. //X So you effectively can send out a ConfigurationEvent and anybody could write an own CDI Extension where //X this gets observed. That way you can effectively have configuration and inter-CDI communication already before //X the CDI container is booted. //X I think I need a better explanation what problem you want to solve by it though. // // That said, you'll notice the lambdas below that are my effort to defer // actual instantiation to as late as possible. The only other way I can // think of to defer actual bean referencing any further is by using // byte code proxies that look up bean references on each method call. // Can you think of a better way to further defer the actual configution start and // bean referencing? // // Even with this deferring, I am running into issues like EJBs not // instantiated and JPA registries not done yet on certain containers like // Payara. When I refer to application scoped beans at this stage, it seems I am gettting // my own copy and then the application creates another copy when the // application code actually bootstraps. All related to referncing beans too // early I believe. afterBeanDiscovery.addBean( new BeanWrapper<>(Configuration.class, () -> startConfiguration(configurer))); // Mark: I tried removing some of this code in favor of globally enabled alternatives. // These are in AxonDefaultConfiguration. Howver, I immidiately ran into // circular reference issues. I do not think alternatives are really // a viable solution for this use case and I need to keep sticking to something // similar to this unless you know of a way. addIfNotConfigured(CommandBus.class, commandBusProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).commandBus(), afterBeanDiscovery); addIfNotConfigured(QueryBus.class, queryBusProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).queryBus(), afterBeanDiscovery); addIfNotConfigured(QueryGateway.class, queryGatewayProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).queryGateway(), afterBeanDiscovery); addIfNotConfigured(QueryUpdateEmitter.class, queryUpdateEmitterProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).queryUpdateEmitter(), afterBeanDiscovery); addIfNotConfigured(EventBus.class, eventBusProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).eventBus(), afterBeanDiscovery); addIfNotConfigured(Serializer.class, serializerProducer, () -> CdiUtilities.getReference(beanManager, Configuration.class).serializer(), afterBeanDiscovery); } void afterDeploymentValidation( @Observes final AfterDeploymentValidation afterDeploymentValidation, final BeanManager beanManager) { // Ensure the configuration is started. CdiUtilities.getReference(beanManager, Configuration.class).commandBus(); } void init(@Observes @Initialized(ApplicationScoped.class) Object initialized) { } void destroy(@Observes @Destroyed(ApplicationScoped.class) final Object destroyed) { } void beforeShutdown(@Observes BeforeShutdown event, BeanManager beanManager) { } private Configuration startConfiguration(Configurer configurer) { logger.info("Starting Axon configuration."); return configurer.start(); } private void registerMessageHandlers(BeanManager beanManager, Configurer configurer, EventHandlingConfiguration eventHandlingConfiguration) { for (MessageHandlingBeanDefinition messageHandler : messageHandlers) { Component<Object> component = new Component<>(() -> null, "messageHandler", c -> messageHandler.getBean().create(beanManager.createCreationalContext(null))); if (messageHandler.isEventHandler()) { logger.info("Registering event handler: {}.", messageHandler.getBean().getBeanClass().getSimpleName()); eventHandlingConfiguration.registerEventHandler(c -> component.get()); } if (messageHandler.isCommandHandler()) { logger.info("Registering command handler: {}.", messageHandler.getBean().getBeanClass().getSimpleName()); configurer.registerCommandHandler(c -> component.get()); } if (messageHandler.isQueryHandler()) { logger.info("Registering query handler: {}.", messageHandler.getBean().getBeanClass().getSimpleName()); configurer.registerQueryHandler(c -> component.get()); } } } @SuppressWarnings("unchecked") private void registerAggregates(BeanManager beanManager, Configurer configurer) { aggregates.forEach(aggregateDefinition -> { logger.info("Registering aggregate: {}.", aggregateDefinition.aggregateType().getSimpleName()); AggregateConfigurer<?> aggregateConfigurer = AggregateConfigurer.defaultConfiguration(aggregateDefinition.aggregateType()); if (aggregateDefinition.repository().isPresent()) { aggregateConfigurer.configureRepository( c -> produce(beanManager, aggregateRepositoryProducerMap .get(aggregateDefinition.repository().get()))); } else { if (aggregateRepositoryProducerMap.containsKey(aggregateDefinition.repositoryName())) { aggregateConfigurer.configureRepository( c -> produce(beanManager, aggregateRepositoryProducerMap .get(aggregateDefinition.repositoryName()))); } else { // TODO: 8/29/2018 check how to do in CDI world: register repository as a bean // TODO: 8/29/2018 check how to do in CDI world: aggregate factory aggregateDefinition.snapshotTriggerDefinition().ifPresent(triggerDefinition -> aggregateConfigurer .configureSnapshotTrigger( c -> produce(beanManager, snapshotTriggerDefinitionProducerMap .get(triggerDefinition)))); if (aggregateDefinition.isJpaAggregate()) { aggregateConfigurer.configureRepository( c -> new GenericJpaRepository( // TODO: 8/29/2018 what to do about default EntityManagerProvider (check spring impl) c.getComponent(EntityManagerProvider.class), aggregateDefinition.aggregateType(), c.eventBus(), c::repository, c.getComponent(LockFactory.class, () -> NullLockFactory.INSTANCE), c.parameterResolverFactory(), c.handlerDefinition(aggregateDefinition.aggregateType()))); } } } if (aggregateDefinition.commandTargetResolver().isPresent()) { aggregateConfigurer.configureCommandTargetResolver( c -> produce(beanManager, commandTargetResolverProducerMap.get(aggregateDefinition.commandTargetResolver().get()))); } else { commandTargetResolverProducerMap.keySet() .stream() .filter(resolver -> aggregates.stream() .filter(a -> a.commandTargetResolver().isPresent()) .map(a -> a.commandTargetResolver().get()) .noneMatch(resolver::equals)) .findFirst() // TODO: 8/29/2018 what if there are more "default" resolvers .ifPresent(resolver -> aggregateConfigurer.configureCommandTargetResolver( c -> produce(beanManager, commandTargetResolverProducerMap.get(resolver)))); } configurer.configureAggregate(aggregateConfigurer); }); } private void registerSagaStore(BeanManager beanManager, Configurer configurer) { sagaStoreProducerMap.keySet() .stream() .filter(storeName -> sagas.stream() .filter(sd -> sd.sagaStore().isPresent()) .map(sd -> sd.sagaStore().get()) .noneMatch(storeName::equals)) .findFirst() // TODO: 8/29/2018 what if there are more "default" saga stores??? .ifPresent(storeName -> { SagaStore sagaStore = produce(beanManager, sagaStoreProducerMap.get(storeName)); logger.info("Registering saga store {}.", sagaStore.getClass().getSimpleName()); configurer.registerComponent(SagaStore.class, c -> sagaStore); }); } @SuppressWarnings("unchecked") private void registerSagas(BeanManager beanManager, AfterBeanDiscovery afterBeanDiscovery, Configurer configurer) { sagas.forEach(sagaDefinition -> { logger.info("Registering saga {}.", sagaDefinition.sagaType().getSimpleName()); if (!sagaDefinition.explicitConfiguration() && !sagaConfigurationProducerMap.containsKey(sagaDefinition.configurationName())) { SagaConfiguration<?> sagaConfiguration = SagaConfiguration .subscribingSagaManager(sagaDefinition.sagaType()); afterBeanDiscovery.addBean(new BeanWrapper<>(sagaDefinition.configurationName(), SagaConfiguration.class, () -> sagaConfiguration)); sagaDefinition.sagaStore() .ifPresent(sagaStore -> sagaConfiguration .configureSagaStore(c -> produce(beanManager, sagaStoreProducerMap.get(sagaStore)))); configurer.registerModule(sagaConfiguration); } }); } private <T> void addIfNotConfigured(Class<T> componentType, Producer<T> componentProducer, Supplier<T> componentSupplier, AfterBeanDiscovery afterBeanDiscovery) { if (componentProducer == null) { afterBeanDiscovery.addBean(new BeanWrapper<>(componentType, componentSupplier)); } } private <T> T produce(BeanManager beanManager, Producer<T> producer) { return producer.produce(beanManager.createCreationalContext(null)); } }