From 48b694fa47e0f1cc2a6153cbf3b1dbd20b8d97fb Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 10 Jan 2017 15:59:29 +0000 Subject: [PATCH] @WithStateMachine with factory and builder - Add support so that @WithStateMachine can be used with machines build via @EnableStateMachineFactory or via manual builders. - Adding new annotation @EnableWithStateMachine to help handling needed context configuration. - Add tests and docs. - Fixes #292 - Fixes #224 --- docs/src/reference/asciidoc/sm.adoc | 32 +++ .../annotation/WithStateMachine.java | 13 +- .../config/EnableStateMachine.java | 9 +- .../config/EnableStateMachineFactory.java | 9 +- .../config/EnableWithStateMachine.java | 45 ++++ .../StateMachineHandlerCallHelper.java | 60 +++++- .../support/StateMachineObjectSupport.java | 13 +- .../MethodAnnotationWithBuilderTests.java | 192 ++++++++++++++++++ .../MethodAnnotationWithFactoryTests.java | 106 ++++++++++ .../docs/DocsConfigurationSampleTests4.java | 46 +++++ 10 files changed, 504 insertions(+), 21 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableWithStateMachine.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithBuilderTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithFactoryTests.java diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 7783e189..a0d7fc25 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -1144,6 +1144,15 @@ application context by using annotation `name` field. include::samples/DocsConfigurationSampleTests4.java[tags=snippetAA] ---- +Sometimes it is more convenient to use _machine id_ which is something +user can set to better identify multiple instances. This id maps to +_getId()_ method in a _StateMachine_ interface. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAAA] +---- + _@WithStateMachine_ can also be used as a meta-annotation as shown above. In this case you could annotate your bean with _WithMyBean_. @@ -1158,6 +1167,29 @@ Return type of these methods doesn't matter and is effectively discard. ==== +=== Enabling Integration +All features for _@WithStateMachine_ can be enabled by using +annotation _@EnableWithStateMachine_ which simply imports needed +configuration into Spring Application Context. Both +_@EnableStateMachine_ and _@EnableStateMachineFactory_ are already +annotated with this so there is no need for user to add it again. +However if machine is build and configured without a use of +configuration adapters, _@EnableWithStateMachine_ must be used order +to use features with _@WithStateMachine_. Idea for this is shown +below. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAAAA] +---- + +[IMPORTANT] +==== +If machine is not created as a _Bean_ then it is mandatory to set +_BeanFactory_ for a machine as shown above. Otherwise machine will be +unaware of handlers calling your _@WithStateMachine_ methods. +==== + === Method Parameters Every annotation is supporting exactly same set of possible method parameters but runtime behaviour is different depending on an diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java index d706943e..7248cfd5 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.stereotype.Component; @@ -42,10 +43,18 @@ import org.springframework.stereotype.Component; public @interface WithStateMachine { /** - * The name of a state machine which annotated bean should be associated. + * The name of a state machine bean which annotated bean should be associated. + * Defaults to {@code stateMachine} * * @return the state machine bean name */ String name() default StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE; + /** + * The id of a state machine which annotated bean should be associated. + * + * @return the state machine id + * @see StateMachine#getId() + */ + String id() default ""; } 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 b5d1774c..50fb0b15 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -27,7 +27,6 @@ import org.springframework.context.annotation.Import; import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; -import org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration; import org.springframework.statemachine.config.configuration.StateMachineCommonConfiguration; import org.springframework.statemachine.config.configuration.StateMachineConfiguration; import org.springframework.statemachine.config.configuration.StateMachineConfigurationImportSelector; @@ -43,9 +42,9 @@ import org.springframework.statemachine.config.configuration.StateMachineConfigu @Target(ElementType.TYPE) @Documented @EnableAnnotationConfiguration -@Import({ StateMachineConfigurationImportSelector.class, StateMachineCommonConfiguration.class, - StateMachineConfiguration.class, ObjectPostProcessorConfiguration.class, - StateMachineAnnotationPostProcessorConfiguration.class }) +@Import({ StateMachineConfigurationImportSelector.class, StateMachineCommonConfiguration.class, StateMachineConfiguration.class, + ObjectPostProcessorConfiguration.class }) +@EnableWithStateMachine public @interface EnableStateMachine { /** 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 9b628cbb..f40d5cb5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -27,7 +27,6 @@ import org.springframework.context.annotation.Import; import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; -import org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration; import org.springframework.statemachine.config.configuration.StateMachineCommonConfiguration; import org.springframework.statemachine.config.configuration.StateMachineConfigurationImportSelector; import org.springframework.statemachine.config.configuration.StateMachineFactoryConfiguration; @@ -43,9 +42,9 @@ import org.springframework.statemachine.config.configuration.StateMachineFactory @Target(ElementType.TYPE) @Documented @EnableAnnotationConfiguration -@Import({ StateMachineConfigurationImportSelector.class, StateMachineCommonConfiguration.class, - StateMachineFactoryConfiguration.class, ObjectPostProcessorConfiguration.class, - StateMachineAnnotationPostProcessorConfiguration.class }) +@Import({ StateMachineConfigurationImportSelector.class, StateMachineCommonConfiguration.class, StateMachineFactoryConfiguration.class, + ObjectPostProcessorConfiguration.class }) +@EnableWithStateMachine public @interface EnableStateMachineFactory { /** diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableWithStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableWithStateMachine.java new file mode 100644 index 00000000..5dbd0503 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableWithStateMachine.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.annotation.WithStateMachine; +import org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration; +import org.springframework.stereotype.Component; + +/** + * Annotation which enables features needed for {@link WithStateMachine}. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@Component +@Configuration +@Import(StateMachineAnnotationPostProcessorConfiguration.class) +public @interface EnableWithStateMachine { +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java index 51f6dc42..f0c2d84c 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -48,6 +48,7 @@ import org.springframework.statemachine.config.configuration.StateMachineHandler import org.springframework.statemachine.state.State; import org.springframework.statemachine.support.StateMachineUtils; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Helper class which is used from a StateMachineObjectSupport to ease handling @@ -84,14 +85,15 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be Annotation metaAnnotation = handler.getMetaAnnotation(); WithStateMachine withStateMachine = AnnotationUtils.findAnnotation(handler.getBeanClass(), WithStateMachine.class); - String statemachineBeanName = withStateMachine.name(); - String key = metaAnnotation.annotationType().getName() + statemachineBeanName; - List list = cache.get(key); - if (list == null) { - list = new ArrayList<>(); - cache.put(key, list); + + if (StringUtils.hasText(withStateMachine.name())) { + updateCache(metaAnnotation.annotationType().getName() + withStateMachine.name(), + new CacheEntry(handler, annotation, metaAnnotation)); + } + if (StringUtils.hasText(withStateMachine.id())) { + updateCache(metaAnnotation.annotationType().getName() + withStateMachine.id(), + new CacheEntry(handler, annotation, metaAnnotation)); } - list.add(new CacheEntry(handler, annotation, metaAnnotation)); } } @@ -103,6 +105,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateChanged(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateChanged.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -120,6 +125,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateEntry(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateEntry.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -137,6 +145,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateExit(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateExit.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -154,6 +165,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnEventNotAccepted(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnEventNotAccepted.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -175,6 +189,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be public void callOnTransitionStart(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnTransitionStart.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -192,6 +209,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnTransition(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnTransition.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -209,6 +229,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnTransitionEnd(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnTransitionEnd.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -226,6 +249,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateMachineStart(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateMachineStart.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -239,6 +265,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateMachineStop(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateMachineStop.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -252,6 +281,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnStateMachineError(String stateMachineId, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnStateMachineError.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -265,6 +297,9 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be } public void callOnExtendedStateChanged(String stateMachineId, Object key, Object value, StateContext stateContext) { + if (!StringUtils.hasText(stateMachineId)) { + return; + } List> handlersList = new ArrayList>(); String cacheKey = OnExtendedStateChanged.class.getName() + stateMachineId; List list = getCacheEntries(cacheKey); @@ -279,6 +314,15 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be getStateMachineHandlerResults(handlersList, stateContext); } + private void updateCache(String key, CacheEntry cacheEntry) { + List list = cache.get(key); + if (list == null) { + list = new ArrayList<>(); + cache.put(key, list); + } + list.add(cacheEntry); + } + private synchronized List getCacheEntries(String cacheKey) { if (stateMachineHandlerApplicationListener != null) { Long l = stateMachineHandlerApplicationListener.getLastRefreshTime(); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java index 9fda7578..5523a12b 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -138,6 +138,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateChanged(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateChanged(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateChanged(stateContext.getStateMachine().getId(), stateContext); stateListener.stateChanged(stateContext.getSource(), stateContext.getTarget()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -154,6 +155,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateEntered(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateEntry(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateEntry(stateContext.getStateMachine().getId(), stateContext); stateListener.stateEntered(stateContext.getTarget()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -170,6 +172,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateExited(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateExit(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateExit(stateContext.getStateMachine().getId(), stateContext); stateListener.stateExited(stateContext.getSource()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -186,6 +189,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyEventNotAccepted(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnEventNotAccepted(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnEventNotAccepted(stateContext.getStateMachine().getId(), stateContext); stateListener.eventNotAccepted(stateContext.getMessage()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -202,6 +206,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransitionStart(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnTransitionStart(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnTransitionStart(stateContext.getStateMachine().getId(), stateContext); stateListener.transitionStarted(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -218,6 +223,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransition(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnTransition(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnTransition(stateContext.getStateMachine().getId(), stateContext); stateListener.transition(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -234,6 +240,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransitionEnd(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnTransitionEnd(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnTransitionEnd(stateContext.getStateMachine().getId(), stateContext); stateListener.transitionEnded(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -250,6 +257,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineStarted(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineStart(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateMachineStart(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineStarted(stateContext.getStateMachine()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -266,6 +274,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineStopped(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineStop(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateMachineStop(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineStopped(stateContext.getStateMachine()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -282,6 +291,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineError(StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineError(getBeanName(), stateContext); + stateMachineHandlerCallHelper.callOnStateMachineError(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineError(stateContext.getStateMachine(), stateContext.getException()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { @@ -298,6 +308,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyExtendedStateChanged(Object key, Object value, StateContext stateContext) { try { stateMachineHandlerCallHelper.callOnExtendedStateChanged(getBeanName(), key, value, stateContext); + stateMachineHandlerCallHelper.callOnExtendedStateChanged(stateContext.getStateMachine().getId(), key, value, stateContext); stateListener.extendedStateChanged(key, value); stateListener.stateContext(stateContext); if (contextEventsEnabled) { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithBuilderTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithBuilderTests.java new file mode 100644 index 00000000..dfd7029b --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithBuilderTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2017 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.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.beans.factory.BeanFactory; +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.StateMachine; +import org.springframework.statemachine.config.EnableWithStateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.config.StateMachineBuilder.Builder; +import org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration; +import org.springframework.statemachine.config.configurers.ConfigurationConfigurer; + +@SuppressWarnings("unchecked") +public class MethodAnnotationWithBuilderTests extends AbstractStateMachineTests { + + @Test + public void testMethodAnnotations1() throws Exception { + context.register(BeanConfig1.class, StateMachineAnnotationPostProcessorConfiguration.class); + context.refresh(); + + Bean1 bean1 = context.getBean(Bean1.class); + + StateMachine machine = context.getBean(StateMachine.class); + machine.start(); + + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + public void testMethodAnnotations2() throws Exception { + context.register(BeanConfig2.class); + context.refresh(); + + Bean1 bean1 = context.getBean(Bean1.class); + + StateMachine machine = context.getBean(StateMachine.class); + machine.start(); + + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + public void testMethodAnnotations3() throws Exception { + context.register(BeanConfig1.class, StateMachineAnnotationPostProcessorConfiguration.class); + context.refresh(); + + Bean1 bean1 = context.getBean(Bean1.class); + + StateMachine machine = buildMachine(context); + machine.start(); + + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + } + + @Test + public void testMethodAnnotations4() throws Exception { + context.register(BeanConfig3.class); + context.refresh(); + + Bean2 bean2 = context.getBean(Bean2.class); + + StateMachine machine = buildMachine(context); + machine.start(); + + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + assertThat(bean2.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + } + + @WithStateMachine(id = "xxx") + static class Bean1 { + + CountDownLatch onStateChangedLatch = new CountDownLatch(1); + + @OnStateChanged + public void onStateChanged() { + onStateChangedLatch.countDown(); + } + } + + @WithStateMachine(name = "", id = "xxx") + static class Bean2 { + + CountDownLatch onStateChangedLatch = new CountDownLatch(1); + + @OnStateChanged + public void onStateChanged() { + onStateChangedLatch.countDown(); + } + } + + @Configuration + static class BeanConfig1 { + + @Bean + public Bean1 bean1() { + return new Bean1(); + } + + @Bean + public StateMachine stateMachine() throws Exception { + return buildMachine(null); + } + } + + @EnableWithStateMachine + static class BeanConfig2 { + + @Bean + public Bean1 bean1() { + return new Bean1(); + } + + @Bean + public StateMachine stateMachine() throws Exception { + return buildMachine(null); + } + } + + @EnableWithStateMachine + static class BeanConfig3 { + + @Bean + public Bean2 bean2() { + return new Bean2(); + } + } + + private static StateMachine buildMachine(BeanFactory beanFactory) throws Exception { + Builder builder = StateMachineBuilder.builder(); + + ConfigurationConfigurer withConfiguration = builder.configureConfiguration().withConfiguration(); + withConfiguration.machineId("xxx"); + if (beanFactory != null) { + withConfiguration.beanFactory(beanFactory); + } + + builder.configureStates() + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + + builder.configureTransitions() + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + + return builder.build(); + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithFactoryTests.java new file mode 100644 index 00000000..fca16791 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationWithFactoryTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 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.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +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.StateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +@SuppressWarnings("unchecked") +public class MethodAnnotationWithFactoryTests extends AbstractStateMachineTests { + + @Test + public void testMethodAnnotations1() throws Exception { + context.register(BeanConfig1.class, Config1.class); + context.refresh(); + + Bean1 bean1 = context.getBean(Bean1.class); + + StateMachineFactory factory = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, StateMachineFactory.class); + StateMachine machine = factory.getStateMachine("xxx"); + machine.start(); + + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + } + + @WithStateMachine(name = "xxx") + static class Bean1 { + + CountDownLatch onStateChangedLatch = new CountDownLatch(1); + + @OnStateChanged + public void onStateChanged() { + onStateChangedLatch.countDown(); + } + } + + @Configuration + static class BeanConfig1 { + + @Bean + public Bean1 bean1() { + return new Bean1(); + } + } + + @Configuration + @EnableStateMachineFactory + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java index e19851a9..4f8ba008 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests4.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Map; +import org.springframework.beans.factory.BeanFactory; import org.springframework.messaging.Message; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.ExtendedState; @@ -37,6 +38,8 @@ import org.springframework.statemachine.annotation.OnStateMachineStart; import org.springframework.statemachine.annotation.OnStateMachineStop; import org.springframework.statemachine.annotation.OnTransition; import org.springframework.statemachine.annotation.WithStateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.config.StateMachineBuilder.Builder; public class DocsConfigurationSampleTests4 extends AbstractStateMachineTests { @@ -60,6 +63,16 @@ public class DocsConfigurationSampleTests4 extends AbstractStateMachineTests { } // end::snippetAA[] +// tag::snippetAAAA[] + @WithStateMachine(id = "myMachineId") + public class Bean16 { + + @OnTransition + public void anyTransition() { + } + } +// end::snippetAAAA[] + // tag::snippetAAA[] @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -68,6 +81,39 @@ public class DocsConfigurationSampleTests4 extends AbstractStateMachineTests { } // end::snippetAAA[] + +// tag::snippetAAAAA[] + public static StateMachine buildMachine(BeanFactory beanFactory) throws Exception { + Builder builder = StateMachineBuilder.builder(); + + builder.configureConfiguration() + .withConfiguration() + .machineId("myMachineId") + .beanFactory(beanFactory); + + builder.configureStates() + .withStates() + .initial("S1") + .state("S2"); + + builder.configureTransitions() + .withExternal() + .source("S1") + .target("S2") + .event("E1"); + + return builder.build(); + } + + @WithStateMachine(id = "myMachineId") + static class Bean17 { + + @OnStateChanged + public void onStateChanged() { + } + } +// end::snippetAAAAA[] + // tag::snippetB[] @WithStateMachine public class Bean3 {