diff --git a/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationConfigurationBeanDefinitionParser.java b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationConfigurationBeanDefinitionParser.java index a898c1080c..d4a4830e33 100644 --- a/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationConfigurationBeanDefinitionParser.java +++ b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationConfigurationBeanDefinitionParser.java @@ -20,9 +20,11 @@ import org.axonframework.eventhandling.annotation.AnnotationEventListenerBeanPostProcessor; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; import org.w3c.dom.Element; import static org.axonframework.contextsupport.spring.SpringContextParameterResolverFactoryBuilder.getBeanReference; @@ -65,8 +67,16 @@ public class AnnotationConfigurationBeanDefinitionParser extends AbstractBeanDef */ @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - registerAnnotationCommandHandlerBeanPostProcessor(element, parserContext); - registerAnnotationEventListenerBeanPostProcessor(element, parserContext); + String phase = element.hasAttribute(PHASE_ATTRIBUTE) ? element.getAttribute(PHASE_ATTRIBUTE) : null; + String unsubscribe = element.hasAttribute(UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE) ? element.getAttribute( + UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE) : null; + String eventBus = element.hasAttribute(EVENT_BUS_ATTRIBUTE) ? element.getAttribute(EVENT_BUS_ATTRIBUTE) : null; + String commandBus = element.hasAttribute(COMMAND_BUS_ATTRIBUTE) ? element + .getAttribute(COMMAND_BUS_ATTRIBUTE) : null; + registerAnnotationCommandHandlerBeanPostProcessor(commandBus, phase, + unsubscribe, parserContext.getRegistry()); + registerAnnotationEventListenerBeanPostProcessor(eventBus, phase, unsubscribe, + parserContext.getRegistry()); return null; } @@ -74,56 +84,61 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa * Create the {@link org.springframework.beans.factory.config.BeanDefinition} for the {@link * AnnotationEventListenerBeanPostProcessor} and register it. * - * @param element The {@link Element} being parsed. - * @param parserContext The running {@link ParserContext}. + * @param eventBus The bean name of the event bus to subscribe to + * @param phase The lifecycle phase for the post processor + * @param unsubscribeOnShutdown Whether to unsubscribe beans on shutdown + * @param registry The registry containing bean definitions */ - private void registerAnnotationEventListenerBeanPostProcessor(Element element, ParserContext parserContext) { + public void registerAnnotationEventListenerBeanPostProcessor(String eventBus, String phase, + String unsubscribeOnShutdown, + BeanDefinitionRegistry registry) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AnnotationEventListenerBeanPostProcessor.class); beanDefinition.getPropertyValues().add("parameterResolverFactory", - getBeanReference(parserContext.getRegistry())); - if (element.hasAttribute(PHASE_ATTRIBUTE)) { - beanDefinition.getPropertyValues().add("phase", element.getAttribute(PHASE_ATTRIBUTE)); + getBeanReference(registry)); + if (StringUtils.hasText(phase)) { + beanDefinition.getPropertyValues().add("phase", phase); } - if (element.hasAttribute(UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE)) { - beanDefinition.getPropertyValues().add("unsubscribeOnShutdown", - element.getAttribute(UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE)); + if (StringUtils.hasText(unsubscribeOnShutdown)) { + beanDefinition.getPropertyValues().add("unsubscribeOnShutdown", unsubscribeOnShutdown); } - if (element.hasAttribute(EVENT_BUS_ATTRIBUTE)) { - String eventBusReference = element.getAttribute(EVENT_BUS_ATTRIBUTE); - RuntimeBeanReference beanReference = new RuntimeBeanReference(eventBusReference); + if (StringUtils.hasText(eventBus)) { + RuntimeBeanReference beanReference = new RuntimeBeanReference(eventBus); beanDefinition.getPropertyValues().addPropertyValue("eventBus", beanReference); } - parserContext.getRegistry().registerBeanDefinition(EVENT_LISTENER_BEAN_NAME, beanDefinition); + registry.registerBeanDefinition(EVENT_LISTENER_BEAN_NAME, beanDefinition); } /** * Create the {@link org.springframework.beans.factory.config.BeanDefinition} for the {@link * AnnotationCommandHandlerBeanPostProcessor} and register it. * - * @param element The {@link Element} being parsed. - * @param parserContext The running {@link ParserContext}. + * @param commandBus The bean name of the command bus to subscribe to + * @param phase The lifecycle phase for the post processor + * @param unsubscribeOnShutdown Whether to unsubscribe beans on shutdown + * @param registry The registry containing bean definitions */ - private void registerAnnotationCommandHandlerBeanPostProcessor(Element element, ParserContext parserContext) { + public void registerAnnotationCommandHandlerBeanPostProcessor(String commandBus, String phase, + String unsubscribeOnShutdown, + BeanDefinitionRegistry registry) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AnnotationCommandHandlerBeanPostProcessor.class); beanDefinition.getPropertyValues().add("parameterResolverFactory", getBeanReference( - parserContext.getRegistry())); - if (element.hasAttribute(PHASE_ATTRIBUTE)) { - beanDefinition.getPropertyValues().add("phase", element.getAttribute(PHASE_ATTRIBUTE)); + registry)); + if (StringUtils.hasText(phase)) { + beanDefinition.getPropertyValues().add("phase", phase); } - if (element.hasAttribute(UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE)) { + if (StringUtils.hasText(unsubscribeOnShutdown)) { beanDefinition.getPropertyValues().add("unsubscribeOnShutdown", - element.getAttribute(UNSUBSCRIBE_ON_SHUTDOWN_ATTRIBUTE)); + unsubscribeOnShutdown); } - if (element.hasAttribute(COMMAND_BUS_ATTRIBUTE)) { - String commandBusReference = element.getAttribute(COMMAND_BUS_ATTRIBUTE); - RuntimeBeanReference beanReference = new RuntimeBeanReference(commandBusReference); + if (StringUtils.hasText(commandBus)) { + RuntimeBeanReference beanReference = new RuntimeBeanReference(commandBus); beanDefinition.getPropertyValues().addPropertyValue("commandBus", beanReference); } - parserContext.getRegistry().registerBeanDefinition(COMMAND_HANDLER_BEAN_NAME, beanDefinition); + registry.registerBeanDefinition(COMMAND_HANDLER_BEAN_NAME, beanDefinition); } } diff --git a/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDriven.java b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDriven.java new file mode 100644 index 0000000000..85495486ea --- /dev/null +++ b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDriven.java @@ -0,0 +1,68 @@ +/* + * 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 org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for {@link org.springframework.context.annotation.Configuration @Configuration} that will automatically + * subscribe {@link org.axonframework.commandhandling.annotation.CommandHandler @CommandHandler} and {@link + * org.axonframework.eventhandling.annotation.EventHandler @EventHandler} annotated beans with the CommandBus and + * EventBus, respectively. + *

+ * If a context contains multiple EventBus or CommandBus implementations, you must indicate the isntance to use as + * a property on this annotation. + * + * @author Allard Buijze + * @since 2.3 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(AnnotationDrivenConfiguration.class) +public @interface AnnotationDriven { + + /** + * The bean name of the Event Bus to register {@link + * org.axonframework.eventhandling.annotation.EventHandler @EventHandler} annotated beans with. + */ + String eventBus() default ""; + + /** + * The bean name of the Command Bus to register {@link + * org.axonframework.commandhandling.annotation.CommandHandler @CommandHandler} annotated beans with. + */ + String commandBus() default ""; + + /** + * Whether to unsubscribe beans on shutdown. Default to false. Setting this to true will + * explicitly unsubscribe beans from the Event- and CommandBus when shutting down the application context. + */ + boolean unsubscribeOnShutdown() default false; + + /** + * The phase in the application context lifecycle in which to subscribe (and possibly unsubscribe) the handlers + * with the CommandBus and EventBus. + */ + int phase() default 0; +} diff --git a/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfiguration.java b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfiguration.java new file mode 100644 index 0000000000..dd83cbcd91 --- /dev/null +++ b/core/src/main/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfiguration.java @@ -0,0 +1,67 @@ +/* + * 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 org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.Map; + +/** + * Spring @Configuration related class that adds Axon Annotation PostProcessors to the BeanDefinitionRegistry. + * + * @author Allard Buijze + * @see org.axonframework.contextsupport.spring.AnnotationDriven + * @since 2.3 + */ +public class AnnotationDrivenConfiguration implements ImportBeanDefinitionRegistrar { + + private static final String ANNOTATION_TYPE = AnnotationDriven.class.getName(); + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + final AnnotationConfigurationBeanDefinitionParser parser = new AnnotationConfigurationBeanDefinitionParser(); + Map attributes = importingClassMetadata.getAnnotationAttributes(ANNOTATION_TYPE); + parser.registerAnnotationCommandHandlerBeanPostProcessor(getCommandBus(attributes), getPhase(attributes), + getUnsubscribeOnShutdown(attributes), + registry); + parser.registerAnnotationEventListenerBeanPostProcessor(getEventBus(attributes), getPhase(attributes), + getUnsubscribeOnShutdown(attributes), + registry); + } + + private String getEventBus(Map attributes) { + final Object eventBus = attributes.get("eventBus"); + return eventBus == null ? null : eventBus.toString(); + } + + private String getCommandBus(Map attributes) { + final Object commandBus = attributes.get("commandBus"); + return commandBus == null ? null : commandBus.toString(); + } + + private String getUnsubscribeOnShutdown(Map attributes) { + final Boolean unsubscribeOnShutdown = (Boolean) attributes.get("unsubscribeOnShutdown"); + return unsubscribeOnShutdown == null ? null : Boolean.toString(unsubscribeOnShutdown); + } + + private String getPhase(Map attributes) { + final Integer phase = (Integer) attributes.get("phase"); + return phase == null ? null : Integer.toString(phase); + } +} diff --git a/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_CustomValues.java b/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_CustomValues.java new file mode 100644 index 0000000000..c74f5afc0f --- /dev/null +++ b/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_CustomValues.java @@ -0,0 +1,148 @@ +/* + * 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 org.axonframework.commandhandling.CommandBus; +import org.axonframework.commandhandling.annotation.AnnotationCommandHandlerAdapter; +import org.axonframework.commandhandling.annotation.CommandHandler; +import org.axonframework.eventhandling.EventBus; +import org.axonframework.eventhandling.EventListener; +import org.axonframework.eventhandling.annotation.EventHandler; +import org.junit.*; +import org.junit.runner.*; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Allard Buijze + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AnnotationDrivenConfigurationTest_CustomValues.Context.class) +public class AnnotationDrivenConfigurationTest_CustomValues { + + @Autowired + private ApplicationContext applicationContext; + + @Qualifier("commandBus") + @Autowired + private CommandBus commandBus; + + @Qualifier("eventBus") + @Autowired + private EventBus eventBus; + + @Autowired + private DefaultListableBeanFactory beanFactory; + + @Test + public void testAnnotationConfigurationAnnotationWrapsBeans() throws Exception { + Object eventHandler = applicationContext.getBean("eventHandler"); + Object commandHandler = applicationContext.getBean("commandHandler"); + + + assertTrue(eventHandler instanceof EventListener); + assertTrue(commandHandler instanceof org.axonframework.commandhandling.CommandHandler); + + verify(commandBus).subscribe(String.class.getName(), + (org.axonframework.commandhandling.CommandHandler) commandHandler); + verify(eventBus).subscribe((EventListener) eventHandler); + } + + @Test + public void testEventListenerPostProcessorBeanDefinitionContainCustomValues() { + final MutablePropertyValues propertyValues = beanFactory.getBeanDefinition( + "__axon-annotation-event-listener-bean-post-processor") + .getPropertyValues(); + assertEquals("-1000", propertyValues.get("phase")); + assertEquals("true", propertyValues.get("unsubscribeOnShutdown")); + } + + @Test + public void testCommandHandlerPostProcessorBeanDefinitionContainCustomValues() { + final MutablePropertyValues propertyValues = beanFactory.getBeanDefinition( + "__axon-annotation-command-handler-bean-post-processor") + .getPropertyValues(); + assertEquals("-1000", propertyValues.get("phase")); + assertEquals("true", propertyValues.get("unsubscribeOnShutdown")); + } + + @AnnotationDriven(eventBus = "eventBus", commandBus = "commandBus", unsubscribeOnShutdown = true, phase = -1000) + @Configuration + public static class Context { + + @Bean + public AnnotatedEventHandler eventHandler() { + return new AnnotatedEventHandler(); + } + + @Bean + public AnnotatedCommandHandler commandHandler() { + return new AnnotatedCommandHandler(); + } + + @Bean + public CommandBus commandBus() { + return mock(CommandBus.class); + } + + @Bean + public CommandBus alternateCommandBus() { + final CommandBus commandBus = mock(CommandBus.class); + doThrow(new AssertionError("Should not use this command bus")).when(commandBus).subscribe(anyString(), + any(AnnotationCommandHandlerAdapter.class)); + return commandBus; + } + + @Bean + public EventBus eventBus() { + return mock(EventBus.class); + } + + @Bean + public EventBus alternativeEventBus() { + final EventBus mock = mock(EventBus.class); + doThrow(new AssertionError("Should not interact with this event bus")) + .when(mock) + .subscribe(any(EventListener.class)); + return mock; + } + } + + public static class AnnotatedEventHandler { + + @EventHandler + public void on(String someEvent) { + } + } + + public static class AnnotatedCommandHandler { + + @CommandHandler + public void on(String someEvent) { + } + } +} diff --git a/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_DefaultValues.java b/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_DefaultValues.java new file mode 100644 index 0000000000..74e50b12dc --- /dev/null +++ b/core/src/test/java/org/axonframework/contextsupport/spring/AnnotationDrivenConfigurationTest_DefaultValues.java @@ -0,0 +1,96 @@ +/* + * 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 org.axonframework.commandhandling.CommandBus; +import org.axonframework.commandhandling.SimpleCommandBus; +import org.axonframework.commandhandling.annotation.CommandHandler; +import org.axonframework.eventhandling.EventBus; +import org.axonframework.eventhandling.EventListener; +import org.axonframework.eventhandling.SimpleEventBus; +import org.axonframework.eventhandling.annotation.EventHandler; +import org.junit.*; +import org.junit.runner.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * @author Allard Buijze + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AnnotationDrivenConfigurationTest_DefaultValues.Context.class) +public class AnnotationDrivenConfigurationTest_DefaultValues { + + @Autowired + private ApplicationContext applicationContext; + + + @Test + public void testAnnotationConfigurationAnnotationWrapsBeans() throws Exception { + Object eventHandler = applicationContext.getBean("eventHandler"); + Object commandHandler = applicationContext.getBean("commandHandler"); + + + assertTrue(eventHandler instanceof EventListener); + assertTrue(commandHandler instanceof org.axonframework.commandhandling.CommandHandler); + } + + @AnnotationDriven() + @Configuration + public static class Context { + + @Bean + public AnnotatedEventHandler eventHandler() { + return new AnnotatedEventHandler(); + } + + @Bean + public AnnotatedCommandHandler commandHandler() { + return new AnnotatedCommandHandler(); + } + + @Bean + public CommandBus commandBus() { + return new SimpleCommandBus(); + } + + @Bean + public EventBus eventBus() { + return new SimpleEventBus(); + } + } + + public static class AnnotatedEventHandler { + + @EventHandler + public void on(String someEvent) { + } + } + + public static class AnnotatedCommandHandler { + + @CommandHandler + public void on(String someEvent) { + } + } +} diff --git a/core/src/test/resources/contexts/axon-namespace-support-context.xml b/core/src/test/resources/contexts/axon-namespace-support-context.xml index e91aeb1b19..a0f7437d31 100644 --- a/core/src/test/resources/contexts/axon-namespace-support-context.xml +++ b/core/src/test/resources/contexts/axon-namespace-support-context.xml @@ -1,6 +1,6 @@ - - + - + diff --git a/pom.xml b/pom.xml index ebb60471fa..e70b6e24b7 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + org.axonframework axon @@ -111,7 +112,7 @@ 1.7.5 1.2.17 - 3.2.6.RELEASE + 4.0.3.RELEASE 3.2.3 2.4.0 4.2.1