diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java index 89eab1de..aa94175e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java @@ -53,4 +53,12 @@ public @interface EnableStateMachine { */ String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE}; + /** + * Defines if application context events for a state machine are + * enable or not. On default events are enabled. + * + * @return true, if events are enabled. + */ + boolean contextEvents() default true; + } \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java index 6ebe5b94..446df3bb 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java @@ -41,7 +41,7 @@ import org.springframework.statemachine.config.configuration.StateMachineFactory @EnableAnnotationConfiguration @Import({StateMachineFactoryConfiguration.class,ObjectPostProcessorConfiguration.class}) public @interface EnableStateMachineFactory { - + /** * The name of bean, or if plural, aliases for bean created based on this * annotation. If left unspecified bean name will be autogenerated. @@ -50,5 +50,13 @@ public @interface EnableStateMachineFactory { * @return the array if names or empty as default */ String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY}; - + + /** + * Defines if application context events for a state machine are + * enable or not. On default events are enabled. + * + * @return true, if events are enabled. + */ + boolean contextEvents() default true; + } \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index 79497664..30ec2678 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -66,6 +66,8 @@ public class EnumStateMachineFactory, E extends Enum> exten private final StateMachineStates stateMachineStates; + private Boolean contextEvents; + /** * Instantiates a new enum state machine factory. * @@ -140,7 +142,7 @@ public class EnumStateMachineFactory, E extends Enum> exten Collection> stateDatas = popSameParents(stateStack); Collection> transitionsData = getTransitionData(iterator.hasNext(), stateDatas); - machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory()); + machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents); // TODO: last part in if feels a bit hack // (!peek.isInitial() && !machineMap.containsKey(peek.getParent())) if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) { @@ -168,7 +170,7 @@ public class EnumStateMachineFactory, E extends Enum> exten } if (initials == 1) { - machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory()); + machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents); } } @@ -183,13 +185,21 @@ public class EnumStateMachineFactory, E extends Enum> exten RegionState rstate = new RegionState(null, regions); Collection> states = new ArrayList>(); states.add(rstate); - machine = new EnumStateMachine(states, null, rstate, null); + EnumStateMachine m = new EnumStateMachine(states, null, rstate, null); + if (contextEvents != null) { + m.setContextEventsEnabled(contextEvents); + } + machine = m; } } return machine; } + public void setContextEventsEnabled(Boolean contextEvents) { + this.contextEvents = contextEvents; + } + private Collection> getTransitionData(boolean roots, Collection> stateDatas) { if (roots) { return resolveTransitionData(stateMachineTransitions.getTransitions(), stateDatas); @@ -257,7 +267,7 @@ public class EnumStateMachineFactory, E extends Enum> exten private static , E extends Enum> StateMachine buildMachine( Map> machineMap, Map> stateMap, Collection> stateDatas, - Collection> transitionsData, BeanFactory beanFactory) { + Collection> transitionsData, BeanFactory beanFactory, Boolean contextEvents) { State state = null; State initialState = null; Action initialAction = null; @@ -339,6 +349,9 @@ public class EnumStateMachineFactory, E extends Enum> exten EnumStateMachine machine = new EnumStateMachine(states, transitions, initialState, initialTransition, endState, null, null); + if (contextEvents != null) { + machine.setContextEventsEnabled(contextEvents); + } machine.afterPropertiesSet(); if (beanFactory != null) { machine.setBeanFactory(beanFactory); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java index 2a2dd955..863b3a49 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java @@ -21,7 +21,9 @@ import java.util.List; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.config.EnableStateMachine; @@ -45,9 +47,14 @@ public class StateMachineConfiguration, E extends Enum> ext Class namedAnnotation) throws Exception { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(StateMachineDelegatingFactoryBean2.class); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes( + EnableStateMachine.class.getName(), false)); + Boolean contextEvents = attributes.getBoolean("contextEvents"); + beanDefinitionBuilder.addConstructorArgValue(builder); beanDefinitionBuilder.addConstructorArgValue(StateMachine.class); beanDefinitionBuilder.addConstructorArgValue(importingClassMetadata.getClassName()); + beanDefinitionBuilder.addConstructorArgValue(contextEvents); return beanDefinitionBuilder.getBeanDefinition(); } @@ -62,11 +69,13 @@ public class StateMachineConfiguration, E extends Enum> ext extends BeanDelegatingFactoryBean,StateMachineConfigBuilder,StateMachineConfig> { private String clazzName; + private Boolean contextEvents; public StateMachineDelegatingFactoryBean2(StateMachineConfigBuilder builder, Class> clazz, - String clazzName) { + String clazzName, Boolean contextEvents) { super(builder, clazz); this.clazzName = clazzName; + this.contextEvents = contextEvents; } @Override @@ -83,6 +92,7 @@ public class StateMachineConfiguration, E extends Enum> ext EnumStateMachineFactory stateMachineFactory = new EnumStateMachineFactory( stateMachineTransitions, stateMachineStates); stateMachineFactory.setBeanFactory(getBeanFactory()); + stateMachineFactory.setContextEventsEnabled(contextEvents); setObject(stateMachineFactory.getStateMachine()); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java index df50d8e2..ed43c6be 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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.springframework.statemachine.config.configuration; import java.lang.annotation.Annotation; @@ -12,7 +27,9 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnumStateMachineFactory; @@ -35,7 +52,11 @@ public class StateMachineFactoryConfiguration, E extends Enum< Class namedAnnotation) throws Exception { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes( + EnableStateMachineFactory.class.getName(), false)); + Boolean contextEvents = attributes.getBoolean("contextEvents"); beanDefinitionBuilder.addConstructorArgValue(builder); + beanDefinitionBuilder.addConstructorArgValue(contextEvents); return beanDefinitionBuilder.getBeanDefinition(); } @@ -58,9 +79,12 @@ public class StateMachineFactoryConfiguration, E extends Enum< private StateMachineFactory stateMachineFactory; + private Boolean contextEvents; + @SuppressWarnings("unused") - public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder builder) { + public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder builder, Boolean contextEvents) { this.builder = builder; + this.contextEvents = contextEvents; } @Override @@ -88,6 +112,7 @@ public class StateMachineFactoryConfiguration, E extends Enum< StateMachineStates stateMachineStates = stateMachineConfig.getStates(); EnumStateMachineFactory enumStateMachineFactory = new EnumStateMachineFactory(stateMachineTransitions, stateMachineStates); enumStateMachineFactory.setBeanFactory(beanFactory); + enumStateMachineFactory.setContextEventsEnabled(contextEvents); this.stateMachineFactory = enumStateMachineFactory; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index b028054e..75075182 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -105,6 +105,8 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private final Map, Transition> triggerToTransitionMap = new HashMap, Transition>(); + private boolean contextEventsEnabled = true; + /** * Instantiates a new abstract state machine. * @@ -270,6 +272,17 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport return buf.toString(); } + /** + * Set if context application events are enabled. Events + * are enabled by default. Set this to false if you don't + * want state machine to send application context events. + * + * @param contextEventsEnabled the enabled flag + */ + public void setContextEventsEnabled(boolean contextEventsEnabled) { + this.contextEventsEnabled = contextEventsEnabled; + } + protected boolean acceptEvent(Message event) { boolean accepted = currentState.sendEvent(event); @@ -692,49 +705,61 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private void notifyStateChanged(State source, State target) { stateListener.stateChanged(source, target); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishStateChanged(this, source, target); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishStateChanged(this, source, target); + } } } private void notifyStateEntered(State state) { stateListener.stateEntered(state); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishStateEntered(this, state); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishStateEntered(this, state); + } } } private void notifyStateExited(State state) { stateListener.stateExited(state); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishStateExited(this, state); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishStateExited(this, state); + } } } private void notifyTransitionStart(Transition transition) { stateListener.transitionStarted(transition); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishTransitionStart(this, transition); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishTransitionStart(this, transition); + } } } private void notifyTransition(Transition transition) { stateListener.transition(transition); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishTransitionEnd(this, transition); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishTransitionEnd(this, transition); + } } } private void notifyTransitionEnd(Transition transition) { stateListener.transitionEnded(transition); - StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); - if (eventPublisher != null) { - eventPublisher.publishTransition(this, transition); + if (contextEventsEnabled) { + StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); + if (eventPublisher != null) { + eventPublisher.publishTransition(this, transition); + } } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/ContextEventTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/ContextEventTests.java new file mode 100644 index 00000000..e25eeb6e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/ContextEventTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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.springframework.statemachine.event; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * State machine context events related tests. + * + * @author Janne Valkealahti + * + */ +public class ContextEventTests extends AbstractStateMachineTests { + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @SuppressWarnings("unchecked") + @Test + public void contextEventsEnabled() throws Exception { + context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config.class, Config1.class); + context.refresh(); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + machine.start(); + machine.sendEvent(TestEvents.E1); + StateMachineApplicationEventListener listener = context.getBean(StateMachineApplicationEventListener.class); + listener.latch.await(1, TimeUnit.SECONDS); + assertThat(listener.count, greaterThan(1)); + } + + @SuppressWarnings("unchecked") + @Test + public void contextEventsDisabled() throws Exception { + context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config.class, Config2.class); + context.refresh(); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + machine.start(); + machine.sendEvent(TestEvents.E1); + StateMachineApplicationEventListener listener = context.getBean(StateMachineApplicationEventListener.class); + listener.latch.await(1, TimeUnit.SECONDS); + assertThat(listener.count, is(0)); + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + + } + + @Configuration + @EnableStateMachine(contextEvents = false) + static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + + } + + @Configuration + static class Config { + + @Bean + public StateMachineApplicationEventListener stateMachineApplicationEventListener() { + return new StateMachineApplicationEventListener(); + } + } + + static class StateMachineApplicationEventListener implements ApplicationListener { + + CountDownLatch latch = new CountDownLatch(1); + int count = 0; + + @Override + public void onApplicationEvent(StateMachineEvent event) { + count++; + latch.countDown(); + } + } + +}