From 48c65fd9ac90aca2cb57989a319dfb00f940213a Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 4 Feb 2015 18:17:50 +0000 Subject: [PATCH] initial import --- build.gradle | 178 ++++++ docs/src/api/overview.html | 14 + docs/src/api/stylesheet.css | 599 ++++++++++++++++++ docs/src/info/changelog.txt | 4 + docs/src/info/license.txt | 201 ++++++ docs/src/info/notice.txt | 21 + docs/src/info/readme.txt | 24 + docs/src/reference/asciidoc/Guardfile | 13 + docs/src/reference/asciidoc/appendix.adoc | 16 + docs/src/reference/asciidoc/index-docinfo.xml | 22 + docs/src/reference/asciidoc/index.adoc | 37 ++ docs/src/reference/asciidoc/introduction.adoc | 5 + docs/src/reference/asciidoc/preface.adoc | 4 + docs/src/reference/asciidoc/sm.adoc | 5 + gradle.properties | 4 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51018 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 +++++ gradlew.bat | 90 +++ publish-maven.gradle | 60 ++ settings.gradle | 3 + .../statemachine/EnumStateMachine.java | 47 ++ .../statemachine/ExtendedState.java | 37 ++ .../statemachine/StateContext.java | 47 ++ .../statemachine/StateMachine.java | 73 +++ .../StateMachineSystemConstants.java | 32 + .../statemachine/action/Action.java | 36 ++ .../statemachine/annotation/OnTransition.java | 35 + .../annotation/WithStateMachine.java | 36 ++ .../config/EnableStateMachine.java | 54 ++ .../config/EnableStateMachineFactory.java | 54 ++ .../EnumStateMachineConfigurerAdapter.java | 20 + .../config/EnumStateMachineFactory.java | 103 +++ .../config/StateMachineConfig.java | 39 ++ .../config/StateMachineConfigurerAdapter.java | 73 +++ .../config/StateMachineFactory.java | 24 + .../builders/StateMachineConfigBuilder.java | 36 ++ .../builders/StateMachineConfigurer.java | 28 + .../builders/StateMachineStateBuilder.java | 66 ++ .../builders/StateMachineStateConfigurer.java | 24 + .../config/builders/StateMachineStates.java | 74 +++ .../StateMachineTransitionBuilder.java | 72 +++ .../StateMachineTransitionConfigurer.java | 27 + .../builders/StateMachineTransitions.java | 71 +++ .../annotation/AbstractAnnotationBuilder.java | 67 ++ .../AbstractAnnotationConfiguration.java | 96 +++ .../AbstractConfiguredAnnotationBuilder.java | 531 ++++++++++++++++ ...tractImportingAnnotationConfiguration.java | 188 ++++++ .../common/annotation/AnnotationBuilder.java | 36 ++ .../annotation/AnnotationConfigurer.java | 58 ++ .../AnnotationConfigurerAdapter.java | 133 ++++ .../AnnotationConfigurerBuilder.java | 35 + .../EnableAnnotationConfiguration.java | 36 ++ .../annotation/ObjectPostProcessor.java | 53 ++ ...utowireBeanFactoryObjectPostProcessor.java | 118 ++++ .../ObjectPostProcessorConfiguration.java | 42 ++ .../DefaultPropertiesConfigurer.java | 105 +++ .../DefaultResourceConfigurer.java | 126 ++++ .../configurers/PropertiesConfigurer.java | 40 ++ .../PropertiesConfigurerAware.java | 36 ++ .../configurers/ResourceConfigurer.java | 43 ++ .../configurers/ResourceConfigurerAware.java | 39 ++ .../StateMachineConfiguration.java | 103 +++ .../StateMachineFactoryConfiguration.java | 103 +++ .../DefaultExternalTransitionConfigurer.java | 98 +++ .../DefaultInternalTransitionConfigurer.java | 92 +++ .../configurers/DefaultStateConfigurer.java | 74 +++ .../ExternalTransitionConfigurer.java | 39 ++ .../InternalTransitionConfigurer.java | 31 + .../config/configurers/StateConfigurer.java | 38 ++ .../configurers/TransitionConfigurer.java | 76 +++ .../statemachine/guard/Guard.java | 39 ++ .../guard/SpelExpressionGuard.java | 49 ++ .../listener/AbstractCompositeListener.java | 67 ++ .../CompositeStateMachineListener.java | 33 + .../listener/OrderedComposite.java | 107 ++++ .../listener/StateMachineListener.java | 22 + .../MethodAnnotationPostProcessor.java | 45 ++ ...dInvokingStateMachineRuntimeProcessor.java | 54 ++ ...chineActivatorAnnotationPostProcessor.java | 70 ++ .../StateMachineAnnotationPostProcessor.java | 248 ++++++++ .../processor/StateMachineHandler.java | 102 +++ .../StateMachineMethodInvokerHelper.java | 553 ++++++++++++++++ .../StateMachineOnTransitionHandler.java | 35 + .../processor/StateMachineRuntime.java | 30 + .../StateMachineRuntimeProcessor.java | 37 ++ .../statemachine/state/AbstractState.java | 70 ++ .../statemachine/state/EnumState.java | 42 ++ .../statemachine/state/State.java | 60 ++ .../support/AbstractExpressionEvaluator.java | 129 ++++ .../support/AbstractStateMachine.java | 326 ++++++++++ .../support/AnnotatedMethodFilter.java | 75 +++ .../support/BeanFactoryTypeConverter.java | 154 +++++ .../support/DefaultExtendedState.java | 45 ++ .../support/DefaultStateContext.java | 43 ++ .../statemachine/support/ExpressionUtils.java | 96 +++ .../support/FixedMethodFilter.java | 54 ++ .../support/LifecycleObjectSupport.java | 250 ++++++++ .../support/StateMachineContextUtils.java | 110 ++++ .../support/UniqueMethodFilter.java | 46 ++ .../AbstractExternalTransition.java | 31 + .../AbstractInternalTransition.java | 30 + .../transition/AbstractLocalTransition.java | 30 + .../transition/AbstractTransition.java | 105 +++ .../transition/DefaultExternalTransition.java | 30 + .../transition/DefaultInternalTransition.java | 30 + .../statemachine/transition/Transition.java | 48 ++ .../transition/TransitionKind.java | 36 ++ .../statemachine/trigger/EventTrigger.java | 31 + .../statemachine/trigger/Trigger.java | 33 + .../AbstractStateMachineTests.java | 93 +++ .../statemachine/EnumStateMachineTests.java | 211 ++++++ .../StateMachineFactoryTests.java | 81 +++ .../statemachine/StateMachineTests.java | 129 ++++ .../statemachine/TestUtils.java | 94 +++ .../statemachine/action/ActionTests.java | 145 +++++ .../annotation/MethodAnnotationTests.java | 141 +++++ .../config/ConfigurationTests.java | 118 ++++ .../ComplexAnnotationConfigurationTests.java | 121 ++++ .../common/annotation/DependencyBean.java | 32 + .../MixedAnnotationConfigurationTests.java | 97 +++ .../MultipleAnnotationConfigurationTests.java | 94 +++ .../SimpleAnnotationConfiguration2Tests.java | 121 ++++ .../SimpleAnnotationConfigurationTests.java | 121 ++++ .../XmlImportDependenciesTests.java | 96 +++ .../annotation/complex/ComplexTestConfig.java | 49 ++ .../complex/ComplexTestConfigBeanA.java | 33 + .../ComplexTestConfigBeanABuilder.java | 67 ++ .../complex/ComplexTestConfigBeanB.java | 29 + .../ComplexTestConfigBeanBBuilder.java | 61 ++ .../ComplexTestConfigBeanBConfigurer.java | 26 + .../complex/ComplexTestConfigBuilder.java | 58 ++ .../complex/ComplexTestConfiguration.java | 71 +++ .../complex/ComplexTestConfigurer.java | 32 + .../complex/ComplexTestConfigurerAdapter.java | 74 +++ .../annotation/complex/EnableComplexTest.java | 41 ++ .../annotation/simple/EnableSimpleTest.java | 41 ++ .../annotation/simple/EnableSimpleTest2.java | 44 ++ .../annotation/simple/SimpleTestConfig.java | 46 ++ .../simple/SimpleTestConfigBeanA.java | 33 + .../simple/SimpleTestConfigBeanABuilder.java | 67 ++ .../simple/SimpleTestConfigBeanB.java | 29 + .../simple/SimpleTestConfigBeanBBuilder.java | 61 ++ .../SimpleTestConfigBeanBConfigurer.java | 26 + .../simple/SimpleTestConfigBuilder.java | 58 ++ .../simple/SimpleTestConfiguration.java | 63 ++ .../simple/SimpleTestConfiguration2.java | 67 ++ .../simple/SimpleTestConfigurer.java | 32 + .../simple/SimpleTestConfigurerAdapter.java | 74 +++ .../statemachine/guard/GuardTests.java | 172 +++++ .../guard/SpelExpressionGuardTests.java | 105 +++ .../statemachine/listener/ListenerTests.java | 173 +++++ .../statemachine/state/StateActionTests.java | 109 ++++ .../transition/TransitionTests.java | 174 +++++ .../annotation/XmlImportDependencies.xml | 10 + 155 files changed, 12028 insertions(+) create mode 100644 build.gradle create mode 100644 docs/src/api/overview.html create mode 100644 docs/src/api/stylesheet.css create mode 100644 docs/src/info/changelog.txt create mode 100644 docs/src/info/license.txt create mode 100644 docs/src/info/notice.txt create mode 100644 docs/src/info/readme.txt create mode 100644 docs/src/reference/asciidoc/Guardfile create mode 100644 docs/src/reference/asciidoc/appendix.adoc create mode 100644 docs/src/reference/asciidoc/index-docinfo.xml create mode 100644 docs/src/reference/asciidoc/index.adoc create mode 100644 docs/src/reference/asciidoc/introduction.adoc create mode 100644 docs/src/reference/asciidoc/preface.adoc create mode 100644 docs/src/reference/asciidoc/sm.adoc create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 publish-maven.gradle create mode 100644 settings.gradle create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java create mode 100644 spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..637a0a4f5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,178 @@ +buildscript { + repositories { + maven { url 'http://repo.springsource.org/libs-release'} + maven { url 'http://repo.springsource.org/plugins-release' } + } + dependencies { + classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") + classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.2') + classpath("io.spring.gradle:docbook-reference-plugin:0.3.0") + } +} + +configure(allprojects) { + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'idea' + + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + group = 'org.springframework.statemachine' + + [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none'] + + repositories { + mavenCentral() + maven { url "http://repo.springsource.org/libs-release" } + } + + task integrationTest(type: Test) { + include '**/*IntegrationTests.*' + } + + test { + exclude '**/*IntegrationTests.*' + } + + // servlet-api (2.5) and tomcat-servlet-api (3.0) classpath entries should not be + // exported to dependent projects in Eclipse to avoid false compilation errors due + // to changing APIs across these versions + eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.findAll { entry -> entry.path.contains('servlet-api') }*.exported = false + } +} + +configure(subprojects) { subproject -> + apply from: "${rootProject.projectDir}/publish-maven.gradle" + + jar { + manifest.attributes['Implementation-Title'] = subproject.name + manifest.attributes['Implementation-Version'] = subproject.version + + from("${rootProject.projectDir}/src/dist") { + include "license.txt" + include "notice.txt" + into "META-INF" + expand(copyright: new Date().format('yyyy'), version: project.version) + } + } + + javadoc { + // /config/configuration/StateMachineConfiguration.html... + // java.lang.ClassCastException: com.sun.tools.javadoc.MethodDocImpl cannot be cast + // to com.sun.tools.javadoc.AnnotationTypeElementDocImpl + // @Bean(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY) + // vs. + // @Bean + + enabled = false + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = project.name + verbose = true + } + + task sourcesJar(type: Jar, dependsOn:classes) { + classifier = 'sources' + from sourceSets.main.allJava + } + + task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc + } + + artifacts { + archives sourcesJar + archives javadocJar + } +} + +project('spring-statemachine-core') { + description = "Spring State Machine Core" + + dependencies { + compile "org.springframework:spring-messaging:$springVersion" + + testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" + testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" + testCompile "junit:junit:$junitVersion" + } +} + +configure(rootProject) { + description = 'Spring State Machine' + + apply plugin: 'org.asciidoctor.gradle.asciidoctor' + apply plugin: "docbook-reference" + + // don't publish the default jar for the root project + configurations.archives.artifacts.clear() + + reference { + sourceDir = new File(asciidoctor.outputDir , 'docbook5') + pdfFilename = "spring-statemachine-reference.pdf" + epubFilename = "spring-statemachine-reference.epub" + expandPlaceholders = "" + } + + afterEvaluate { + tasks.findAll { it.name.startsWith("reference") }.each{ it.dependsOn.add("asciidoctor") } + } + + asciidoctorj { + version = '1.5.2' + } + + asciidoctor { + sourceDir = file("docs/src/reference/asciidoc") + backends = ['docbook5'] + options eruby: 'erubis' + attributes docinfo: '', + copycss : '', + icons : 'font', + 'source-highlighter': 'prettify', + sectanchors : '', + toc2: '', + idprefix: '', + idseparator: '-', + doctype: 'book', + numbered: '', + 'spring-hadoop-version' : project.version, + 'spring-version' : springVersion, + revnumber : project.version + } + + dependencies { // for integration tests + } + + task api(type: Javadoc) { + group = 'Documentation' + description = 'Generates aggregated Javadoc API documentation.' + title = "${rootProject.description} ${version} API" + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = rootProject.description + options.overview = 'src/api/overview.html' + options.links( + 'http://docs.jboss.org/jbossas/javadoc/4.0.5/connector' + ) + source subprojects.collect { project -> + project.sourceSets.main.allJava + } + destinationDir = new File(buildDir, "api") + classpath = files(subprojects.collect { project -> + project.sourceSets.main.compileClasspath + }) + maxMemory = '1024m' + } + + task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '2.2.1' + } + +} + diff --git a/docs/src/api/overview.html b/docs/src/api/overview.html new file mode 100644 index 000000000..5152c2999 --- /dev/null +++ b/docs/src/api/overview.html @@ -0,0 +1,14 @@ + + +This document is the API specification for the Spring Statemachine project. +
+ +
+

+ For further API reference and developer documentation, see the + Spring Statemachine Project Page. + There you can find the latest news, links to documentation, books, presentations and webinars. +

+
+ + diff --git a/docs/src/api/stylesheet.css b/docs/src/api/stylesheet.css new file mode 100644 index 000000000..029c89c91 --- /dev/null +++ b/docs/src/api/stylesheet.css @@ -0,0 +1,599 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +@import url('resources/fonts/dejavu.css'); + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a:hover, a:focus { + text-decoration:none; + color:#bb7a2a; +} +a:active { + text-decoration:none; + color:#4A6782; +} +a[name] { + color:#353833; +} +a[name]:hover { + text-decoration:none; + color:#353833; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.subNavList li{ + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexHeader { + margin:10px; + position:relative; +} +.indexHeader span{ + margin-right:15px; +} +.indexHeader h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Sans Mono',monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* +List styles +*/ +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width:100%; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align:left; + padding:0px 0px 12px 10px; + width:100%; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space:nowrap; + font-size:13px; +} +td.colLast, th.colLast { + font-size:13px; +} +td.colOne, th.colOne { + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width:25%; + vertical-align:top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor { + background-color:#FFFFFF; +} +.rowColor { + background-color:#EEEEEF; +} +/* +Content styles +*/ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} + +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} + +td.colLast div { + padding-top:0px; +} + + +td.colLast a { + padding-bottom:3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom:0px; +} + + + +/* +Spring +*/ + +pre.code { + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + overflow: auto; + padding: 10px; + margin: 4px 20px 2px 0px; +} + +pre.code code, pre.code code * { + font-size: 1em; +} + +pre.code code, pre.code code * { + padding: 0 !important; + margin: 0 !important; +} + diff --git a/docs/src/info/changelog.txt b/docs/src/info/changelog.txt new file mode 100644 index 000000000..2621608c5 --- /dev/null +++ b/docs/src/info/changelog.txt @@ -0,0 +1,4 @@ +SPRING STATEMACHINE CHANGELOG +================================== +http://projects.spring.io/spring-statemachine/ + diff --git a/docs/src/info/license.txt b/docs/src/info/license.txt new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/docs/src/info/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/docs/src/info/notice.txt b/docs/src/info/notice.txt new file mode 100644 index 000000000..069730285 --- /dev/null +++ b/docs/src/info/notice.txt @@ -0,0 +1,21 @@ +====================================================================== +== NOTICE file corresponding to section 4 d of the Apache License, == +== Version 2.0, for the Spring Framework distribution. == +====================================================================== + +This product includes software developed by +the Apache Software Foundation (http://www.apache.org). + +The end-user documentation included with a redistribution, if any, +must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + +Alternately, this acknowledgement may appear in the software itself, +if and wherever such third-party acknowledgements normally appear. + +The names "Spring", "Spring Framework" and "Spring for Apache Hadoop" +must not be used to endorse or promote products derived from this +software without prior written permission. For written permission, +please contact enquiries@springsource.com. diff --git a/docs/src/info/readme.txt b/docs/src/info/readme.txt new file mode 100644 index 000000000..4eb0c9110 --- /dev/null +++ b/docs/src/info/readme.txt @@ -0,0 +1,24 @@ +SPRING STATEMACHINE +------------------------ +http://projects.spring.io/spring-statemachine/ + +1. INTRODUCTION + +Spring Statemachine is a framework extension introducing state machine +concepts in a spring world. + +2. RELEASE NOTES + +This release comes with complete reference documentation. For further +details, consult the provided javadoc for specific packages and classes. + +3. DISTRIBUTION JAR FILES + +The Spring Statemachine jars files can be found in the 'dist' directory. + +4. GETTING STARTED + +Please see the reference documentation. +Additionally the blog at http://blog.spring.io as well +as sections of interest in the reference documentation. + diff --git a/docs/src/reference/asciidoc/Guardfile b/docs/src/reference/asciidoc/Guardfile new file mode 100644 index 000000000..349456c41 --- /dev/null +++ b/docs/src/reference/asciidoc/Guardfile @@ -0,0 +1,13 @@ +require 'asciidoctor' +require 'erb' + +guard 'shell' do + watch(/^.*\.adoc$/) {|m| + Asciidoctor.render_file(m[0], :to_dir => "build/", :safe => Asciidoctor::SafeMode::UNSAFE, :attributes=> {'idprefix' => '', 'idseparator' => '-', 'copycss' => '', 'icons' => 'font', 'source-highlighter' => 'prettify', 'sectanchors' => '', 'doctype' => 'book','toc2' => '', 'spring-hadoop-version' => '2.1.0.BUILD-SNAPSHOT','spring-version' => '4.1.3.RELEASE', 'revnumber' => '2.1.0.BUILD-SNAPSHOT', 'numbered'=>'' }) + } +end + +guard 'livereload' do + watch(%r{build/.+\.(css|js|html)$}) +end + diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc new file mode 100644 index 000000000..aefe0aeb7 --- /dev/null +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -0,0 +1,16 @@ +[[appendices]] += Appendices + +:numbered!: + +[appendix] +== State Machine Concepts +This appendix provides generic information about state machines. + +=== Glossary + +state machine:: +machine of sort. + +state:: +a stage in a state machine. diff --git a/docs/src/reference/asciidoc/index-docinfo.xml b/docs/src/reference/asciidoc/index-docinfo.xml new file mode 100644 index 000000000..3bfb5a017 --- /dev/null +++ b/docs/src/reference/asciidoc/index-docinfo.xml @@ -0,0 +1,22 @@ +{revnumber} +Spring Statemachine + + + Janne + Valkealahti + Pivotal + + + + + 2015 + Pivotal Software, Inc. + + + + Copies of this document may be made for your own use and for + distribution to others, provided that you do not charge any fee for such + copies and further provided that each copy contains this Copyright + Notice, whether distributed in print or electronically. + + diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc new file mode 100644 index 000000000..3dc1ac1af --- /dev/null +++ b/docs/src/reference/asciidoc/index.adoc @@ -0,0 +1,37 @@ +:hadoop-Configuration: http://hadoop.apache.org/docs/r{hadoop-version}/api/org/apache/hadoop/conf/Configuration.html +:hadoop-FileSystem: http://hadoop.apache.org/docs/r{hadoop-version}/api/org/apache/hadoop/fs/FileSystem.html +:hadoop-Security: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-common/SecureMode.html +:hadoop-Streaming: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/HadoopStreaming.html +:hadoop-DistributedCache: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/DistributedCacheDeploy.html +:hadoop-WordCount: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html#Example:_WordCount_v1.0 +:hadoop-FileSystemShell: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-common/FileSystemShell.html +:hadoop-WebHdfs: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-hdfs/WebHDFS.html +:hadoop-Hftp: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-hdfs/Hftp.html +:java-ClassLoader: http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html +:core-ApplicationContext: http://docs.spring.io/spring/docs/{spring-version}/javadoc-api/org/springframework/context/ApplicationContext.html +:core-beans-factory-placeholderconfigurer: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/beans.html#beans-factory-placeholderconfigurer +:core-beans-environment: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/beans.html#beans-environment +:core-ResourcePatternResolver: http://docs.spring.io/spring/docs/{spring-version}/javadoc-api/org/springframework/core/io/support/ResourcePatternResolver.html +:core-ref-util: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/xsd-config.html#xsd-config-body-schemas-util-properties +:core-aop-schema-advisors: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/aop.html#aop-schema-advisors +:core-dao: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/dao.html +:core-dao-exceptions: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/dao.html#dao-exceptions +:core-jdbc: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html +:core-jdbc-JdbcTemplate: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate +:shdp-FsShell: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/FsShell.html +:shdp-DistCp: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/DistCp.html +:shdp-HdfsResourceLoader: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/HdfsResourceLoader.html +:shdp-SpringDataStoreTextWriterConfigurerAdapter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/SpringDataStoreTextWriterConfigurerAdapter.html +:shdp-EnableDataStoreTextWriter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/EnableDataStoreTextWriter.html +:shdp-DataStoreTextWriterConfigurer: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/builders/DataStoreTextWriterConfigurer.html +:shdp-DataStoreWriter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/DataStoreWriter.html +:spring-data-hadoop-boot-jar: spring-data-hadoop-boot-{spring-hadoop-version}.jar + += Spring Statemachine - Reference Documentation + +include::preface.adoc[] +include::introduction.adoc[] + +[[springandhadoop]] +include::sm.adoc[] +include::appendix.adoc[] diff --git a/docs/src/reference/asciidoc/introduction.adoc b/docs/src/reference/asciidoc/introduction.adoc new file mode 100644 index 000000000..c6129e8ee --- /dev/null +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -0,0 +1,5 @@ +[[introduction]] += Introduction + +== Requirements +TBD diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc new file mode 100644 index 000000000..c2e716ca8 --- /dev/null +++ b/docs/src/reference/asciidoc/preface.adoc @@ -0,0 +1,4 @@ +[preface] +== Preface +Spring Statemachine is a framework for application developers to use +state machine concepts with Spring. diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc new file mode 100644 index 000000000..8c282753c --- /dev/null +++ b/docs/src/reference/asciidoc/sm.adoc @@ -0,0 +1,5 @@ +[[statemachine]] += State Machine +TBD + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..a7b026163 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +version=1.0.0.BUILD-SNAPSHOT +springVersion = 4.1.4.RELEASE +hamcrestVersion = 1.3 +junitVersion = 4.11 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c97a8bdb9088d370da7e88784a7a093b971aa23a GIT binary patch literal 51018 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..aec99730b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/publish-maven.gradle b/publish-maven.gradle new file mode 100644 index 000000000..a598ddbfc --- /dev/null +++ b/publish-maven.gradle @@ -0,0 +1,60 @@ +apply plugin: 'maven' + +ext.optionalDeps = [] +ext.providedDeps = [] + +ext.optional = { optionalDeps << it } +ext.provided = { providedDeps << it } + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // respect 'optional' and 'provided' dependencies + gradleProject.optionalDeps.each { dep -> + generatedPom.dependencies.findAll { it.artifactId == dep.name }*.optional = true + } + gradleProject.providedDeps.each { dep -> + generatedPom.dependencies.findAll { it.artifactId == dep.name }*.scope = 'provided' + } + + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == 'test' + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = 'https://github.com/spring-projects/spring-statemachine' + organization { + name = 'SpringSource' + url = 'http://spring.io/spring-statemachine' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'https://github.com/spring-projects/spring-statemachine' + connection = 'scm:git:git://github.com/spring-projects/spring-statemachine' + developerConnection = 'scm:git:git://github.com/spring-projects/spring-statemachine' + } + developers { + developer { + id = 'jvalkeal' + name = 'Janne Valkealahti' + email = 'janne.valkealahti@gmail.com' + } + } + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..0786e4e6f --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'spring-statemachine' + +include 'spring-statemachine-core' diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java new file mode 100644 index 000000000..df1b1aa6a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java @@ -0,0 +1,47 @@ +/* + * 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; + +import java.util.Collection; + +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.support.AbstractStateMachine; +import org.springframework.statemachine.transition.Transition; + +/** + * Specialisation of a {@link StateMachine} using enums + * as its {@link State} and event types. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EnumStateMachine, E extends Enum> extends AbstractStateMachine { + + /** + * Instantiates a new enum state machine. + * + * @param states the states + * @param transitions the transitions + * @param initialState the initial state + */ + public EnumStateMachine(Collection> states, Collection> transitions, + State initialState) { + super(states, transitions, initialState); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java new file mode 100644 index 000000000..8746c1a71 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java @@ -0,0 +1,37 @@ +/* + * 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; + +import java.util.Map; + +/** + * Extended states are used to supplement state machine with a variables. If + * extended state is used a complete condition of a state machine is a + * combination of its state an extended state variables. + * + * @author Janne Valkealahti + * + */ +public interface ExtendedState { + + /** + * Gets the extended state variables. + * + * @return the extended state variables + */ + Map getVariables(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java new file mode 100644 index 000000000..a07c77c9d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.Transition; + +/** + * {@code StateContext} is representing a current context used in + * {@link Transition}s, {@link Action}s and {@link Guard}s order to get access + * to event headers and {@link ExtendedState}. + * + * @author Janne Valkealahti + * + */ +public interface StateContext { + + /** + * Gets the event message headers. + * + * @return the event message headers + */ + MessageHeaders getMessageHeaders(); + + /** + * Gets the state machine extended state. + * + * @return the state machine extended state + */ + ExtendedState getExtendedState(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java new file mode 100644 index 000000000..154f427bc --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java @@ -0,0 +1,73 @@ +/* + * 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; + +import org.springframework.messaging.Message; +import org.springframework.statemachine.listener.StateMachineListener; + +/** + * {@code StateMachine} provides an APIs for generic finite state machine needed + * for basic operations like working with states, events and a lifecycle. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachine { + + /** + * Gets the initial state {@code S}. + * + * @return initial state + */ + S getInitialState(); + + /** + * Gets the current state {@code S}. + * + * @return current state + */ + S getState(); + + /** + * Start the state machine. + */ + void start(); + + /** + * Send an event {@code E} wrapped with a {@link Message} to the state + * machine. + * + * @param event the wrapped event to send + */ + void sendEvent(Message event); + + /** + * Send an event {@code E} to the state machine. + * + * @param event the event to send + */ + void sendEvent(E event); + + /** + * Adds the state listener. + * + * @param listener the listener + */ + void addStateListener(StateMachineListener listener); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java new file mode 100644 index 000000000..38c91c5bd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Various constants used in state machine lib. + * + * @author Janne Valkealahti + * + */ +public abstract class StateMachineSystemConstants { + + /** Default bean id for state machine. */ + public static final String DEFAULT_ID_STATEMACHINE = "stateMachine"; + + /** Default bean id for state machine factory. */ + public static final String DEFAULT_ID_STATEMACHINEFACTORY = "stateMachineFactory"; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java new file mode 100644 index 000000000..41a10ee3b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java @@ -0,0 +1,36 @@ +/* + * 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.action; + +import org.springframework.statemachine.StateContext; + +/** + * Generic strategy interface used by a state machine to respond + * events by executing an {@code Action} with a {@link StateContext}. + * + * @author Janne Valkealahti + * + */ +public interface Action { + + /** + * Execute action with a {@link StateContext}. + * + * @param context the state context + */ + void execute(StateContext context); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java new file mode 100644 index 000000000..52c2facf8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java @@ -0,0 +1,35 @@ +/* + * 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.annotation; + +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; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface OnTransition { + + String source() default ""; + + String target() default ""; + +} 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 new file mode 100644 index 000000000..4d2df8a94 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java @@ -0,0 +1,36 @@ +/* + * 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.annotation; + +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.stereotype.Component; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Component +public @interface WithStateMachine { + + String name() 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 new file mode 100644 index 000000000..1f9da39f5 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java @@ -0,0 +1,54 @@ +/* + * 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; + +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; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.StateMachineConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({StateMachineConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableStateMachine { + + /** + * The name of bean, or if plural, aliases for bean created based on this + * annotation. If left unspecified bean name will be autogenerated. + * + * @see Bean#name() + * @return the array if names or empty as default + */ + String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE}; + +} \ 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 new file mode 100644 index 000000000..6ebe5b947 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java @@ -0,0 +1,54 @@ +/* + * 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; + +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; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.StateMachineFactoryConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@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. + * + * @see Bean#name() + * @return the array if names or empty as default + */ + String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY}; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java new file mode 100644 index 000000000..75c03de15 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java @@ -0,0 +1,20 @@ +/* + * 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; + +public class EnumStateMachineConfigurerAdapter, E extends Enum> extends StateMachineConfigurerAdapter { + +} 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 new file mode 100644 index 000000000..2dc61697a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData; +import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.support.LifecycleObjectSupport; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.DefaultInternalTransition; +import org.springframework.statemachine.transition.Transition; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EnumStateMachineFactory, E extends Enum> extends LifecycleObjectSupport implements + StateMachineFactory, E> { + + private final StateMachineTransitions stateMachineTransitions; + + private final StateMachineStates stateMachineStates; + + /** + * Instantiates a new enum state machine factory. + * + * @param stateMachineTransitions the state machine transitions + * @param stateMachineStates the state machine states + */ + public EnumStateMachineFactory(StateMachineTransitions stateMachineTransitions, + StateMachineStates stateMachineStates) { + this.stateMachineTransitions = stateMachineTransitions; + this.stateMachineStates = stateMachineStates; + } + + @Override + public StateMachine, E> getStateMachine() { + return stateMachine(); + } + + public StateMachine, E> stateMachine() { + Map> stateMap = new HashMap>(); + for (StateData stateData : stateMachineStates.getStates()) { + stateMap.put(stateData.getState(), new EnumState(stateData.getState(), stateData.getDeferred(), + stateData.getEntryActions(), stateData.getExitActions())); + } + + Collection> transitions = new ArrayList>(); + for (TransitionData transitionData : stateMachineTransitions.getTransitions()) { + S source = transitionData.getSource(); + S target = transitionData.getTarget(); + E event = transitionData.getEvent(); + if (transitionData.getKind() == TransitionKind.EXTERNAL) { + DefaultExternalTransition transition = new DefaultExternalTransition(stateMap.get(source), + stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard()); + transitions.add(transition); + + } else if (transitionData.getKind() == TransitionKind.INTERNAL) { + DefaultInternalTransition transition = new DefaultInternalTransition(stateMap.get(source), + transitionData.getActions(), event, transitionData.getGuard()); + transitions.add(transition); + } + } + + EnumStateMachine machine = new EnumStateMachine(stateMap.values(), transitions, + stateMap.get(stateMachineStates.getInitialState())); + machine.afterPropertiesSet(); + if (getBeanFactory() != null) { + machine.setBeanFactory(getBeanFactory()); + } + machine.setAutoStartup(isAutoStartup()); + machine.start(); + return machine; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java new file mode 100644 index 000000000..38ef84c8b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java @@ -0,0 +1,39 @@ +/* + * 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; + +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; + +public class StateMachineConfig { + + public final StateMachineTransitions transitions; + + public final StateMachineStates states; + + public StateMachineConfig(StateMachineTransitions transitions, StateMachineStates states) { + this.transitions = transitions; + this.states = states; + } + + public StateMachineTransitions getTransitions() { + return transitions; + } + + public StateMachineStates getStates() { + return states; + } +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java new file mode 100644 index 000000000..79798acd8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java @@ -0,0 +1,73 @@ +/* + * 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; + +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; + +public class StateMachineConfigurerAdapter implements StateMachineConfigurer { + + private StateMachineTransitionBuilder transitionBuilder; + private StateMachineStateBuilder stateBuilder; + + @Override + public final void init(StateMachineConfigBuilder config) throws Exception { + config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); + config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); + } + + @Override + public void configure(StateMachineConfigBuilder config) throws Exception { + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + } + + @Override + public boolean isAssignable(AnnotationBuilder> builder) { + return builder instanceof StateMachineConfigBuilder; + } + + protected final StateMachineTransitionBuilder getStateMachineTransitionBuilder() throws Exception { + if (transitionBuilder != null) { + return transitionBuilder; + } + transitionBuilder = new StateMachineTransitionBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(transitionBuilder); + return transitionBuilder; + } + + protected final StateMachineStateBuilder getStateMachineStateBuilder() throws Exception { + if (stateBuilder != null) { + return stateBuilder; + } + stateBuilder = new StateMachineStateBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(stateBuilder); + return stateBuilder; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java new file mode 100644 index 000000000..6a3f2a97e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java @@ -0,0 +1,24 @@ +/* + * 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; + +import org.springframework.statemachine.StateMachine; + +public interface StateMachineFactory { + + StateMachine getStateMachine(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java new file mode 100644 index 000000000..df70a85b2 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java @@ -0,0 +1,36 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; + +public class StateMachineConfigBuilder + extends + AbstractConfiguredAnnotationBuilder, StateMachineConfigBuilder, StateMachineConfigBuilder> { + + @SuppressWarnings("unchecked") + @Override + protected StateMachineConfig performBuild() throws Exception { + StateMachineTransitionBuilder sharedObject = getSharedObject(StateMachineTransitionBuilder.class); + StateMachineStateBuilder sharedObject2 = getSharedObject(StateMachineStateBuilder.class); + StateMachineStates states = (StateMachineStates) sharedObject2.build(); + StateMachineTransitions transitions = (StateMachineTransitions) sharedObject.build(); + StateMachineConfig bean = new StateMachineConfig(transitions, states); + return bean; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java new file mode 100644 index 000000000..9658e47e1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java @@ -0,0 +1,28 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +public interface StateMachineConfigurer extends + AnnotationConfigurer, StateMachineConfigBuilder> { + + void configure(StateMachineStateConfigurer transitions) throws Exception; + + void configure(StateMachineTransitionConfigurer transitions) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java new file mode 100644 index 000000000..fcb7c48b1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java @@ -0,0 +1,66 @@ +/* + * 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.builders; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.statemachine.config.configurers.DefaultStateConfigurer; +import org.springframework.statemachine.config.configurers.StateConfigurer; + +public class StateMachineStateBuilder + extends AbstractConfiguredAnnotationBuilder, StateMachineStateConfigurer, StateMachineStateBuilder> + implements StateMachineStateConfigurer { + + private Collection> states = new ArrayList>(); + private S initialState; + + public StateMachineStateBuilder() { + super(); + } + + public StateMachineStateBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + public StateMachineStateBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + protected StateMachineStates performBuild() throws Exception { + StateMachineStates bean = new StateMachineStates(initialState, states); + return bean; + } + + @Override + public StateConfigurer withStates() throws Exception { + return apply(new DefaultStateConfigurer()); + } + + public void add(Collection> states) { + this.states.addAll(states); + } + + public void setInitialState(S state) { + this.initialState = state; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java new file mode 100644 index 000000000..c2b053d16 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java @@ -0,0 +1,24 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.configurers.StateConfigurer; + +public interface StateMachineStateConfigurer { + + StateConfigurer withStates() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java new file mode 100644 index 000000000..aff6a0339 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java @@ -0,0 +1,74 @@ +/* + * 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.builders; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public class StateMachineStates { + + private Collection> states; + + private final S initialState; + + public StateMachineStates(S initialState, Collection> states) { + this.states = states; + this.initialState = initialState; + } + + public Collection> getStates() { + return states; + } + + public S getInitialState() { + return initialState; + } + + public static class StateData { + private S state; + private Collection deferred; + private Collection entryActions; + private Collection exitActions; + public StateData(S state, Collection deferred) { + this(state, deferred, null, null); + } + public StateData(S state, Collection deferred, Collection entryActions, Collection exitActions) { + this.state = state; + this.deferred = deferred; + this.entryActions = entryActions; + this.exitActions = exitActions; + } + public StateData(S state, E[] deferred) { + this(state, deferred != null ? Arrays.asList(deferred) : new ArrayList()); + } + public S getState() { + return state; + } + public Collection getDeferred() { + return deferred; + } + public Collection getEntryActions() { + return entryActions; + } + public Collection getExitActions() { + return exitActions; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java new file mode 100644 index 000000000..ec59f9fb7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -0,0 +1,72 @@ +/* + * 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.builders; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.TransitionKind; + +public class StateMachineTransitionBuilder + extends + AbstractConfiguredAnnotationBuilder, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements StateMachineTransitionConfigurer { + + private Collection> transitionData = new ArrayList>(); + + public StateMachineTransitionBuilder() { + super(); + } + + public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + protected StateMachineTransitions performBuild() throws Exception { + StateMachineTransitions bean = new StateMachineTransitions(transitionData); + return bean; + } + + @Override + public ExternalTransitionConfigurer withExternal() throws Exception { + return apply(new DefaultExternalTransitionConfigurer()); + } + + @Override + public InternalTransitionConfigurer withInternal() throws Exception { + return apply(new DefaultInternalTransitionConfigurer()); + } + + public void add(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + transitionData.add(new TransitionData(source, target, event, actions, guard, kind)); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java new file mode 100644 index 000000000..a1cc39a4e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java @@ -0,0 +1,27 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; + +public interface StateMachineTransitionConfigurer { + + ExternalTransitionConfigurer withExternal() throws Exception; + + InternalTransitionConfigurer withInternal() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java new file mode 100644 index 000000000..f71a8a157 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java @@ -0,0 +1,71 @@ +/* + * 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.builders; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.TransitionKind; + +public class StateMachineTransitions { + + private Collection> transitions; + + public StateMachineTransitions(Collection> transitions) { + this.transitions = transitions; + } + + public Collection> getTransitions() { + return transitions; + } + + public static class TransitionData { + S source; + S target; + E event; + Collection actions; + Guard guard; + TransitionKind kind; + public TransitionData(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + this.source = source; + this.target = target; + this.event = event; + this.actions = actions; + this.guard = guard; + this.kind = kind; + } + public S getSource() { + return source; + } + public S getTarget() { + return target; + } + public E getEvent() { + return event; + } + public Collection getActions() { + return actions; + } + public Guard getGuard() { + return guard; + } + public TransitionKind getKind() { + return kind; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java new file mode 100644 index 000000000..039e92412 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java @@ -0,0 +1,67 @@ +/* + * 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.common.annotation; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A base {@link AnnotationBuilder} that ensures the object being built is only + * built one time. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param the type of Object that is being built + */ +public abstract class AbstractAnnotationBuilder implements AnnotationBuilder { + + /** Flag tracking build */ + private AtomicBoolean building = new AtomicBoolean(); + + /** Built object is stored here */ + private O object; + + @Override + public final O build() throws Exception { + if(building.compareAndSet(false, true)) { + object = doBuild(); + return object; + } + throw new IllegalStateException("This object has already been built"); + } + + /** + * Gets the object that was built. If it has not been built yet an Exception + * is thrown. + * + * @return the Object that was built + */ + public final O getObject() { + if(!building.get()) { + throw new IllegalStateException("This object has not been built"); + } + return object; + } + + /** + * Subclasses should implement this to perform the build. + * + * @return the object that should be returned by {@link #build()}. + * @throws Exception if an error occurs + */ + protected abstract O doBuild() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java new file mode 100644 index 000000000..546f2ea77 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java @@ -0,0 +1,96 @@ +/* + * 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.common.annotation; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.ClassUtils; + +/** + * Base implementation of @{@link Configuration} class. + * + * @author Janne Valkealahti + * + * @param The object that used builder returns + * @param The type of the builder + */ +public abstract class AbstractAnnotationConfiguration, O> + implements ImportAware, BeanClassLoaderAware { + + private List> configurers; + + private ClassLoader beanClassLoader; + + private AnnotationAttributes annotationAttributes; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + beanClassLoader = classLoader; + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + Map enableConfigurationAttrMap = + importMetadata.getAnnotationAttributes(EnableAnnotationConfiguration.class.getName()); + AnnotationAttributes enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap); + if(enableConfigurationAttrs == null) { + // search parent classes + Class currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader); + for(Class classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) { + EnableAnnotationConfiguration enableConfigurationAnnotation = + AnnotationUtils.findAnnotation(classToInspect, EnableAnnotationConfiguration.class); + if(enableConfigurationAnnotation == null) { + continue; + } + enableConfigurationAttrMap = AnnotationUtils + .getAnnotationAttributes(enableConfigurationAnnotation); + enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap); + } + } + annotationAttributes = enableConfigurationAttrs; + } + + /** + * Sets the configurers. + * + * @param configurers the configurers + * @throws Exception the exception + */ + @Autowired(required=false) + public void setConfigurers(List> configurers) throws Exception { + this.configurers = configurers; + onConfigurers(configurers); + } + + public AnnotationAttributes getAnnotationAttributes() { + return annotationAttributes; + } + + public List> getConfigurers() { + return configurers; + } + + protected abstract void onConfigurers(List> configurers) throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java new file mode 100644 index 000000000..7e2733592 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java @@ -0,0 +1,531 @@ +/* + * 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.common.annotation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; + +/** + * A base {@link AnnotationBuilder} that allows {@link AnnotationConfigurer}s to be + * applied to it. This makes modifying the {@link AnnotationBuilder} a strategy + * that can be customised and broken up into a number of + * {@link AnnotationConfigurer} objects that have more specific goals than that + * of the {@link AnnotationBuilder}. + *

+ * + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The object that this builder returns + * @param The interface of type B + * @param The type of this builder (that is returned by the base class) + */ +public abstract class AbstractConfiguredAnnotationBuilder> + extends AbstractAnnotationBuilder { + + private final static Log log = LogFactory.getLog(AbstractConfiguredAnnotationBuilder.class); + + /** Configurers which are added to this builder before the configure step */ + private final LinkedHashMap>, List>> mainConfigurers = + new LinkedHashMap>, List>>(); + + /** Configurers which are added to this builder during the configuration phase */ + private final LinkedHashMap>, List>> postConfigurers = + new LinkedHashMap>, List>>(); + + private final Map, Object> sharedObjects = new HashMap, Object>(); + + private final boolean allowConfigurersOfSameType; + + /** Current state of this builder */ + private BuildState buildState = BuildState.UNBUILT; + + private ObjectPostProcessor objectPostProcessor; + + /** + * Instantiates a new annotation builder. + */ + protected AbstractConfiguredAnnotationBuilder() { + this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR); + } + + /** + * Instantiates a new annotation builder. + * + * @param objectPostProcessor the object post processor + */ + protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor objectPostProcessor) { + this(objectPostProcessor,false); + } + + /** + * Instantiates a new annotation builder. + * + * @param objectPostProcessor the object post processor + * @param allowConfigurersOfSameType the allow configurers of same type + */ + protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor objectPostProcessor, boolean allowConfigurersOfSameType) { + Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null"); + this.objectPostProcessor = objectPostProcessor; + this.allowConfigurersOfSameType = allowConfigurersOfSameType; + } + + @Override + protected final O doBuild() throws Exception { + synchronized (mainConfigurers) { + buildState = BuildState.INITIALIZING_MAINS; + beforeInit(); + initMainConfigurers(); + + buildState = BuildState.CONFIGURING_MAINS; + beforeConfigureMains(); + configureMainConfigurers(); + + buildState = BuildState.CONFIGURING_POSTS; + beforeConfigurePosts(); + configurePostConfigurers(); + + buildState = BuildState.BUILDING; + O result = performBuild(); + + buildState = BuildState.BUILT; + return result; + } + } + + /** + * Similar to {@link #build()} and {@link #getObject()} but checks the state + * to determine if {@link #build()} needs to be called first. + * + * @return the result of {@link #build()} or {@link #getObject()}. If an + * error occurs while building, returns null. + */ + public O getOrBuild() { + if (isUnbuilt()) { + try { + return build(); + } catch (Exception e) { + log.error("Failed to perform build. Returning null", e); + return null; + } + } else { + return getObject(); + } + } + + /** + * Applies a {@link AnnotationConfigurerAdapter} to this + * {@link AnnotationBuilder} and invokes + * {@link AnnotationConfigurerAdapter#setBuilder(AnnotationBuilder)}. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + @SuppressWarnings("unchecked") + public > C apply(C configurer) throws Exception { + add(configurer); + configurer.addObjectPostProcessor(objectPostProcessor); + configurer.setBuilder((B) this); + return configurer; + } + + /** + * Similar to {@link #apply(AnnotationConfigurer)} but checks the state + * to determine if {@link #apply(AnnotationConfigurer)} needs to be called first. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + @SuppressWarnings("unchecked") + public > C getOrApply(C configurer) throws Exception { + C existing = (C) getConfigurer(configurer.getClass()); + if (existing != null) { + return existing; + } + return apply(configurer); + } + + /** + * Applies a {@link AnnotationConfigurer} to this {@link AnnotationBuilder} + * overriding any {@link AnnotationConfigurer} of the exact same class. Note + * that object hierarchies are not considered. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + public > C apply(C configurer) throws Exception { + add(configurer); + return configurer; + } + + /** + * Sets an object that is shared by multiple {@link AnnotationConfigurer}. + * + * @param sharedType the Class to key the shared object by. + * @param object the Object to store + * @param type of share object + */ + @SuppressWarnings("unchecked") + public void setSharedObject(Class sharedType, C object) { + this.sharedObjects.put((Class) sharedType, object); + } + + /** + * Gets a shared Object. Note that object hierarchies are not considered. + * + * @param sharedType the type of the shared Object + * @param type of share object + * @return the shared Object or null if it is not found + */ + @SuppressWarnings("unchecked") + public C getSharedObject(Class sharedType) { + return (C) this.sharedObjects.get(sharedType); + } + + /** + * Gets the shared objects. + * + * @return Shared objects + */ + public Map, Object> getSharedObjects() { + return Collections.unmodifiableMap(this.sharedObjects); + } + + /** + * Adds {@link AnnotationConfigurer} ensuring that it is allowed and + * invoking {@link AnnotationConfigurer#init(AnnotationBuilder)} immediately + * if necessary. + * + * @param configurer the {@link AnnotationConfigurer} to add + * @param type of AnnotationConfigurer + * @throws Exception if an error occurs + */ + @SuppressWarnings("unchecked") + private > void add(C configurer) throws Exception { + Assert.notNull(configurer, "configurer cannot be null"); + + Class> clazz = + (Class>) configurer.getClass(); + + if (!buildState.isConfigured()) { + synchronized (mainConfigurers) { + List> configs = allowConfigurersOfSameType ? this.mainConfigurers.get(clazz) : null; + if (configs == null) { + configs = new ArrayList>(1); + } + configs.add(configurer); + this.mainConfigurers.put(clazz, configs); + if (buildState.isInitializing()) { + configurer.init((B) this); + } + } + } else { + synchronized (postConfigurers) { + List> configs = allowConfigurersOfSameType ? this.postConfigurers.get(clazz) : null; + if (configs == null) { + configs = new ArrayList>(1); + } + configs.add(configurer); + this.postConfigurers.put(clazz, configs); + configurer.init((B) this); + } + } + } + + /** + * Gets all the {@link AnnotationConfigurer} instances by its class name or an + * empty List if not found. Note that object hierarchies are not considered. + * + * @param clazz the {@link AnnotationConfigurer} class to look for + * @param type of AnnotationConfigurer + * @return All configurers + */ + @SuppressWarnings("unchecked") + public > List getConfigurers(Class clazz) { + List configs = (List) this.mainConfigurers.get(clazz); + if (configs == null) { + return new ArrayList(); + } + return new ArrayList(configs); + } + + /** + * Removes all the {@link AnnotationConfigurer} instances by its class name or an + * empty List if not found. Note that object hierarchies are not considered. + * + * @param clazz the {@link AnnotationConfigurer} class to look for + * @param type of AnnotationConfigurer + * @return Empty list of configurers + */ + @SuppressWarnings("unchecked") + public > List removeConfigurers(Class clazz) { + List configs = (List) this.mainConfigurers.remove(clazz); + if (configs == null) { + return new ArrayList(); + } + return new ArrayList(configs); + } + + /** + * Gets the {@link AnnotationConfigurer} by its class name or + * null if not found. Note that object hierarchies are not + * considered. + * + * @param clazz the configurer class type + * @param type of AnnotationConfigurer + * @return Matched configurers + */ + @SuppressWarnings("unchecked") + public > C getConfigurer(Class clazz) { + List> configs = this.mainConfigurers.get(clazz); + if (configs == null) { + return null; + } + if (configs.size() != 1) { + throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs); + } + return (C) configs.get(0); + } + + /** + * Removes and returns the {@link AnnotationConfigurer} by its class name or + * null if not found. Note that object hierarchies are not + * considered. + * + * @param clazz the configurer class type + * @param type of AnnotationConfigurer + * @return Matched configurers + */ + @SuppressWarnings("unchecked") + public > C removeConfigurer(Class clazz) { + List> configs = this.mainConfigurers.remove(clazz); + if (configs == null) { + return null; + } + if (configs.size() != 1) { + throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs); + } + return (C) configs.get(0); + } + + /** + * Specifies the {@link ObjectPostProcessor} to use. + * @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null + * @return the {@link AnnotationBuilder} for further customizations + */ + @SuppressWarnings("unchecked") + public O objectPostProcessor(ObjectPostProcessor objectPostProcessor) { + Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null"); + this.objectPostProcessor = objectPostProcessor; + return (O) this; + } + + /** + * Performs post processing of an object. The default is to delegate to the + * {@link ObjectPostProcessor}. + * + * @param object the Object to post process + * @param

type of processed object + * @return the possibly modified Object to use + */ + protected

P postProcess(P object) { + return (P) this.objectPostProcessor.postProcess(object); + } + + /** + * Invoked prior to invoking each + * {@link AnnotationConfigurer#init(AnnotationBuilder)} method. Subclasses may + * override this method to hook into the lifecycle without using a + * {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeInit() throws Exception { + } + + /** + * Invoked prior to invoking each main + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} method. + * Subclasses may override this method to hook into the lifecycle without + * using a {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeConfigureMains() throws Exception { + } + + /** + * Invoked prior to invoking each post + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} method. + * Subclasses may override this method to hook into the lifecycle without + * using a {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeConfigurePosts() throws Exception { + } + + /** + * Subclasses must implement this method to build the object that is being returned. + * + * @return Object build by this builder + * @throws Exception if error occurred + */ + protected abstract O performBuild() throws Exception; + + @SuppressWarnings("unchecked") + private void initMainConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getMainConfigurers()) { + configurer.init((B) this); + } + } + + @SuppressWarnings("unchecked") + private void configureMainConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getMainConfigurers()) { + configurer.configure((B) this); + } + } + + @SuppressWarnings("unchecked") + private void configurePostConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getPostConfigurers()) { + configurer.configure((B) this); + } + } + + /** + * Gets all configurers. + * + * @return the configurers + */ + private Collection> getMainConfigurers() { + List> result = new ArrayList>(); + for (List> configs : this.mainConfigurers.values()) { + result.addAll(configs); + } + return result; + } + + private Collection> getPostConfigurers() { + List> result = new ArrayList>(); + for (List> configs : this.postConfigurers.values()) { + result.addAll(configs); + } + return result; + } + + /** + * Determines if the object is unbuilt. + * + * @return true, if unbuilt else false + */ + private boolean isUnbuilt() { + synchronized (mainConfigurers) { + return buildState == BuildState.UNBUILT; + } + } + + /** + * The build state for the application + */ + private static enum BuildState { + + /** + * This is the state before the {@link AnnotationBuilder#build()} is invoked + */ + UNBUILT(0), + + /** + * The state from when {@link AnnotationBuilder#build()} is first invoked until + * all the {@link AnnotationConfigurer#init(AnnotationBuilder)} methods have + * been invoked. + */ + INITIALIZING_MAINS(1), + + /** + * The state from after all main + * {@link AnnotationConfigurer#init(AnnotationBuilder)} + * have been invoked until after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * methods have been invoked. + */ + CONFIGURING_MAINS(2), + + /** + * The state from after all post + * {@link AnnotationConfigurer#init(AnnotationBuilder)} + * have been invoked until after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * methods have been invoked. + */ + CONFIGURING_POSTS(3), + + /** + * From the point after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * have completed to just after + * {@link AbstractConfiguredAnnotationBuilder#performBuild()}. + */ + BUILDING(4), + + /** + * After the object has been completely built. + */ + BUILT(5); + + private final int order; + + BuildState(int order) { + this.order = order; + } + + /** + * Checks if is initializing. + * + * @return true, if is initializing + */ + public boolean isInitializing() { + return INITIALIZING_MAINS.order == order; + } + + /** + * Determines if the state is CONFIGURING or later + * + * @return true, if configured + */ + public boolean isConfigured() { + return order >= CONFIGURING_MAINS.order; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java new file mode 100644 index 000000000..0474946c6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java @@ -0,0 +1,188 @@ +/* + * 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.common.annotation; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.DefaultBeanNameGenerator; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link Configuration} which works on a bean definition level + * relying on {@link ImportBeanDefinitionRegistrar} phase to register beans. + * + * @author Janne Valkealahti + * + * @param The object that used builder returns + * @param The type of the builder + */ +public abstract class AbstractImportingAnnotationConfiguration, O> implements + ImportBeanDefinitionRegistrar, BeanFactoryAware, EnvironmentAware { + + private BeanFactory beanFactory; + + private Environment environment; + + private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + Class annotationType = getAnnotation(); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes( + annotationType.getName(), false)); + String[] names = attributes.getStringArray("name"); + + BeanDefinition beanDefinition; + try { + beanDefinition = buildBeanDefinition(); + } catch (Exception e) { + throw new RuntimeException("Error with onConfigurers", e); + } + + if (ObjectUtils.isEmpty(names)) { + // ok, name(s) not given, generate one + names = new String[] { beanNameGenerator.generateBeanName(beanDefinition, registry) }; + } + + registry.registerBeanDefinition(names[0], beanDefinition); + if (names.length > 1) { + for (int i = 1; i < names.length; i++) { + registry.registerAlias(names[0], names[i]); + } + } + } + + protected abstract static class BeanDelegatingFactoryBean, O> + implements FactoryBean, InitializingBean { + + private final B builder; + + private O object; + + private List> configurers; + + public BeanDelegatingFactoryBean(B builder){ + this.builder = builder; + } + + @Override + public abstract Class getObjectType(); + + @Override + public O getObject() throws Exception { + return object; + } + + @Override + public boolean isSingleton() { + return true; + } + +// @Override +// public void afterPropertiesSet() throws Exception { +// for (AnnotationConfigurer configurer : configurers) { +// if (configurer.isAssignable(builder)) { +// // we need builder.apply(configurer); +//// builder. +// } +// } +// // should be getOrBuild??? +// object = builder.build(); +// } + + @Autowired(required = false) + public void setConfigurers(List> configurers) { + this.configurers = configurers; + } + + public B getBuilder() { + return builder; + } + + public List> getConfigurers() { + return configurers; + } + + protected void setObject(O object) { + this.object = object; + } + + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, + "beanFactory be of type ListableBeanFactory but was " + beanFactory); + this.beanFactory = beanFactory; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + /** + * Called to get a bean definition to register. + * + * @return the bean definition to register + * @throws Exception if error occurred + */ + protected abstract BeanDefinition buildBeanDefinition() throws Exception; + + /** + * Gets the annotation specific for this configurer. + * + * @return the annotation + */ + protected abstract Class getAnnotation(); + + /** + * Gets the bean factory. + * + * @return the bean factory + */ + protected BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Gets the environment. + * + * @return the environment + */ + protected Environment getEnvironment() { + return environment; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java new file mode 100644 index 000000000..b8ccceea4 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java @@ -0,0 +1,36 @@ +/* + * 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.common.annotation; + +/** + * Interface for building an {@link Object}. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The type of the Object being built + */ +public interface AnnotationBuilder { + + /** + * Builds the object and returns it or null. + * + * @return the Object to be built or null if the implementation allows it. + * @throws Exception if an error occurred when building the Object + */ + O build() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java new file mode 100644 index 000000000..befdafc3a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java @@ -0,0 +1,58 @@ +/* + * 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.common.annotation; + +/** + * Allows for configuring an {@link AnnotationBuilder}. All + * {@link AnnotationConfigurer}s first have their {@link #init(AnnotationBuilder)} + * method invoked. After all {@link #init(AnnotationBuilder)} methods have been + * invoked, each {@link #configure(AnnotationBuilder)} method is invoked. + * + * @see AbstractConfiguredAnnotationBuilder + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The object being built by the {@link AnnotationBuilder} B + * @param The {@link AnnotationBuilder} that builds objects of type O. This is + * also the {@link AnnotationBuilder} that is being configured. + */ +public interface AnnotationConfigurer> { + + /** + * Initialise the {@link AnnotationBuilder}. Here only shared state should be + * created and modified, but not properties on the {@link AnnotationBuilder} + * used for building the object. This ensures that the + * {@link #configure(AnnotationBuilder)} method uses the correct shared + * objects when building. + * + * @param builder the builder + * @throws Exception if error occurred + */ + void init(B builder) throws Exception; + + /** + * Configure the {@link AnnotationBuilder} by setting the necessary properties + * on the {@link AnnotationBuilder}. + * + * @param builder the builder + * @throws Exception if error occurred + */ + void configure(B builder) throws Exception; + + boolean isAssignable(AnnotationBuilder builder); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java new file mode 100644 index 000000000..227933d3c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java @@ -0,0 +1,133 @@ +/* + * 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.common.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.GenericTypeResolver; + +/** + * A base class for {@link AnnotationConfigurer} that allows subclasses to only + * implement the methods they are interested in. It also provides a mechanism + * for using the {@link AnnotationConfigurer} and when done gaining access to the + * {@link AnnotationBuilder} that is being configured. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The interface of type B + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + */ +public abstract class AnnotationConfigurerAdapter> + implements AnnotationConfigurer { + + private B builder; + + private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor(); + + @Override + public void init(B builder) throws Exception {} + + @Override + public void configure(B builder) throws Exception {} + + /** + * Return the {@link AnnotationBuilder} when done using the + * {@link AnnotationConfigurer}. This is useful for method chaining. + * + * @return the {@link AnnotationBuilder} + */ + @SuppressWarnings("unchecked") + public I and() { + // we're either casting to itself or its interface + return (I) getBuilder(); + } + + /** + * Gets the {@link AnnotationBuilder}. Cannot be null. + * + * @return the {@link AnnotationBuilder} + * @throws IllegalStateException if AnnotationBuilder is null + */ + protected final B getBuilder() { + if(builder == null) { + throw new IllegalStateException("annotationBuilder cannot be null"); + } + return builder; + } + + /** + * Adds an {@link ObjectPostProcessor} to be used for this adapter. The + * default implementation does nothing to the object. + * + * @param objectPostProcessor the {@link ObjectPostProcessor} to use + */ + public void addObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor); + } + + /** + * Sets the {@link AnnotationBuilder} to be used. This is automatically set + * when using + * {@link AbstractConfiguredAnnotationBuilder#apply(AnnotationConfigurerAdapter)} + * + * @param builder the {@link AnnotationBuilder} to set + */ + public void setBuilder(B builder) { + this.builder = builder; + } + + /** + * An {@link ObjectPostProcessor} that delegates work to numerous + * {@link ObjectPostProcessor} implementations. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static final class CompositeObjectPostProcessor implements ObjectPostProcessor { + + private List> postProcessors = new ArrayList>(); + + @Override + public Object postProcess(Object object) { + for(ObjectPostProcessor opp : postProcessors) { + Class oppClass = opp.getClass(); + Class oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class); + if(oppType == null || oppType.isAssignableFrom(object.getClass())) { + object = opp.postProcess(object); + } + } + return object; + } + + /** + * Adds an {@link ObjectPostProcessor} to use + * + * @param objectPostProcessor the {@link ObjectPostProcessor} to add + * @return true if the {@link ObjectPostProcessor} was added, else false + */ + private boolean addObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + return this.postProcessors.add(objectPostProcessor); + } + + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return true; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java new file mode 100644 index 000000000..4c4c59c95 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java @@ -0,0 +1,35 @@ +/* + * 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.common.annotation; + +/** + * Interface for wrapping a return type from {@link AnnotationConfigurer} + * into {@link AnnotationBuilder}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface AnnotationConfigurerBuilder { + + /** + * Get a parent {@link AnnotationBuilder} working + * with a {@link AnnotationConfigurer}. + * @return The parent {@link AnnotationBuilder} + */ + I and(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java new file mode 100644 index 000000000..e6e015fe8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java @@ -0,0 +1,36 @@ +/* + * 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.common.annotation; + +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; + +/** + * Base annotation used in JavaConfig order to enable + * some base functionality. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface EnableAnnotationConfiguration { + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java new file mode 100644 index 000000000..a09a18026 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java @@ -0,0 +1,53 @@ +/* + * 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.common.annotation; + +import org.springframework.beans.factory.Aware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Allows initialization of Objects. Typically this is used to call the + * {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and + * ensure that {@link DisposableBean#destroy()} has been invoked. + * + * @param the bound of the types of Objects this {@link ObjectPostProcessor} supports. + * + * @author Rob Winch + */ +public interface ObjectPostProcessor { + + /** + * Initialize the object possibly returning a modified instance that should + * be used instead. + * + * @param object the object to initialize + * @param the type of a processed object + * @return the initialized version of the object + */ + O postProcess(O object); + + /** + * A do nothing implementation of the {@link ObjectPostProcessor} + */ + ObjectPostProcessor QUIESCENT_POSTPROCESSOR = new ObjectPostProcessor() { + @Override + public T postProcess(T object) { + return object; + } + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java new file mode 100644 index 000000000..e09a39554 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java @@ -0,0 +1,118 @@ +/* + * 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.common.annotation.configuration; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.util.Assert; + +/** + * Post processor handling lifecycle methods of + * POJOs created from builder/configurer. + * + * @author Janne Valkealahti + * + */ +final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor, DisposableBean, SmartLifecycle { + + private final static Log log = LogFactory.getLog(AutowireBeanFactoryObjectPostProcessor.class); + + private final AutowireCapableBeanFactory autowireBeanFactory; + private final List disposableBeans = new ArrayList(); + private final List lifecycleBeans = new ArrayList(); + + private boolean running; + + /** + * Instantiates a new autowire bean factory object post processor. + * + * @param autowireBeanFactory the autowire bean factory + */ + public AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) { + Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null"); + this.autowireBeanFactory = autowireBeanFactory; + } + + @SuppressWarnings("unchecked") + @Override + public T postProcess(T object) { + T result = (T) autowireBeanFactory.initializeBean(object, null); + if(result instanceof DisposableBean) { + disposableBeans.add((DisposableBean) result); + } + if(result instanceof Lifecycle) { + lifecycleBeans.add((Lifecycle) result); + } + return result; + } + + @Override + public void destroy() throws Exception { + for(DisposableBean disposable : disposableBeans) { + try { + disposable.destroy(); + } catch(Exception error) { + log.error(error); + } + } + } + + @Override + public void start() { + running = true; + for (Lifecycle bean : lifecycleBeans) { + bean.start(); + } + } + + @Override + public void stop() { + for (Lifecycle bean : lifecycleBeans) { + bean.stop(); + } + running = false; + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public int getPhase() { + return 0; + } + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java new file mode 100644 index 000000000..20a88a397 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java @@ -0,0 +1,42 @@ +/* + * 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.common.annotation.configuration; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; + +/** + * Spring {@link Configuration} that exports the default + * {@link ObjectPostProcessor}. This class is not intended to be imported + * manually rather it is imported automatically when using {@link EnableAnnotationConfiguration} + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @see EnableAnnotationConfiguration + */ +@Configuration +public class ObjectPostProcessorConfiguration { + + @Bean + public ObjectPostProcessor objectPostProcessor(AutowireCapableBeanFactory beanFactory) { + return new AutowireBeanFactoryObjectPostProcessor(beanFactory); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java new file mode 100644 index 000000000..4dc7a158e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java @@ -0,0 +1,105 @@ +/* + * 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.common.annotation.configurers; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +/** + * {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer AnnotationConfigurer} + * which knows how to handle configuring a {@link Properties}. + * + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The type of interface or builder itself returned by the configurer + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + */ +public class DefaultPropertiesConfigurer> + extends AnnotationConfigurerAdapter implements PropertiesConfigurer { + + private Properties properties = new Properties(); + + /** + * Adds a {@link Properties} to this builder. + * + * @param properties the properties + * @return the {@link PropertiesConfigurer} for chaining + */ + @Override + public PropertiesConfigurer properties(Properties properties) { + if (properties != null) { + this.properties.putAll(properties); + } + return this; + } + + @Override + public PropertiesConfigurer properties(Map properties) { + Properties props = new Properties(); + if (properties != null) { + props.putAll(properties); + } + return properties(props); + } + + /** + * Adds a property to this builder. + * + * @param key the key + * @param value the value + * @return the {@link PropertiesConfigurer} for chaining + */ + @Override + public PropertiesConfigurer property(String key, String value) { + properties.put(key, value); + return this; + } + + /** + * Gets the {@link Properties} configured for this builder. + * + * @return the properties + */ + public Properties getProperties() { + return properties; + } + + @Override + public void configure(B builder) throws Exception { + if (!configureProperties(builder, properties)) { + if (builder instanceof PropertiesConfigurerAware) { + ((PropertiesConfigurerAware)builder).configureProperties(properties); + } + } + } + + /** + * Configure properties. If this implementation is extended, + * custom configure handling can be handled here. + * + * @param builder the builder + * @param properties the properties + * @return true, if properties configure is handled + */ + protected boolean configureProperties(B builder, Properties properties){ + return false; + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java new file mode 100644 index 000000000..1ded2f009 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java @@ -0,0 +1,126 @@ +/* + * 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.common.annotation.configurers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +/** + * {@link AnnotationConfigurer} which knows how to handle + * configuring a {@link Resource}s. + * + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + * @param The type of an interface of B + */ +public class DefaultResourceConfigurer> + extends AnnotationConfigurerAdapter implements ResourceConfigurer { + + private Set resources = new HashSet(); + private final DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + + @Override + public void configure(B builder) throws Exception { + if (!configureResources(builder, resources)) { + if (builder instanceof ResourceConfigurerAware) { + ((ResourceConfigurerAware)builder).configureResources(resources); + } + } + } + + /** + * Adds a {@link Set} of {@link Resource}s to this builder. + * + * @param resources the resources + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resources(Set resources) { + this.resources.addAll(resources); + return this; + } + + /** + * Adds a {@link Resource} to this builder. + * + * @param resource the resource + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resource(Resource resource) { + resources.add(resource); + return this; + } + + /** + * Adds a {@link Resource} to this builder. + * + * @param resource the resource + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resource(String resource) { + resources.add(resourceLoader.getResource(resource)); + return this; + } + + /** + * Adds a {@link Resource}s to this builder. + * + * @param resources the resources + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resources(List resources) { + if (resources != null) { + for (String resource : resources) { + resource(resource); + } + } + return this; + } + + /** + * Gets the {@link Resource}s configured for this builder. + * + * @return the resources + */ + public Set getResources() { + return resources; + } + + /** + * Configure resources. If this implementation is extended, + * custom configure handling can be handled here. + * + * @param builder the builder + * @param resources the resources + * @return true, if resources configure is handled + */ + protected boolean configureResources(B builder, Set resources){ + return false; + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java new file mode 100644 index 000000000..e8ed6e796 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java @@ -0,0 +1,40 @@ +/* + * 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.common.annotation.configurers; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +/** + * Interface for {@link DefaultPropertiesConfigurer} which act + * as intermediate gatekeeper between a user and + * an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface PropertiesConfigurer extends AnnotationConfigurerBuilder { + + PropertiesConfigurer properties(Properties properties); + + PropertiesConfigurer properties(Map properties); + + PropertiesConfigurer property(String key, String value); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java new file mode 100644 index 000000000..11bfeaa78 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java @@ -0,0 +1,36 @@ +/* + * 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.common.annotation.configurers; + +import java.util.Properties; + +/** + * Interface for {@link org.springframework.statemachine.config.common.annotation.AnnotationBuilder AnnotationBuilder} + * which wants to be aware of {@link Properties} configured by {@link DefaultPropertiesConfigurer}. + * + * @author Janne Valkealahti + * + */ +public interface PropertiesConfigurerAware { + + /** + * Configure {@link Properties}. + * + * @param properties the properties + */ + void configureProperties(Properties properties); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java new file mode 100644 index 000000000..3bf28be34 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java @@ -0,0 +1,43 @@ +/* + * 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.common.annotation.configurers; + +import java.util.List; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +/** + * Interface for {@link DefaultResourceConfigurer} which act + * as intermediate gatekeeper between a user and + * an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface ResourceConfigurer extends AnnotationConfigurerBuilder { + + ResourceConfigurer resources(Set resources); + + ResourceConfigurer resources(List resources); + + ResourceConfigurer resource(Resource resource); + + ResourceConfigurer resource(String resource); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java new file mode 100644 index 000000000..b52b196c7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java @@ -0,0 +1,39 @@ +/* + * 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.common.annotation.configurers; + +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Interface for {@link AnnotationBuilder} which wants to be + * aware of {@link Resource}s configured by {@link DefaultResourceConfigurer}. + * + * @author Janne Valkealahti + * + */ +public interface ResourceConfigurerAware { + + /** + * Configure {@link Resource}s. + * + * @param resources the resources + */ + void configureResources(Set resources); + +} 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 new file mode 100644 index 000000000..d201f06c6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java @@ -0,0 +1,103 @@ +package org.springframework.statemachine.config.configuration; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +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.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.state.State; + +@Configuration +public class StateMachineConfiguration, E extends Enum> extends + AbstractImportingAnnotationConfiguration, StateMachineConfig> { + + private final StateMachineConfigBuilder builder = new StateMachineConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(StateMachineDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableStateMachine.class; + } + + private static class StateMachineDelegatingFactoryBean, E extends Enum> implements + FactoryBean, E>>, BeanFactoryAware, InitializingBean { + + private final StateMachineConfigBuilder builder; + + private List, StateMachineConfigBuilder>> configurers; + + private BeanFactory beanFactory; + + private StateMachine, E> stateMachine; + + @SuppressWarnings("unused") + public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder builder) { + this.builder = builder; + } + + @Override + public StateMachine, E> getObject() throws Exception { + return stateMachine; + } + + @Override + public Class getObjectType() { + return StateMachine.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer, StateMachineConfigBuilder> configurer : configurers) { + builder.apply(configurer); + } + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + EnumStateMachineFactory stateMachineFactory = new EnumStateMachineFactory(stateMachineTransitions, stateMachineStates); + stateMachineFactory.setBeanFactory(beanFactory); + stateMachine = stateMachineFactory.getStateMachine(); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Autowired(required=false) + protected void onConfigurers( + List, StateMachineConfigBuilder>> configurers) + throws Exception { + this.configurers = configurers; + } + + } + +} 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 new file mode 100644 index 000000000..13d84571d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java @@ -0,0 +1,103 @@ +package org.springframework.statemachine.config.configuration; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +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.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.state.State; + +@Configuration +public class StateMachineFactoryConfiguration, E extends Enum> extends + AbstractImportingAnnotationConfiguration, StateMachineConfig> { + + private final StateMachineConfigBuilder builder = new StateMachineConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableStateMachineFactory.class; + } + + private static class StateMachineFactoryDelegatingFactoryBean, E extends Enum> implements + FactoryBean, E>>, BeanFactoryAware, InitializingBean { + + private final StateMachineConfigBuilder builder; + + private List, StateMachineConfigBuilder>> configurers; + + private BeanFactory beanFactory; + + private StateMachineFactory, E> stateMachineFactory; + + @SuppressWarnings("unused") + public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder builder) { + this.builder = builder; + } + + @Override + public StateMachineFactory, E> getObject() throws Exception { + return stateMachineFactory; + } + + @Override + public Class getObjectType() { + return StateMachineFactory.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer, StateMachineConfigBuilder> configurer : configurers) { + builder.apply(configurer); + } + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + EnumStateMachineFactory enumStateMachineFactory = new EnumStateMachineFactory(stateMachineTransitions, stateMachineStates); + enumStateMachineFactory.setBeanFactory(beanFactory); + this.stateMachineFactory = enumStateMachineFactory; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Autowired(required=false) + protected void onConfigurers( + List, StateMachineConfigBuilder>> configurers) + throws Exception { + this.configurers = configurers; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java new file mode 100644 index 000000000..baaa93fd1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java @@ -0,0 +1,98 @@ +/* + * 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.configurers; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * Default implementation of a {@link ExternalTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultExternalTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements ExternalTransitionConfigurer { + + private S source; + + private S target; + + private E event; + + private Collection actions = new ArrayList(); + + private Guard guard; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.add(source, target, event, actions, guard, TransitionKind.EXTERNAL); + } + + @Override + public ExternalTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public ExternalTransitionConfigurer target(S target) { + this.target = target; + return this; + } + + @Override + public ExternalTransitionConfigurer event(E event) { + this.event = event; + return this; + } + + @Override + public ExternalTransitionConfigurer action(Action action) { + actions.add(action); + return this; + } + + @Override + public ExternalTransitionConfigurer guard(Guard guard) { + this.guard = guard; + return this; + } + + @Override + public ExternalTransitionConfigurer guardExpression(String expression) { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java new file mode 100644 index 000000000..c976f825b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java @@ -0,0 +1,92 @@ +/* + * 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.configurers; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * Default implementation of a {@link InternalTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultInternalTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements InternalTransitionConfigurer { + + private S source; + + private S target; + + private E event; + + private Collection actions = new ArrayList(); + + private Guard guard; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.add(source, target, event, actions, guard, TransitionKind.INTERNAL); + } + + @Override + public InternalTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public InternalTransitionConfigurer event(E event) { + this.event = event; + return this; + } + + @Override + public InternalTransitionConfigurer action(Action action) { + actions.add(action); + return this; + } + + @Override + public InternalTransitionConfigurer guard(Guard guard) { + this.guard = guard; + return this; + } + + @Override + public InternalTransitionConfigurer guardExpression(String expression) { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java new file mode 100644 index 000000000..a8179e97b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java @@ -0,0 +1,74 @@ +/* + * 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.configurers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +public class DefaultStateConfigurer + extends AnnotationConfigurerAdapter, StateMachineStateConfigurer, StateMachineStateBuilder> + implements StateConfigurer { + + private final Collection> states = new ArrayList>(); + + private S initial; + + @Override + public void configure(StateMachineStateBuilder builder) throws Exception { + builder.add(states); + builder.setInitialState(initial); + } + + @Override + public StateConfigurer initial(S initial) { + this.initial = initial; + return this; + } + + @Override + public StateConfigurer state(S state) { + return state(state, (E[])null); + } + + @Override + public StateConfigurer state(S state, Collection entryActions, Collection exitActions) { + states.add(new StateData(state, null, entryActions, exitActions)); + return this; + } + + @Override + public StateConfigurer state(S state, E... deferred) { + states.add(new StateData(state, deferred)); + return this; + } + + @Override + public StateConfigurer states(Set states) { + for (S s : states) { + state(s); + } + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java new file mode 100644 index 000000000..9ae8df407 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java @@ -0,0 +1,39 @@ +/* + * 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.configurers; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring external {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface ExternalTransitionConfigurer extends + TransitionConfigurer, S, E> { + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + ExternalTransitionConfigurer target(S target); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java new file mode 100644 index 000000000..01410ed53 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java @@ -0,0 +1,31 @@ +/* + * 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.configurers; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring internal {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface InternalTransitionConfigurer extends + TransitionConfigurer, S, E> { + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java new file mode 100644 index 000000000..366ed70f2 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java @@ -0,0 +1,38 @@ +/* + * 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.configurers; + +import java.util.Collection; +import java.util.Set; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +public interface StateConfigurer extends + AnnotationConfigurerBuilder> { + + StateConfigurer initial(S initial); + + StateConfigurer state(S state); + + StateConfigurer state(S state, Collection entryActions, Collection exitActions); + + StateConfigurer state(S state, E... deferred); + + StateConfigurer states(Set states); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java new file mode 100644 index 000000000..01690fbd5 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -0,0 +1,76 @@ +/* + * 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.configurers; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.Transition; + +/** + * Base {@code TransitionConfigurer} interface for configuring {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of a transition configurer + * @param the type of state + * @param the type of event + */ +public interface TransitionConfigurer extends + AnnotationConfigurerBuilder> { + + /** + * Specify a source state {@code S} for this {@link Transition}. + * + * @param source the source state {@code S} + * @return configurer for chaining + */ + T source(S source); + + /** + * Specify event {@code E} for this {@link Transition}. + * + * @param event the event for transition + * @return configurer for chaining + */ + T event(E event); + + /** + * Specify {@link Action} for this {@link Transition}. + * + * @param action the action + * @return configurer for chaining + */ + T action(Action action); + + /** + * Specify a {@link Guard} for this {@link Transition}. + * + * @param guard the guard + * @return configurer for chaining + */ + T guard(Guard guard); + + /** + * Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}. + * + * @param expression the SpEL expression + * @return configurer for chaining + */ + T guardExpression(String expression); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java new file mode 100644 index 000000000..b0477733b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java @@ -0,0 +1,39 @@ +/* + * 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.guard; + +import org.springframework.statemachine.StateContext; + +/** + * {@code Guard}s are typically considered as guard conditions which affect the + * behaviour of a state machine by enabling actions or transitions only when they + * evaluate to {@code TRUE} and disabling them when they evaluate to + * {@code FALSE}. + * + * @author Janne Valkealahti + * + */ +public interface Guard { + + /** + * Evaluate a guard condition. + * + * @param context the state context + * @return true, if guard evaluation is successful, false otherwise. + */ + boolean evaluate(StateContext context); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java new file mode 100644 index 000000000..7b731ab32 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java @@ -0,0 +1,49 @@ +/* + * 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.guard; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.statemachine.StateContext; +import org.springframework.util.Assert; + +/** + * {@link Guard} which uses Spring SpEL expression for condition evaluation. + * + * @author Janne Valkealahti + * + */ +public class SpelExpressionGuard implements Guard { + + private final Expression expression; + + /** + * Instantiates a new spel expression guard. + * + * @param expression the expression + */ + public SpelExpressionGuard(Expression expression) { + Assert.notNull(expression, "Expression cannot be null"); + this.expression = expression; + } + + @Override + public boolean evaluate(StateContext context) { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context); + return expression.getValue(evaluationContext, Boolean.class); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java new file mode 100644 index 000000000..ec5535668 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java @@ -0,0 +1,67 @@ +/* + * 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.listener; + +import java.util.List; + +/** + * Base implementation for all composite listeners. + * + * @author Janne Valkealahti + * + * @param the type of the listener + */ +public class AbstractCompositeListener { + + /** List of ordered composite listeners */ + private OrderedComposite listeners; + + /** + * Constructs instance with an empty listener list. + */ + public AbstractCompositeListener() { + listeners = new OrderedComposite(); + } + + /** + * Sets the list of listeners. This clears + * all existing listeners. + * + * @param listeners the new listeners + */ + public void setListeners(List listeners) { + this.listeners.setItems(listeners); + } + + /** + * Register a new listener. + * + * @param listener the listener + */ + public void register(T listener) { + listeners.add(listener); + } + + /** + * Gets the listeners. + * + * @return the listeners + */ + public OrderedComposite getListeners() { + return listeners; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java new file mode 100644 index 000000000..263da3140 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java @@ -0,0 +1,33 @@ +/* + * 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.listener; + +import java.util.Iterator; + +import org.springframework.statemachine.state.State; + +public class CompositeStateMachineListener extends AbstractCompositeListener, E>> implements + StateMachineListener, E> { + + @Override + public void stateChanged(State from, State to) { + for (Iterator, E>> iterator = getListeners().reverse(); iterator.hasNext();) { + StateMachineListener, E> listener = iterator.next(); + listener.stateChanged(from, to); + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java new file mode 100644 index 000000000..b94c9660a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java @@ -0,0 +1,107 @@ +/* + * 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.listener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; + +/** + * Composite item which can be used in other components which + * may want to allow automatic and annotation based ordering. + * Good use case is a list of listeners where user may want + * to place some of them to be processed before the others. + * + * @author Janne Valkealahti + * + * @param the type of the item + */ +public class OrderedComposite { + + private List unordered = new ArrayList(); + + private List ordered = new ArrayList(); + + private Comparator comparator = new AnnotationAwareOrderComparator(); + + private List list = new ArrayList(); + + /** + * Public setter for the listeners. + * + * @param items items + */ + public void setItems(List items) { + unordered.clear(); + ordered.clear(); + for (S s : items) { + add(s); + } + } + + /** + * Register additional item. + * + * @param item item + */ + public void add(S item) { + if (item instanceof Ordered) { + if (!ordered.contains(item)) { + ordered.add(item); + } + } else if (AnnotationUtils.isAnnotationDeclaredLocally(Order.class, item.getClass())) { + if (!ordered.contains(item)) { + ordered.add(item); + } + } else if (!unordered.contains(item)) { + unordered.add(item); + } + Collections.sort(ordered, comparator); + list.clear(); + list.addAll(ordered); + list.addAll(unordered); + } + + /** + * Public getter for the list of items. The {@link Ordered} items come + * first, followed by any unordered ones. + * + * @return an iterator over the list of items + */ + public Iterator iterator() { + return new ArrayList(list).iterator(); + } + + /** + * Public getter for the list of items in reverse. The {@link Ordered} items + * come last, after any unordered ones. + * + * @return an iterator over the list of items + */ + public Iterator reverse() { + ArrayList result = new ArrayList(list); + Collections.reverse(result); + return result.iterator(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java new file mode 100644 index 000000000..eec8ffe63 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java @@ -0,0 +1,22 @@ +/* + * 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.listener; + +public interface StateMachineListener { + + void stateChanged(S from, S to); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java new file mode 100644 index 000000000..1412c0729 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java @@ -0,0 +1,45 @@ +/* + * 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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * Strategy interface for post-processing annotated methods. + * + * @author Mark Fisher + * @author Janne Valkealahti + * + * @param the type of an annotation + */ +public interface MethodAnnotationPostProcessor { + + /** + * Post process a bean. As a result of a given bean, its name, method and + * annotation in a method, this method can return a new bean or + * null. Caller of this method is then responsible to handle + * newly created object. + * + * @param bean the bean + * @param beanName the bean name + * @param method the method + * @param annotation the annotation + * @return the object + */ + Object postProcess(Object bean, String beanName, Method method, T annotation); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java new file mode 100644 index 000000000..37261fd41 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java @@ -0,0 +1,54 @@ +/* + * 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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * A simple {@link StateMachineRuntimeProcessor} implementation using + * methods from a state machine protected bean. + * + * @author Janne Valkealahti + * + * @param the return type + */ +public class MethodInvokingStateMachineRuntimeProcessor implements StateMachineRuntimeProcessor { + + private final StateMachineMethodInvokerHelper delegate; + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Method method) { + delegate = new StateMachineMethodInvokerHelper(targetObject, method); + } + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, String methodName) { + delegate = new StateMachineMethodInvokerHelper(targetObject, methodName); + } + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Class annotationType) { + delegate = new StateMachineMethodInvokerHelper(targetObject, annotationType); + } + + @Override + public T process(StateMachineRuntime stateMachineRuntime) { + try { + return delegate.process(stateMachineRuntime); + } catch (Exception e) { + throw new RuntimeException("Error processing bean", e); + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java new file mode 100644 index 000000000..f6055e7a8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java @@ -0,0 +1,70 @@ +/* + * 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.processor; + +import java.lang.reflect.Method; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.core.annotation.OrderUtils; +import org.springframework.statemachine.annotation.OnTransition; + +/** + * Post-processor for Methods annotated with {@link OnTransition}. + * + * @author Janne Valkealahti + * + */ +public class StateMachineActivatorAnnotationPostProcessor implements MethodAnnotationPostProcessor{ + + protected final BeanFactory beanFactory; + + public StateMachineActivatorAnnotationPostProcessor(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcess(Object bean, String beanName, Method method, OnTransition annotation) { + StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, annotation); + + Integer order = findOrder(bean, method); + if (order != null) { + handler.setOrder(order); + } + return handler; + } + + /** + * Find {@link Order} order either from a class or + * method level. Method level always takes presence + * over class level. + * + * @param bean the bean to inspect + * @param method the method to inspect + * @return the order or NULL if not found + */ + private static Integer findOrder(Object bean, Method method) { + Integer order = OrderUtils.getOrder(bean.getClass()); + Order ann = AnnotationUtils.findAnnotation(method, Order.class); + if (ann != null) { + order = ann.value(); + } + return order; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java new file mode 100644 index 000000000..ea7170346 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java @@ -0,0 +1,248 @@ +/* + * 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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * A {@link BeanPostProcessor} implementation that processes method-level + * annotations such as {@link OnTransition}. + * + * @author Mark Fisher + * @author Marius Bogoevici + * @author Janne Valkealahti + * + */ +public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, + Lifecycle, ApplicationListener { + + private final static Log log = LogFactory.getLog(StateMachineAnnotationPostProcessor.class); + + /** Factory from BeanFactoryAware */ + private volatile ConfigurableListableBeanFactory beanFactory; + + /** Post processors map - annotation -> method post processor */ + private final Map, MethodAnnotationPostProcessor> postProcessors = + new HashMap, MethodAnnotationPostProcessor>(); + + /** + * Application events for post processed beans (if bean instance of ApplicationListener) will + * be dispatched from here via callback in this class. + */ + private final Set> listeners = new HashSet>(); + + /** + * Lifecycle callbacks for post processed bean (if bean instance of Lifecycle) will + * be dispatched from here via callback in this class. + */ + private final Set lifecycles = new HashSet(); + + /** Flag for Lifecycle in this class */ + private volatile boolean running = true; + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(), + "a ConfigurableListableBeanFactory is required"); + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + postProcessors.put(OnTransition.class, new StateMachineActivatorAnnotationPostProcessor(beanFactory)); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + final Class beanClass = getBeanClass(bean); + + if (!isStereotype(beanClass)) { + // we only post-process stereotype components + return bean; + } + + ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation[] annotations = AnnotationUtils.getAnnotations(method); + + for (Annotation annotation : annotations) { + MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType()); + + if (postProcessor != null && shouldCreateHandler(annotation)) { + Object result = postProcessor.postProcess(bean, beanName, method, annotation); + + if (result != null && result instanceof StateMachineHandler) { + String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); + + if (result instanceof BeanNameAware) { + ((BeanNameAware) result).setBeanName(endpointBeanName); + } + beanFactory.registerSingleton(endpointBeanName, result); + if (result instanceof BeanFactoryAware) { + ((BeanFactoryAware) result).setBeanFactory(beanFactory); + } + if (result instanceof InitializingBean) { + try { + ((InitializingBean) result).afterPropertiesSet(); + } catch (Exception e) { + throw new BeanInitializationException("failed to initialize annotated component", e); + } + } + if (result instanceof Lifecycle) { + lifecycles.add((Lifecycle) result); + if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) { + ((SmartLifecycle) result).start(); + } + } + if (result instanceof ApplicationListener) { + listeners.add((ApplicationListener) result); + } + } + } + } + } + }); + return bean; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + for (ApplicationListener listener : listeners) { + try { + listener.onApplicationEvent(event); + } catch (ClassCastException e) { + if (log.isWarnEnabled() && event != null) { + log.warn("ApplicationEvent of type [" + event.getClass() + + "] not accepted by ApplicationListener [" + listener + "]"); + } + } + } + } + + @Override + public boolean isRunning() { + return this.running; + } + + @Override + public void start() { + for (Lifecycle lifecycle : this.lifecycles) { + if (!lifecycle.isRunning()) { + lifecycle.start(); + } + } + this.running = true; + } + + @Override + public void stop() { + for (Lifecycle lifecycle : this.lifecycles) { + if (lifecycle.isRunning()) { + lifecycle.stop(); + } + } + this.running = false; + } + + private boolean shouldCreateHandler(Annotation annotation) { + return true; + } + + /** + * Gets the bean class. Will check if bean is a proxy and + * find a class from there as target class, otherwise + * we just get bean class. + * + * @param bean the bean + * @return the bean class + */ + private Class getBeanClass(Object bean) { + Class targetClass = AopUtils.getTargetClass(bean); + return (targetClass != null) ? targetClass : bean.getClass(); + } + + /** + * Checks if class is a stereotype meaning if there is + * a Component annotation present. + * + * @param beanClass the bean class + * @return true, if is stereotype + */ + private boolean isStereotype(Class beanClass) { + List annotations = new ArrayList(Arrays.asList(beanClass.getAnnotations())); + Class[] interfaces = beanClass.getInterfaces(); + for (Class iface : interfaces) { + annotations.addAll(Arrays.asList(iface.getAnnotations())); + } + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) { + return true; + } + } + return false; + } + + private String generateBeanName(String originalBeanName, Method method, Class annotationType) { + String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType); + String name = baseName; + int count = 1; + while (beanFactory.containsBean(name)) { + name = baseName + "#" + (++count); + } + return name; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java new file mode 100644 index 000000000..df31233bb --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java @@ -0,0 +1,102 @@ +/* + * 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.processor; + +import java.lang.reflect.Method; + +import org.springframework.core.Ordered; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.annotation.WithStateMachine; + +/** + * Handler for a common object representing something to be run. + * This is usually used when a plain pojo is configured with {@link WithStateMachine} + * and {@link OnTransition} annotations. + * + * @author Janne Valkealahti + * + */ +public class StateMachineHandler implements Ordered { + + private final StateMachineRuntimeProcessor processor; + + private int order = Ordered.LOWEST_PRECEDENCE; + + /** + * Instantiates a new container handler. + * + * @param target the target bean + */ +// public StateMachineHandler(Object target) { +// this(new MethodInvokingStateMachineRuntimeProcessor(target, OnTransition.class)); +// } + + /** + * Instantiates a new container handler. + * + * @param target the target bean + * @param method the method + */ + public StateMachineHandler(Object target, Method method) { + this(new MethodInvokingStateMachineRuntimeProcessor(target, method)); + } + + /** + * Instantiates a new container handler. + * + * @param target the target bean + * @param methodName the method name + */ + public StateMachineHandler(Object target, String methodName) { + this(new MethodInvokingStateMachineRuntimeProcessor(target, methodName)); + } + + /** + * Instantiates a new container handler. + * + * @param the generic type + * @param processor the processor + */ + public StateMachineHandler(MethodInvokingStateMachineRuntimeProcessor processor) { + this.processor = processor; + } + + @Override + public int getOrder() { + return order; + } + + /** + * Sets the order used get value from {@link #getOrder()}. + * Default value is {@link Ordered#LOWEST_PRECEDENCE}. + * + * @param order the new order + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * Handle container using a {@link StateMachineRuntimeProcessor}. + * + * @param stateMachineRuntime the state machine runtime + * @return the result value + */ + public Object handle(StateMachineRuntime stateMachineRuntime) { + return processor.process(stateMachineRuntime); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java new file mode 100644 index 000000000..f6f1e8301 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java @@ -0,0 +1,553 @@ +/* + * 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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.support.AbstractExpressionEvaluator; +import org.springframework.statemachine.support.AnnotatedMethodFilter; +import org.springframework.statemachine.support.FixedMethodFilter; +import org.springframework.statemachine.support.UniqueMethodFilter; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * A helper class using spel to execute target methods. + * + * @author Janne Valkealahti + * + * @param the return type + */ +public class StateMachineMethodInvokerHelper extends AbstractExpressionEvaluator { + + private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS"; + + private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS"; + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final Object targetObject; + + private volatile String displayString; + + private volatile boolean requiresReply; + + private final Map, HandlerMethod> handlerMethods; + + private final Map, HandlerMethod> handlerMessageMethods; + + private final LinkedList, HandlerMethod>> handlerMethodsList; + + private final HandlerMethod handlerMethod; + + private final Class expectedType; + + public StateMachineMethodInvokerHelper(Object targetObject, Method method) { + this(targetObject, method, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Method method, Class expectedType) { + this(targetObject, null, method, expectedType); + } + + public StateMachineMethodInvokerHelper(Object targetObject, String methodName) { + this(targetObject, methodName, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, String methodName, Class expectedType) { + this(targetObject, null, methodName, expectedType); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Class annotationType) { + this(targetObject, annotationType, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + Class expectedType) { + this(targetObject, annotationType, (String) null, expectedType); + } + + public T process(StateMachineRuntime stateMachineRuntime) throws Exception { + ParametersWrapper wrapper = new ParametersWrapper(stateMachineRuntime.getStateContext()); + return processInternal(wrapper); + } + + @Override + public String toString() { + return this.displayString; + } + + private StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + Method method, Class expectedType) { + Assert.notNull(method, "method must not be null"); + this.expectedType = expectedType; + this.requiresReply = expectedType != null; + if (expectedType != null) { + Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE, + "method must have a return type"); + } + Assert.notNull(targetObject, "targetObject must not be null"); + this.targetObject = targetObject; + this.handlerMethod = new HandlerMethod(method); + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; + this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType); + this.setDisplayString(targetObject, method); + } + + private StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + String methodName, Class expectedType) { + Assert.notNull(targetObject, "targetObject must not be null"); + this.expectedType = expectedType; + this.targetObject = targetObject; + this.requiresReply = expectedType != null; + Map, HandlerMethod>> handlerMethodsForTarget = this.findHandlerMethodsForTarget( + targetObject, annotationType, methodName, requiresReply); + Map, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS); + Map, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS); + if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) + || (handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) { + if (handlerMethods.size() == 1) { + this.handlerMethod = handlerMethods.values().iterator().next(); + } else { + this.handlerMethod = handlerMessageMethods.values().iterator().next(); + } + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; + } else { + this.handlerMethod = null; + this.handlerMethods = handlerMethods; + this.handlerMessageMethods = handlerMessageMethods; + this.handlerMethodsList = new LinkedList, HandlerMethod>>(); + + // TODO Consider to use global option to determine a precedence of + // methods + this.handlerMethodsList.add(this.handlerMethods); + this.handlerMethodsList.add(this.handlerMessageMethods); + } + this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType); + this.setDisplayString(targetObject, methodName); + } + + private void setDisplayString(Object targetObject, Object targetMethod) { + StringBuilder sb = new StringBuilder(targetObject.getClass().getName()); + if (targetMethod instanceof Method) { + sb.append("." + ((Method) targetMethod).getName()); + } else if (targetMethod instanceof String) { + sb.append("." + targetMethod); + } + this.displayString = sb.toString() + "]"; + } + + private void prepareEvaluationContext(StandardEvaluationContext context, Object method, + Class annotationType) { + Class targetType = AopUtils.getTargetClass(this.targetObject); + if (method instanceof Method) { + context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method)); + if (expectedType != null) { + Assert.state( + context.getTypeConverter().canConvert( + TypeDescriptor.valueOf(((Method) method).getReturnType()), + TypeDescriptor.valueOf(expectedType)), "Cannot convert to expected type (" + + expectedType + ") from " + method); + } + } else if (method == null || method instanceof String) { + AnnotatedMethodFilter filter = new AnnotatedMethodFilter(annotationType, (String) method, + this.requiresReply); + Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()), + "Cannot convert to expected type (" + expectedType + ") from " + method); + context.registerMethodFilter(targetType, filter); + } + context.setVariable("target", targetObject); + } + + private boolean canReturnExpectedType(AnnotatedMethodFilter filter, Class targetType, TypeConverter typeConverter) { + if (expectedType == null) { + return true; + } + List methods = filter.filter(Arrays.asList(ReflectionUtils.getAllDeclaredMethods(targetType))); + for (Method method : methods) { + if (typeConverter.canConvert(TypeDescriptor.valueOf(method.getReturnType()), TypeDescriptor.valueOf(expectedType))) { + return true; + } + } + return false; + } + + private T processInternal(ParametersWrapper parameters) throws Exception { + HandlerMethod candidate = this.findHandlerMethodForParameters(parameters); + Assert.notNull(candidate, "No candidate methods found for messages."); + Expression expression = candidate.getExpression(); + Class expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType(); + try { + @SuppressWarnings("unchecked") + T result = (T) this.evaluateExpression(expression, parameters, expectedType); + if (this.requiresReply) { + Assert.notNull(result, "Expression evaluation result was null, but this processor requires a reply."); + } + return result; + } catch (Exception e) { + Throwable evaluationException = e; + if (e instanceof EvaluationException && e.getCause() != null) { + evaluationException = e.getCause(); + } + if (evaluationException instanceof Exception) { + throw (Exception) evaluationException; + } else { + throw new IllegalStateException("Cannot process message", evaluationException); + } + } + } + + private Map, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject, + final Class annotationType, final String methodName, final boolean requiresReply) { + + Map, HandlerMethod>> handlerMethods = new HashMap, HandlerMethod>>(); + + final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>(); + final Map, HandlerMethod> candidateMessageMethods = new HashMap, HandlerMethod>(); + final Class targetClass = this.getTargetClass(targetObject); + MethodFilter methodFilter = new UniqueMethodFilter(targetClass); + ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + boolean matchesAnnotation = false; + if (method.isBridge()) { + return; + } + if (isMethodDefinedOnObjectClass(method)) { + return; + } + if (method.getDeclaringClass().equals(Proxy.class)) { + return; + } + if (!Modifier.isPublic(method.getModifiers())) { + return; + } + if (requiresReply && void.class.equals(method.getReturnType())) { + return; + } + if (methodName != null && !methodName.equals(method.getName())) { + return; + } + if (annotationType != null && AnnotationUtils.findAnnotation(method, annotationType) != null) { + matchesAnnotation = true; + } + HandlerMethod handlerMethod = null; + try { + handlerMethod = new HandlerMethod(method); + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Method [" + method + "] is not eligible for container handling.", e); + } + return; + } + Class targetParameterType = handlerMethod.getTargetParameterType(); + if (matchesAnnotation || annotationType == null) { + if (handlerMethod.isMessageMethod()) { + if (candidateMessageMethods.containsKey(targetParameterType)) { + throw new IllegalArgumentException("Found more than one method match for type " + + "[Message<" + targetParameterType + ">]"); + } + candidateMessageMethods.put(targetParameterType, handlerMethod); + } else { + if (candidateMethods.containsKey(targetParameterType)) { + String exceptionMessage = "Found more than one method match for "; + if (Void.class.equals(targetParameterType)) { + exceptionMessage += "empty parameter for 'payload'"; + } else { + exceptionMessage += "type [" + targetParameterType + "]"; + } + throw new IllegalArgumentException(exceptionMessage); + } + candidateMethods.put(targetParameterType, handlerMethod); + } + } + } + }, methodFilter); + + if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) { + handlerMethods.put(CANDIDATE_METHODS, candidateMethods); + handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); + return handlerMethods; + } + + Assert.state(!handlerMethods.isEmpty(), "Target object of type [" + this.targetObject.getClass() + + "] has no eligible methods for handling Container."); + + return handlerMethods; + } + + private Class getTargetClass(Object targetObject) { + Class targetClass = targetObject.getClass(); + if (AopUtils.isAopProxy(targetObject)) { + targetClass = AopUtils.getTargetClass(targetObject); + if (targetClass == targetObject.getClass()) { + try { + // Maybe a proxy with no target - e.g. gateway + Class[] interfaces = ((Advised) targetObject).getProxiedInterfaces(); + if (interfaces != null && interfaces.length == 1) { + targetClass = interfaces[0]; + } + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Exception trying to extract interface", e); + } + } + } + } + else if (org.springframework.util.ClassUtils.isCglibProxyClass(targetClass)) { + Class superClass = targetObject.getClass().getSuperclass(); + if (!Object.class.equals(superClass)) { + targetClass = superClass; + } + } + return targetClass; + } + + private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) { + if (this.handlerMethod != null) { + return this.handlerMethod; + } else { + return this.handlerMethods.get(Void.class); + } + } + + private static boolean isMethodDefinedOnObjectClass(Method method) { + if (method == null) { + return false; + } + if (method.getDeclaringClass().equals(Object.class)) { + return true; + } + if (ReflectionUtils.isEqualsMethod(method) || ReflectionUtils.isHashCodeMethod(method) + || ReflectionUtils.isToStringMethod(method) || AopUtils.isFinalizeMethod(method)) { + return true; + } + return (method.getName().equals("clone") && method.getParameterTypes().length == 0); + } + + + /** + * Helper class for generating and exposing metadata for a candidate handler method. The metadata includes the SpEL + * expression and the expected payload type. + */ + private static class HandlerMethod { + + private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); + + private final Method method; + + private final Expression expression; + + private volatile TypeDescriptor targetParameterTypeDescriptor; + + private volatile Class targetParameterType = Void.class; + + private volatile boolean messageMethod; + + HandlerMethod(Method method) { + this.method = method; + this.expression = this.generateExpression(method); + } + + + Expression getExpression() { + return this.expression; + } + + Class getTargetParameterType() { + return this.targetParameterType; + } + + private boolean isMessageMethod() { + return messageMethod; + } + + @Override + public String toString() { + return this.method.toString(); + } + + private Expression generateExpression(Method method) { + StringBuilder sb = new StringBuilder("#target." + method.getName() + "("); + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + boolean hasUnqualifiedMapParameter = false; + for (int i = 0; i < parameterTypes.length; i++) { + if (i != 0) { + sb.append(", "); + } + MethodParameter methodParameter = new MethodParameter(method, i); + TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter); + Class parameterType = parameterTypeDescriptor.getObjectType(); + Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]); + if (mappingAnnotation != null) { + Class annotationType = mappingAnnotation.annotationType(); + +// if (annotationType.equals(YarnEnvironments.class)) { +// sb.append("environment"); +// } else if (annotationType.equals(YarnEnvironment.class)) { +// YarnEnvironment headerAnnotation = (YarnEnvironment) mappingAnnotation; +// sb.append(this.determineEnvironmentExpression(headerAnnotation, methodParameter)); +// } else if (annotationType.equals(YarnParameters.class)) { +// Assert.isTrue(Map.class.isAssignableFrom(parameterType), +// "The @YarnParameters annotation can only be applied to a Map-typed parameter."); +// sb.append("parameters"); +// } else if (annotationType.equals(YarnParameter.class)) { +// YarnParameter headerAnnotation = (YarnParameter) mappingAnnotation; +// sb.append(this.determineParameterExpression(headerAnnotation, methodParameter)); +// } + } + } + if (hasUnqualifiedMapParameter) { + if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) { + throw new IllegalArgumentException( + "Unable to determine payload matching parameter due to ambiguous Map typed parameters. " + + "Consider adding the @Payload and or @Headers annotations as appropriate."); + } + } + sb.append(")"); + if (this.targetParameterTypeDescriptor == null) { + this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class); + } + return EXPRESSION_PARSER.parseExpression(sb.toString()); + } + + private Annotation findMappingAnnotation(Annotation[] annotations) { + if (annotations == null || annotations.length == 0) { + return null; + } + Annotation match = null; + for (Annotation annotation : annotations) { + Class type = annotation.annotationType(); +// if (type.equals(YarnParameters.class) || type.equals(YarnParameter.class) +// || type.equals(YarnEnvironments.class) || type.equals(YarnEnvironment.class)) { +// if (match != null) { +// throw new IllegalArgumentException( +// "At most one parameter annotation can be provided for message mapping, " +// + "but found two: [" + match.annotationType().getName() + "] and [" +// + annotation.annotationType().getName() + "]"); +// } +// match = annotation; +// } + } + return match; + } + +// private String determineParameterExpression(YarnParameter parameterAnnotation, MethodParameter methodParameter) { +// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); +// String headerName = null; +// String relativeExpression = ""; +// String valueAttribute = parameterAnnotation.value(); +// if (!StringUtils.hasText(valueAttribute)) { +// headerName = methodParameter.getParameterName(); +// } else if (valueAttribute.indexOf('.') != -1) { +// String tokens[] = valueAttribute.split("\\.", 2); +// headerName = tokens[0]; +// if (StringUtils.hasText(tokens[1])) { +// relativeExpression = "." + tokens[1]; +// } +// } else { +// headerName = valueAttribute; +// } +// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " +// + "disabled or header name is not explicitly provided via @YarnParameter annotation."); +// String headerRetrievalExpression = "parameters['" + headerName + "']"; +// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; +// String fallbackExpression = (parameterAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " +// + headerName + "')" +// : "null"; +// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; +// } + +// private String determineEnvironmentExpression(YarnEnvironment environmentAnnotation, MethodParameter methodParameter) { +// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); +// String headerName = null; +// String relativeExpression = ""; +// String valueAttribute = environmentAnnotation.value(); +// if (!StringUtils.hasText(valueAttribute)) { +// headerName = methodParameter.getParameterName(); +// } else if (valueAttribute.indexOf('.') != -1) { +// String tokens[] = valueAttribute.split("\\.", 2); +// headerName = tokens[0]; +// if (StringUtils.hasText(tokens[1])) { +// relativeExpression = "." + tokens[1]; +// } +// } else { +// headerName = valueAttribute; +// } +// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " +// + "disabled or header name is not explicitly provided via @YarnEnvironment annotation."); +// String headerRetrievalExpression = "environment['" + headerName + "']"; +// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; +// String fallbackExpression = (environmentAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " +// + headerName + "')" +// : "null"; +// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; +// } + + } + + /** + * Wrapping everything we need to work with spel. + */ + public class ParametersWrapper { + + private final StateContext stateContext; + + public ParametersWrapper(StateContext stateContext) { + this.stateContext = stateContext; + } + + public StateContext getStateContext() { + return stateContext; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java new file mode 100644 index 000000000..ed45aa8a3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java @@ -0,0 +1,35 @@ +/* + * 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.processor; + +import java.lang.reflect.Method; + +import org.springframework.statemachine.annotation.OnTransition; + +public class StateMachineOnTransitionHandler extends StateMachineHandler { + + private OnTransition annotation; + + public StateMachineOnTransitionHandler(Object target, Method method, OnTransition annotation) { + super(target, method); + this.annotation = annotation; + } + + public OnTransition getAnnotation() { + return annotation; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java new file mode 100644 index 000000000..3ac4be7f7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java @@ -0,0 +1,30 @@ +/* + * 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.processor; + +import org.springframework.statemachine.StateContext; + +/** + * A generic runtime representation of a state machine. + * + * @author Janne Valkealahti + * + */ +public interface StateMachineRuntime { + + StateContext getStateContext(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java new file mode 100644 index 000000000..10a42e8f9 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java @@ -0,0 +1,37 @@ +/* + * 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.processor; + +/** + * Defines a strategy of processing a state machine and returning + * some Object (or null). + * + * @author Janne Valkealahti + * + * @param type + */ +public interface StateMachineRuntimeProcessor { + + /** + * Process the container based on information available + * from {@link StateMachineRuntime}. + * + * @param stateMachineRuntime the yarn container runtime + * @return the result + */ + T process(StateMachineRuntime stateMachineRuntime); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java new file mode 100644 index 000000000..c17f4daf7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java @@ -0,0 +1,70 @@ +/* + * 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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public abstract class AbstractState implements State { + + private S id; + private Collection deferred; + private Collection entryActions; + private Collection exitActions; + + public AbstractState(S id) { + this(id, null); + } + + public AbstractState(S id, Collection deferred) { + this(id, deferred, null, null); + } + + public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + this.id = id; + this.deferred = deferred; + this.entryActions = entryActions; + this.exitActions = exitActions; + } + + @Override + public S getId() { + return id; + } + + @Override + public Collection getDeferredEvents() { + return deferred; + } + + @Override + public Collection getEntryActions() { + return entryActions; + } + + @Override + public Collection getExitActions() { + return exitActions; + } + + @Override + public String toString() { + return "AbstractState [id=" + id + ", deferred=" + deferred + ", entryActions=" + entryActions + + ", exitActions=" + exitActions + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java new file mode 100644 index 000000000..4c85cbdee --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java @@ -0,0 +1,42 @@ +/* + * 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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public class EnumState, E extends Enum> extends AbstractState { + + public EnumState(S id) { + super(id); + } + + public EnumState(S id, Collection deferred) { + super(id, deferred); + } + + public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + super(id, deferred, entryActions, exitActions); + } + + @Override + public String toString() { + return "EnumState [getId()=" + getId() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + + ", toString()=" + super.toString() + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java new file mode 100644 index 000000000..4055a227d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java @@ -0,0 +1,60 @@ +/* + * 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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +/** + * {@code State} is an interface representing possible state in a state machine. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface State { + + /** + * Gets the state identifier. + * + * @return the identifier + */ + S getId(); + + /** + * Gets the deferred events for this state. + * + * @return the state deferred events + */ + Collection getDeferredEvents(); + + /** + * Gets {@link Action}s executed entering in this state. + * + * @return the state entry actions + */ + Collection getEntryActions(); + + /** + * Gets {@link Action}s executed exiting from this state. + * + * @return the state exit actions + */ + Collection getExitActions(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java new file mode 100644 index 000000000..8a06b3bf6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java @@ -0,0 +1,129 @@ +/* + * 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.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Base class providing common functionality for using Spring expression language. + * + * @author Mark Fisher + * @author Dave Syer + * @author Oleg Zhurakousky + * @author Artem Bilan + * @author Gary Russell + * @author Janne Valkealahti + * + */ +public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, InitializingBean { + + private volatile StandardEvaluationContext evaluationContext; + + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(); + + private volatile BeanFactory beanFactory; + + @Override + public void setBeanFactory(final BeanFactory beanFactory) { + if (beanFactory != null) { + this.beanFactory = beanFactory; + this.typeConverter.setBeanFactory(beanFactory); + if (this.evaluationContext != null && this.evaluationContext.getBeanResolver() == null) { + this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + getEvaluationContext(); + } + + /** + * Sets the conversion service. + * + * @param conversionService the new conversion service + */ + public void setConversionService(ConversionService conversionService) { + if (conversionService != null) { + this.typeConverter.setConversionService(conversionService); + } + } + + /** + * Gets the evaluation context. + * + * @return the evaluation context + */ + protected StandardEvaluationContext getEvaluationContext() { + return this.getEvaluationContext(true); + } + + /** + * Emits a WARN log if the beanFactory field is null, unless the argument is false. + * @param beanFactoryRequired set to false to suppress the warning. + * @return The evaluation context. + */ + protected final StandardEvaluationContext getEvaluationContext(boolean beanFactoryRequired) { + if (this.evaluationContext == null) { + if (this.beanFactory == null && !beanFactoryRequired) { + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(); + } + else { + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); + } + if (this.typeConverter != null) { + this.evaluationContext.setTypeConverter(this.typeConverter); + } + } + return this.evaluationContext; + } + + protected Object evaluateExpression(String expression, Object input) { + return this.evaluateExpression(expression, input, (Class) null); + } + + protected T evaluateExpression(String expression, Object input, Class expectedType) { + return this.expressionParser.parseExpression(expression).getValue(this.getEvaluationContext(), input, expectedType); + } + + protected Object evaluateExpression(Expression expression, Object input) { + return this.evaluateExpression(expression, input, (Class) null); + } + + protected T evaluateExpression(Expression expression, Class expectedType) { + return expression.getValue(this.getEvaluationContext(), expectedType); + } + + protected Object evaluateExpression(Expression expression) { + return expression.getValue(this.getEvaluationContext()); + } + + protected T evaluateExpression(Expression expression, Object input, Class expectedType) { + return expression.getValue(this.getEvaluationContext(), input, expectedType); + } + +} 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 new file mode 100644 index 000000000..6d8efeceb --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -0,0 +1,326 @@ +/* + * 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.support; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.OrderComparator; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.ExtendedState; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.listener.CompositeStateMachineListener; +import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.processor.StateMachineHandler; +import org.springframework.statemachine.processor.StateMachineOnTransitionHandler; +import org.springframework.statemachine.processor.StateMachineRuntime; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; +import org.springframework.statemachine.transition.TransitionKind; +import org.springframework.statemachine.trigger.Trigger; +import org.springframework.util.Assert; + +/** + * Base implementation of a {@link StateMachine} loosely modelled from UML state + * machine. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractStateMachine extends LifecycleObjectSupport implements StateMachine, E> { + + private static final Log log = LogFactory.getLog(AbstractStateMachine.class); + + private final Collection> states; + + private final Collection> transitions; + + private final State initialState; + + private final ExtendedState extendedState; + + private final Queue> eventQueue = new ConcurrentLinkedQueue>(); + + private final LinkedList> deferList = new LinkedList>(); + + private final CompositeStateMachineListener stateListener = new CompositeStateMachineListener(); + + private volatile State currentState; + + private volatile Runnable task; + + /** + * Instantiates a new abstract state machine. + * + * @param states the states of this machine + * @param transitions the transitions of this machine + * @param initialState the initial state of this machine + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState) { + this(states, transitions, initialState, new DefaultExtendedState()); + } + + /** + * Instantiates a new abstract state machine. + * + * @param states the states of this machine + * @param transitions the transitions of this machine + * @param initialState the initial state of this machine + * @param extendedState the extended state of this machine + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState, ExtendedState extendedState) { + super(); + this.states = states; + this.transitions = transitions; + this.initialState = initialState; + this.extendedState = extendedState; + } + + @Override + public State getState() { + return currentState; + } + + @Override + public State getInitialState() { + return initialState; + } + + @Override + public void sendEvent(Message event) { + // TODO: machine header looks weird! + event = MessageBuilder.fromMessage(event).setHeader("machine", this).build(); + if (log.isDebugEnabled()) { + log.debug("Queue event " + event); + } + eventQueue.add(event); + scheduleEventQueueProcessing(); + } + + @Override + public void sendEvent(E event) { + sendEvent(MessageBuilder.withPayload(event).build()); + } + + @Override + protected void doStart() { + super.doStart(); + switchToState(initialState, null); + } + + @Override + public void addStateListener(StateMachineListener, E> listener) { + stateListener.register(listener); + } + + /** + * Gets the {@link State}s defined in this machine. Returned collection is + * an unmodifiable copy because states in a state machine are immutable. + * + * @return immutable copy of existing states + */ + public Collection> getStates() { + return Collections.unmodifiableCollection(states); + } + + private void switchToState(State state, Message event) { + log.info("Moving into state=" + state + " from " + currentState); + + exitFromState(currentState, event); + stateListener.stateChanged(currentState, state); + + callHandlers(currentState, state, event); + + currentState = state; + entryToState(state, event); + + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + if (transition.getTrigger() == null && source.equals(currentState)) { + switchToState(target, event); + } + + } + + } + + private void exitFromState(State state, Message event) { + if (state != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + Collection actions = state.getExitActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(new DefaultStateContext(messageHeaders, extendedState)); + } + } + } + } + + private void entryToState(State state, Message event) { + if (state != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + Collection actions = state.getEntryActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(new DefaultStateContext(messageHeaders, extendedState)); + } + } + } + } + + private void processEventQueue() { + log.debug("Process event queue"); + Message queuedEvent = null; + while ((queuedEvent = eventQueue.poll()) != null) { + Message defer = null; + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + Trigger trigger = transition.getTrigger(); + if (source.equals(currentState)) { + if (trigger != null && trigger.evaluate(queuedEvent.getPayload())) { + boolean transit = transition.transit(new DefaultStateContext(queuedEvent.getHeaders(), extendedState)); + if (transit && transition.getKind() != TransitionKind.INTERNAL) { + switchToState(target, queuedEvent); + } + break; + } else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(queuedEvent.getPayload())) { + defer = queuedEvent; + } + } + } + if (defer != null) { + log.info("Deferring event " + defer); + deferList.addLast(defer); + } + } + } + + private void processDeferList() { + log.debug("Process defer list"); + ListIterator> iterator = deferList.listIterator(); + while (iterator.hasNext()) { + Message event = iterator.next(); + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + Trigger trigger = transition.getTrigger(); + if (source.equals(currentState)) { + if (trigger != null && trigger.evaluate(event.getPayload())) { + boolean transit = transition.transit(new DefaultStateContext(event.getHeaders(), extendedState)); + if (transit && transition.getKind() != TransitionKind.INTERNAL) { + switchToState(target, event); + } + iterator.remove(); + } + } + } + } + } + + private void scheduleEventQueueProcessing() { + if (task == null) { + task = new Runnable() { + @Override + public void run() { + processEventQueue(); + processDeferList(); + task = null; + } + }; + getTaskExecutor().execute(task); + } + } + + private void callHandlers(State sourceState, State targetState, Message event) { + if (sourceState != null && targetState != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState); + getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext); + } + + } + + + private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { + StateMachineRuntime runtime = new StateMachineRuntime() { + @Override + public StateContext getStateContext() { + return stateContext; + } + }; + List results = new ArrayList(); + for (StateMachineHandler handler : stateMachineHandlers) { + results.add(handler.handle(runtime)); + } + return results; + } + + private List getStateMachineHandlers(State sourceState, State targetState) { + BeanFactory beanFactory = getBeanFactory(); + + // TODO think how to handle null bf + if (beanFactory == null) { + return Collections.emptyList(); + } + Assert.state(beanFactory instanceof ListableBeanFactory, "Bean factory must be instance of ListableBeanFactory"); + Map handlers = ((ListableBeanFactory) beanFactory) + .getBeansOfType(StateMachineOnTransitionHandler.class); + List handlersList = new ArrayList(); + + for (Entry entry : handlers.entrySet()) { + OnTransition annotation = entry.getValue().getAnnotation(); + String source = annotation.source(); + String target = annotation.target(); + String s = sourceState.getId().toString(); + String t = targetState.getId().toString(); + if (s.equals(source) && t.equals(target)) { + handlersList.add(entry.getValue()); + } + } + + OrderComparator comparator = new OrderComparator(); + Collections.sort(handlersList, comparator); + return handlersList; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java new file mode 100644 index 000000000..b893ebc8c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java @@ -0,0 +1,75 @@ +/* + * 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.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.MethodFilter; +import org.springframework.util.StringUtils; + +/** + * A MethodFilter implementation that enables the following: + *
    + *
  1. matching on method name, if available
  2. + *
  3. exclusion of void-returning methods if 'requiresReply' is true
  4. + *
  5. limiting to annotated methods if at least one is present
  6. + *
+ *

+ * + * @author Mark Fisher + * @author Janne Valkealahti + */ +public class AnnotatedMethodFilter implements MethodFilter { + + private final Class annotationType; + + private final String methodName; + + private final boolean requiresReply; + + public AnnotatedMethodFilter(Class annotationType, String methodName, boolean requiresReply) { + this.annotationType = annotationType; + this.methodName = methodName; + this.requiresReply = requiresReply; + } + + public List filter(List methods) { + List annotatedCandidates = new ArrayList(); + List fallbackCandidates = new ArrayList(); + for (Method method : methods) { + if (method.isBridge()) { + continue; + } + if (this.requiresReply && method.getReturnType().equals(void.class)) { + continue; + } + if (StringUtils.hasText(this.methodName) && !this.methodName.equals(method.getName())) { + continue; + } + if (this.annotationType != null && AnnotationUtils.findAnnotation(method, this.annotationType) != null) { + annotatedCandidates.add(method); + } else { + fallbackCandidates.add(method); + } + } + return (!annotatedCandidates.isEmpty()) ? annotatedCandidates : fallbackCandidates; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java new file mode 100644 index 000000000..121c3e88a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java @@ -0,0 +1,154 @@ +/* + * 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.support; + +import java.beans.PropertyEditor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.expression.TypeConverter; +import org.springframework.util.ClassUtils; + +/** + * @author Dave Syer + * @author Oleg Zhurakousky + * @author Gary Russell + * @author Soby Chacko + * @author Janne Valkealahti + * + */ +public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware { + + private static ConversionService defaultConversionService; + + private volatile SimpleTypeConverter delegate = new SimpleTypeConverter(); + + private volatile boolean haveCalledDelegateGetDefaultEditor; + + private volatile ConversionService conversionService; + + /** + * Instantiates a new bean factory type converter. + */ + public BeanFactoryTypeConverter() { + synchronized (BeanFactoryTypeConverter.class) { + if (defaultConversionService == null) { + defaultConversionService = new DefaultConversionService(); + } + } + this.conversionService = defaultConversionService; + } + + public BeanFactoryTypeConverter(ConversionService conversionService) { + this.conversionService = conversionService; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof ConfigurableBeanFactory) { + Object typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter(); + if (typeConverter instanceof SimpleTypeConverter) { + delegate = (SimpleTypeConverter) typeConverter; + } + } + } + + public boolean canConvert(Class sourceType, Class targetType) { + if (conversionService.canConvert(sourceType, targetType)) { + return true; + } + if (!String.class.isAssignableFrom(sourceType) && !String.class.isAssignableFrom(targetType)) { + // PropertyEditor cannot convert non-Strings + return false; + } + if (!String.class.isAssignableFrom(sourceType)) { + return delegate.findCustomEditor(sourceType, null) != null || this.getDefaultEditor(sourceType) != null; + } + return delegate.findCustomEditor(targetType, null) != null || this.getDefaultEditor(targetType) != null; + } + + public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) { + if (conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) { + return true; + } + // TODO: what does this mean? This method is not used in SpEL so + // probably ignorable? + Class sourceType = sourceTypeDescriptor.getObjectType(); + Class targetType = targetTypeDescriptor.getObjectType(); + return canConvert(sourceType, targetType); + } + + public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) { + // Echoes + // org.springframework.expression.common.ExpressionUtils.convertTypedValue() + if ((targetType.getType() == Void.class || targetType.getType() == Void.TYPE) && value == null) { + return null; + } + if (sourceType != null) { + Class sourceClass = sourceType.getType(); + //Class targetClass = targetType.getType(); + if (sourceType.isAssignableTo(targetType) && ClassUtils.isPrimitiveArray(sourceClass)) { + return value; + } + } + if (conversionService.canConvert(sourceType, targetType)) { + return conversionService.convert(value, sourceType, targetType); + } + if (!String.class.isAssignableFrom(sourceType.getType())) { + PropertyEditor editor = delegate.findCustomEditor(sourceType.getType(), null); + if (editor == null) { + editor = this.getDefaultEditor(sourceType.getType()); + } + if (editor != null) { // INT-1441 + String text = null; + synchronized (editor) { + editor.setValue(value); + text = editor.getAsText(); + } + if (String.class.isAssignableFrom(targetType.getType())) { + return text; + } + return convertValue(text, TypeDescriptor.valueOf(String.class), targetType); + } + } + return delegate.convertIfNecessary(value, targetType.getType()); + } + + private PropertyEditor getDefaultEditor(Class sourceType) { + PropertyEditor defaultEditor; + if (this.haveCalledDelegateGetDefaultEditor) { + defaultEditor = delegate.getDefaultEditor(sourceType); + } else { + synchronized (this) { + // not thread-safe - it builds the defaultEditors field in-place + // (SPR-10191) + defaultEditor = delegate.getDefaultEditor(sourceType); + } + this.haveCalledDelegateGetDefaultEditor = true; + } + return defaultEditor; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java new file mode 100644 index 000000000..fd59d0bad --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java @@ -0,0 +1,45 @@ +/* + * 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.support; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.statemachine.ExtendedState; + +/** + * Default implementation of a {@link ExtendedState}. + * + * @author Janne Valkealahti + * + */ +public class DefaultExtendedState implements ExtendedState { + + private final Map variables; + + /** + * Instantiates a new default extended state. + */ + public DefaultExtendedState() { + this.variables = new HashMap(); + } + + @Override + public Map getVariables() { + return variables; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java new file mode 100644 index 000000000..db0e79497 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java @@ -0,0 +1,43 @@ +/* + * 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.support; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.statemachine.ExtendedState; +import org.springframework.statemachine.StateContext; + +public class DefaultStateContext implements StateContext { + + private final MessageHeaders messageHeaders; + + private final ExtendedState extendedState; + + public DefaultStateContext(MessageHeaders messageHeaders, ExtendedState extendedState) { + this.messageHeaders = messageHeaders; + this.extendedState = extendedState; + } + + @Override + public MessageHeaders getMessageHeaders() { + return messageHeaders; + } + + @Override + public ExtendedState getExtendedState() { + return extendedState; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java new file mode 100644 index 000000000..8a5bb36e6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java @@ -0,0 +1,96 @@ +/* + * 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.context.expression.MapAccessor; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; + +/** + * Utility class with static methods for helping with establishing environments for + * SpEL expressions. + * + * @author Gary Russell + * @author Oleg Zhurakousky + * @author Artem Bilan + */ +public abstract class ExpressionUtils { + + private static final Log logger = LogFactory.getLog(ExpressionUtils.class); + + /** + * Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its + * property accessor property and the supplied {@link ConversionService} in its + * conversionService property. + * + * @param conversionService the conversion service. + * @return the evaluation context. + */ + private static StandardEvaluationContext createStandardEvaluationContext(ConversionService conversionService, + BeanFactory beanFactory) { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + evaluationContext.addPropertyAccessor(new MapAccessor()); + if (conversionService != null) { + evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService)); + } + if (beanFactory != null) { + evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } + return evaluationContext; + } + + /** + * Used to create a context with no BeanFactory, usually in tests. + * @return The evaluation context. + */ + public static StandardEvaluationContext createStandardEvaluationContext() { + return doCreateContext(null); + } + + /** + * Obtains the context from the beanFactory if not null; emits a warning if the beanFactory + * is null. + * @param beanFactory The bean factory. + * @return The evaluation context. + */ + public static StandardEvaluationContext createStandardEvaluationContext(BeanFactory beanFactory) { + if (beanFactory == null) { + logger.warn("Creating EvaluationContext with no beanFactory", new RuntimeException("No beanfactory")); + } + return doCreateContext(beanFactory); + } + + private static StandardEvaluationContext doCreateContext(BeanFactory beanFactory) { + ConversionService conversionService = null; + StandardEvaluationContext evaluationContext = null; + if (beanFactory != null) { + evaluationContext = StateMachineContextUtils.getEvaluationContext(beanFactory); + } + if (evaluationContext == null) { + if (beanFactory != null) { + conversionService = StateMachineContextUtils.getConversionService(beanFactory); + } + evaluationContext = createStandardEvaluationContext(conversionService, beanFactory); + } + return evaluationContext; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java new file mode 100644 index 000000000..83c52bdbd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java @@ -0,0 +1,54 @@ +/* + * 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.support; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.expression.MethodFilter; +import org.springframework.util.Assert; + +/** + * A {@link MethodFilter} implementation that will always return the same Method + * instance within a single-element list if it is present in the candidate list. + * If the Method is not present in the candidate list, it will return an empty + * list. + * + * @author Mark Fisher + * @author Gary Russell + * @author Janne Valkealahti + */ +public class FixedMethodFilter implements MethodFilter { + + private final Method method; + + public FixedMethodFilter(Method method) { + Assert.notNull(method, "method must not be null"); + this.method = method; + } + + public List filter(List methods) { + if (methods != null && methods.contains(this.method)) { + List filteredList = new ArrayList(1); + filteredList.add(this.method); + return filteredList; + } + return Collections. emptyList(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java new file mode 100644 index 000000000..7776aebfa --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java @@ -0,0 +1,250 @@ +/* + * 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.support; + +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * Convenient base class for object which needs spring task scheduler, task + * executor and life cycle handling. + * + * @author Janne Valkealahti + * + */ +public abstract class LifecycleObjectSupport implements InitializingBean, SmartLifecycle, BeanFactoryAware { + + private static final Log log = LogFactory.getLog(LifecycleObjectSupport.class); + + // fields for lifecycle + private volatile boolean autoStartup = true; + private volatile int phase = 0; + private volatile boolean running; + + // lock to protect lifycycle methods + private final ReentrantLock lifecycleLock = new ReentrantLock(); + + // common task handling + private TaskScheduler taskScheduler; + private TaskExecutor taskExecutor; + + // to access bean factory + private volatile BeanFactory beanFactory; + + @Override + public final void afterPropertiesSet() { + try { + this.onInit(); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new BeanInitializationException("failed to initialize", e); + } + } + + @Override + public final void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.notNull(beanFactory, "beanFactory must not be null"); + if(log.isDebugEnabled()) { + log.debug("Setting bean factory: " + beanFactory + " for " + this); + } + this.beanFactory = beanFactory; + } + + @Override + public final boolean isAutoStartup() { + return this.autoStartup; + } + + @Override + public final int getPhase() { + return this.phase; + } + + @Override + public final boolean isRunning() { + this.lifecycleLock.lock(); + try { + return this.running; + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void start() { + this.lifecycleLock.lock(); + try { + if (!this.running) { + this.doStart(); + this.running = true; + if (log.isInfoEnabled()) { + log.info("started " + this); + } else { + if(log.isDebugEnabled()) { + log.debug("already started " + this); + } + } + } + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void stop() { + this.lifecycleLock.lock(); + try { + if (this.running) { + this.doStop(); + this.running = false; + if (log.isInfoEnabled()) { + log.info("stopped " + this); + } + } else { + if (log.isDebugEnabled()) { + log.debug("already stopped " + this); + } + } + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void stop(Runnable callback) { + this.lifecycleLock.lock(); + try { + this.stop(); + callback.run(); + } finally { + this.lifecycleLock.unlock(); + } + } + + /** + * Sets the auto startup. + * + * @param autoStartup the new auto startup + * @see SmartLifecycle + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * Sets the phase. + * + * @param phase the new phase + * @see SmartLifecycle + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Gets the {@link BeanFactory} for this instance. + * + * @return the bean factory. + */ + protected final BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Sets the used {@link TaskScheduler}. + * + * @param taskScheduler the task scheduler + */ + public void setTaskScheduler(TaskScheduler taskScheduler) { + Assert.notNull(taskScheduler, "taskScheduler must not be null"); + this.taskScheduler = taskScheduler; + } + + /** + * Gets the defined {@link TaskScheduler}. + * + * @return the defined task scheduler + */ + protected TaskScheduler getTaskScheduler() { + if(taskScheduler == null && getBeanFactory() != null) { + if(log.isDebugEnabled()) { + log.debug("getting taskScheduler service from bean factory " + getBeanFactory()); + } + taskScheduler = StateMachineContextUtils.getTaskScheduler(getBeanFactory()); + } + return taskScheduler; + } + + /** + * Sets the used {@link TaskExecutor}. + * + * @param taskExecutor the task executor + */ + public void setTaskExecutor(TaskExecutor taskExecutor) { + Assert.notNull(taskExecutor, "taskExecutor must not be null"); + this.taskExecutor = taskExecutor; + } + + /** + * Gets the defined {@link TaskExecutor}. + * + * @return the defined task executor + */ + protected TaskExecutor getTaskExecutor() { + if(taskExecutor == null && getBeanFactory() != null) { + if(log.isDebugEnabled()) { + log.debug("getting taskExecutor service from bean factory " + getBeanFactory()); + } + taskExecutor = StateMachineContextUtils.getTaskExecutor(getBeanFactory()); + } + return taskExecutor; + } + + /** + * Subclasses may implement this for initialization logic. Called + * during the {@link InitializingBean} phase. Implementor should + * always call super method not to break initialization chain. + * + * @throws Exception exception + */ + protected void onInit() throws Exception {} + + /** + * Subclasses may implement this method with the start behavior. This + * method will be invoked while holding the {@link #lifecycleLock}. + */ + protected void doStart() {}; + + /** + * Subclasses may implement this method with the stop behavior. This method + * will be invoked while holding the {@link #lifecycleLock}. + */ + protected void doStop() {}; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java new file mode 100644 index 000000000..86314df2d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java @@ -0,0 +1,110 @@ +/* + * 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.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.task.TaskExecutor; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * Utility methods for accessing common components from the BeanFactory. + * + * @author Janne Valkealahti + * + */ +public class StateMachineContextUtils { + + /* Default task scheduler bean name */ + public static final String TASK_SCHEDULER_BEAN_NAME = "taskScheduler"; + + /* Default task executor bean name */ + public static final String TASK_EXECUTOR_BEAN_NAME = "taskExecutor"; + + /* Default conversion service bean name */ + public static final String CONVERSION_SERVICE_BEAN_NAME = "cloudClusterConversionService"; + + /* Default evaluation context bean name */ + public static final String EVALUATION_CONTEXT_BEAN_NAME = "cloudClusterEvaluationContext"; + + /** + * Return the {@link TaskScheduler} bean whose name is "taskScheduler" if + * available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * @return task scheduler + */ + public static TaskScheduler getTaskScheduler(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class); + } + + /** + * Return the {@link TaskScheduler} bean whose name is "taskExecutor" if + * available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * @return task executor + */ + public static TaskExecutor getTaskExecutor(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, TASK_EXECUTOR_BEAN_NAME, TaskExecutor.class); + } + + /** + * Return the {@link ConversionService} bean whose name is + * "yarnConversionService" if available. + * + * @param beanFactory + * BeanFactory for lookup, must not be null. + * + * @return The {@link ConversionService} bean whose name is + * "yarnConversionService" if available. + */ + public static ConversionService getConversionService(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); + } + + /** + * Return the {@link StandardEvaluationContext} bean whose name is + * "yarnEvaluationContext" if available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * + * @return the instance of {@link StandardEvaluationContext} bean whose name + * is "yarnEvaluationContext" . + */ + public static StandardEvaluationContext getEvaluationContext(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class); + } + + /** + * Gets a bean from a factory with a given name and type. + * + * @param beanFactory the bean factory + * @param beanName the bean name + * @param type the type as of a class + * @return Bean known to a bean factory, null if not found. + */ + private static T getBeanOfType(BeanFactory beanFactory, String beanName, Class type) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + if (!beanFactory.containsBean(beanName)) { + return null; + } + return beanFactory.getBean(beanName, type); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java new file mode 100644 index 000000000..a534e142b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java @@ -0,0 +1,46 @@ +/* + * 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.support; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * A {@link MethodFilter} implementation that will match unique methods. + * + * @author Oleg Zhurakousky + * @author Janne Valkealahti + */ +public class UniqueMethodFilter implements MethodFilter { + + private final List uniqueMethods = new ArrayList(); + + public UniqueMethodFilter(Class targetClass) { + ArrayList allMethods = new ArrayList(Arrays.asList(targetClass.getMethods())); + for (Method method : allMethods) { + this.uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass)); + } + } + + public boolean matches(Method method) { + return this.uniqueMethods.contains(method); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java new file mode 100644 index 000000000..23189c03c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java @@ -0,0 +1,31 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public abstract class AbstractExternalTransition extends AbstractTransition implements Transition { + + public AbstractExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + super(source, target, actions, event, TransitionKind.EXTERNAL, guard); + } + + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java new file mode 100644 index 000000000..d278e67f2 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java @@ -0,0 +1,30 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class AbstractInternalTransition extends AbstractTransition implements Transition { + + public AbstractInternalTransition(State source,Collection actions, E event, Guard guard) { + super(source, source, actions, event, TransitionKind.INTERNAL, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java new file mode 100644 index 000000000..de101a39e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java @@ -0,0 +1,30 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class AbstractLocalTransition extends AbstractTransition implements Transition { + + public AbstractLocalTransition(State source, State target,Collection actions, E event, Guard guard) { + super(source, target, actions, event, TransitionKind.LOCAL, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java new file mode 100644 index 000000000..e3845ca34 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java @@ -0,0 +1,105 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.trigger.EventTrigger; +import org.springframework.statemachine.trigger.Trigger; +import org.springframework.util.Assert; + +/** + * Base implementation of a {@link Transition}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractTransition implements Transition { + + private final State source; + + private final State target; + + private final Collection actions; + + private final TransitionKind kind; + + private final Guard guard; + + private Trigger trigger; + + public AbstractTransition(State source, State target, Collection actions, E event, + TransitionKind kind, Guard guard) { + Assert.notNull(source, "Source must be set"); +// Assert.notNull(target, "Target must be set"); + Assert.notNull(kind, "Transition type must be set"); + this.source = source; + this.target = target; + this.actions = actions; + if (event != null) { + this.trigger = new EventTrigger(event); + } + this.kind = kind; + this.guard = guard; + } + + @Override + public State getSource() { + return source; + } + + @Override + public State getTarget() { + return target; + } + + @Override + public Collection getActions() { + return actions; + } + + @Override + public Trigger getTrigger() { + return trigger; + } + + @Override + public boolean transit(StateContext context) { + if (guard != null) { + if (!guard.evaluate(context)) { + return false; + } + } + if (actions != null) { + for (Action action : actions) { + action.execute(context); + } + } + return true; + } + + @Override + public TransitionKind getKind() { + return kind; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java new file mode 100644 index 000000000..a167a2e03 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java @@ -0,0 +1,30 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class DefaultExternalTransition extends AbstractExternalTransition { + + public DefaultExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + super(source, target, actions, event, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java new file mode 100644 index 000000000..08d428de1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java @@ -0,0 +1,30 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class DefaultInternalTransition extends AbstractInternalTransition { + + public DefaultInternalTransition(State source, Collection actions, E event, Guard guard) { + super(source, actions, event, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java new file mode 100644 index 000000000..7cb9c7dd0 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java @@ -0,0 +1,48 @@ +/* + * 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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.trigger.Trigger; + +/** + * {@code Transition} is something what a state machine associates with a state + * changes. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface Transition { + + boolean transit(StateContext context); + + State getSource(); + + State getTarget(); + + Collection getActions(); + + Trigger getTrigger(); + + TransitionKind getKind(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java new file mode 100644 index 000000000..f557a9926 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java @@ -0,0 +1,36 @@ +/* + * 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.transition; + +/** + * Defines enumeration of a {@link Transition} kind. This is uses within a + * transition to indicate whether its type is external, internal or local. + * + * @author Janne Valkealahti + * + */ +public enum TransitionKind { + + /** Indicates an external transition kind. */ + EXTERNAL, + + /** Indicates an internal transition kind. */ + INTERNAL, + + /** Indicates a local transition kind. */ + LOCAL + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java new file mode 100644 index 000000000..d0548226f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java @@ -0,0 +1,31 @@ +/* + * 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.trigger; + +public class EventTrigger implements Trigger { + + private final E event; + + public EventTrigger(E event) { + this.event = event; + } + + @Override + public boolean evaluate(E event) { + return this.event.equals(event); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java new file mode 100644 index 000000000..17f05175e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java @@ -0,0 +1,33 @@ +/* + * 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.trigger; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code Trigger} is the cause of the {@link Transition}. Cause is usually an + * event but can be some other signal or a change in some condition. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface Trigger { + + boolean evaluate(E event); + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java new file mode 100644 index 000000000..4d95cb525 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -0,0 +1,93 @@ +/* + * 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; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; + +/** + * Base class for stace machine tests. + * + * @author Janne Valkealahti + * + */ +public abstract class AbstractStateMachineTests { + + public enum TestStates { + SI,S1,S2,S3,S4 + } + + public enum TestEvents { + E1,E2,E3,E4 + } + + @Configuration + public static class BaseConfig { + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + public static class TestEntryAction extends AbstractTestAction { + } + + public static class TestExitAction extends AbstractTestAction { + } + + public static class TestAction extends AbstractTestAction { + } + + public static class TestGuard implements Guard { + + public CountDownLatch onEvaluateLatch = new CountDownLatch(1); + + boolean evaluationResult = true; + + public TestGuard() { + } + + public TestGuard(boolean evaluationResult) { + this.evaluationResult = evaluationResult; + } + + @Override + public boolean evaluate(StateContext context) { + onEvaluateLatch.countDown(); + return evaluationResult; + } + } + + protected static class AbstractTestAction implements Action { + + public CountDownLatch onExecuteLatch = new CountDownLatch(1); + + @Override + public void execute(StateContext context) { + onExecuteLatch.countDown(); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java new file mode 100644 index 000000000..ea352a91b --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java @@ -0,0 +1,211 @@ +/* + * 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; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.DefaultInternalTransition; +import org.springframework.statemachine.transition.Transition; + +public class EnumStateMachineTests extends AbstractStateMachineTests { + + @Test + public void testSimpleStateSwitch() { + + State stateSI = new EnumState(TestStates.SI); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + Collection> transitions = new ArrayList>(); + + Collection actionsFromSIToS1 = new ArrayList(); + actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); + + Collection actionsFromS1ToS2 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); + + Collection actionsFromS2ToS3 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + State initialState = machine.getInitialState(); + assertThat(initialState, is(stateSI)); + + State state = machine.getState(); + assertThat(state, is(stateSI)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS1)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + state = machine.getState(); + assertThat(state, is(stateS2)); + + // not processed + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS2)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + state = machine.getState(); + assertThat(state, is(stateS3)); + } + + @Test + public void testDeferredEvents() { + + Collection deferred = new ArrayList(); + deferred.add(TestEvents.E2); + deferred.add(TestEvents.E3); + + // states + State stateSI = new EnumState(TestStates.SI, deferred); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + // transitions + Collection> transitions = new ArrayList>(); + + Collection actionsFromSIToS1 = new ArrayList(); + actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); + + Collection actionsFromS1ToS2 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); + + Collection actionsFromS2ToS3 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + // create machine + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); +// StateMachine, TestEvents> machine2 = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + State initialState = machine.getInitialState(); + assertThat(initialState, is(stateSI)); + + State state = machine.getState(); + assertThat(state, is(stateSI)); + + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + state = machine.getState(); + assertThat(state, is(stateSI)); + + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS3)); + } + + @Test + public void testInternalTransitions() { + State stateSI = new EnumState(TestStates.SI); + + Collection> states = new ArrayList>(); + states.add(stateSI); + + Collection actionsInSI = new ArrayList(); + actionsInSI.add(new LoggingAction("actionsInSI")); + DefaultInternalTransition transitionInternalSI = + new DefaultInternalTransition(stateSI, actionsInSI, TestEvents.E1, null); + + // transitions + Collection> transitions = new ArrayList>(); + transitions.add(transitionInternalSI); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java new file mode 100644 index 000000000..57fafff67 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java @@ -0,0 +1,81 @@ +/* + * 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; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.state.State; + +public class StateMachineFactoryTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testMachineFromFactory() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + + EnumStateMachineFactory stateMachineFactory = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, EnumStateMachineFactory.class); + StateMachine, TestEvents> machine = stateMachineFactory.getStateMachine(); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S2)); + ctx.close(); + } + + @Configuration + @EnableStateMachineFactory + static class Config 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); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java new file mode 100644 index 000000000..775ebace0 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java @@ -0,0 +1,129 @@ +/* + * 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; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +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; + +public class StateMachineTests extends AbstractStateMachineTests { + + @Test + public void test1() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + @SuppressWarnings("unchecked") + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build()); + ctx.close(); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); + } + + } + + @Configuration + @EnableStateMachine + static class Config extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3, TestEvents.E4) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(loggingAction()) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S4) + .target(TestStates.S3) + .event(TestEvents.E4) + .action(loggingAction()); + } + + @Bean + public LoggingAction loggingAction() { + return new LoggingAction("as bean"); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java new file mode 100644 index 000000000..06f6c3c3d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java @@ -0,0 +1,94 @@ +/* + * 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; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.util.ReflectionUtils; + +/** + * Utils for tests. + * + * @author Janne Valkealahti + * + */ +public class TestUtils { + + @SuppressWarnings("unchecked") + public static T readField(String name, Object target) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + return (T) field.get(target); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target); + } + + public static void setField(String name, Object target, Object value) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + field.set(target, value); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target, Object[] args, Class[] argsTypes) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name, argsTypes); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target, args); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java new file mode 100644 index 000000000..d0f1119cb --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java @@ -0,0 +1,145 @@ +/* + * 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.action; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +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; + +/** + * Tests for state machine actions. + * + * @author Janne Valkealahti + * + */ +public class ActionTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testTransitionActions() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + StateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + + TestCountAction testAction1 = ctx.getBean("testAction1", TestCountAction.class); + TestCountAction testAction2 = ctx.getBean("testAction2", TestCountAction.class); + TestCountAction testAction3 = ctx.getBean("testAction3", TestCountAction.class); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + assertThat(testAction1.count, is(1)); + assertThat(testAction2.count, is(1)); + assertThat(testAction3.count, is(1)); + ctx.close(); + } + + @Test + public void testEventFromAction() { + + } + + private static class TestCountAction implements Action { + + int count = 0; + + public TestCountAction() { + count = 0; + } + + @Override + public void execute(StateContext context) { + count++; + } + + } + + @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) + .state(TestStates.S3) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(testAction1()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(testAction2()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(testAction3()); + } + + @Bean + public TestCountAction testAction1() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction2() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction3() { + return new TestCountAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java new file mode 100644 index 000000000..18bd8bcf8 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java @@ -0,0 +1,141 @@ +/* + * 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.annotation; + +import static org.hamcrest.CoreMatchers.is; +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.messaging.support.MessageBuilder; +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; +import org.springframework.statemachine.processor.StateMachineAnnotationPostProcessor; + +public class MethodAnnotationTests extends AbstractStateMachineTests { + + @Test + @SuppressWarnings("unchecked") + public void testMethodAnnotations() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, AnnoConfig.class, BeanConfig1.class, Config1.class); + + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(context.containsBean("fooMachine"), is(true)); + + Bean1 bean1 = context.getBean(Bean1.class); + + // this event should cause 'method1' to get called + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + + assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(bean1.onOnTransitionFromS2ToS3Latch.await(2, TimeUnit.SECONDS), is(false)); + + context.close(); + } + + @WithStateMachine(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE) + static class Bean1 { + + CountDownLatch onMethod1Latch = new CountDownLatch(1); + CountDownLatch onOnTransitionFromS2ToS3Latch = new CountDownLatch(1); + + @OnTransition(source = "S1", target = "S2") + public void method1() { + onMethod1Latch.countDown(); + } + + @OnTransition + public void onTransitionFromS2ToS3() { + onOnTransitionFromS2ToS3Latch.countDown(); + } + + + } + + @Configuration + static class BeanConfig1 { + + @Bean + public Bean1 bean1() { + return new Bean1(); + } + + } + + + @Configuration + static class AnnoConfig { + + @Bean(name="org.springframework.statemachine.internal.springStateMachineAnnotationPostProcessor") + public StateMachineAnnotationPostProcessor springStateMachineAnnotationPostProcessor() { + return new StateMachineAnnotationPostProcessor(); + } + + } + + @Configuration + @EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"}) + 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) + .guard(testGuard()) + .action(testAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java new file mode 100644 index 000000000..071811f87 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java @@ -0,0 +1,118 @@ +/* + * 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; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; + +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +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; + +/** + * Tests for state machine configuration. + * + * @author Janne Valkealahti + * + */ +public class ConfigurationTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testStates() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + assertThat(testAction, notNullValue()); + assertThat(testGuard, notNullValue()); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .guard(testGuard()) + .action(testAction()); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java new file mode 100644 index 000000000..2b360ae14 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java @@ -0,0 +1,121 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class ComplexAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig config = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(config.complexData, notNullValue()); + assertThat(config.complexData, is("complexData")); + + assertThat(config.complexProperties, notNullValue()); + assertThat(config.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(config.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + assertThat(config.complexBeanA, notNullValue()); + assertThat(config.complexBeanA.dataA, notNullValue()); + assertThat(config.complexBeanA.resources, notNullValue()); + + assertThat(config.complexBeanA.dataA, is("complexDataA")); + assertThat(config.complexBeanA.resources.size(), is(2)); + Iterator iterator = config.complexBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("complexResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("complexResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("complexResourceA1")); + assertThat(fileNames[1], is("complexResourceA2")); + + assertTrue(ctx.containsBean("complexConfigData")); + assertTrue(ctx.containsBean("complexConfigBeanB")); + ComplexTestConfigBeanB beanB = ctx.getBean("complexConfigBeanB", ComplexTestConfigBeanB.class); + assertThat(beanB.dataB, is("complexDataB")); + assertThat(beanB.dataBB, is("complexDataBB")); + } + + @Configuration + @EnableComplexTest + static class Config extends ComplexTestConfigurerAdapter { + + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + + @Override + public void configure(ComplexTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("complexResourceA1") + .resource("complexResourceA2") + .and() + .setData("complexDataA"); + } + + @Override + public void configure(ComplexTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("complexDataB") + .setDataBB("complexDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java new file mode 100644 index 000000000..7da317e5e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java @@ -0,0 +1,32 @@ +/* + * 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.common.annotation; + +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; + +public class DependencyBean { + + private SimpleTestConfigBeanB beanB; + + public SimpleTestConfigBeanB getBeanB() { + return beanB; + } + + public void setBeanB(SimpleTestConfigBeanB beanB) { + this.beanB = beanB; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java new file mode 100644 index 000000000..5d5c2e28d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class MixedAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig simpleConfig = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(simpleConfig.simpleData, notNullValue()); + assertThat(simpleConfig.simpleData, is("simpleData")); + + assertThat(simpleConfig.simpleProperties, notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig complexConfig = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(complexConfig.complexData, notNullValue()); + assertThat(complexConfig.complexData, is("complexData")); + assertThat(complexConfig.complexProperties, notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + assertThat(complexConfig.simpleTestConfig, notNullValue()); + assertThat(complexConfig.simpleTestConfig.simpleData, notNullValue()); + assertThat(complexConfig.simpleTestConfig.simpleData, is("simpleData")); + + } + + @Configuration + @EnableSimpleTest + static class SimpleConfig extends SimpleTestConfigurerAdapter { + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + } + + + @Configuration + @EnableComplexTest + static class ComplexConfig extends ComplexTestConfigurerAdapter { + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java new file mode 100644 index 000000000..2e17e2b1e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java @@ -0,0 +1,94 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class MultipleAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig simpleConfig = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(simpleConfig.simpleData, notNullValue()); + assertThat(simpleConfig.simpleData, is("simpleData")); + + assertThat(simpleConfig.simpleProperties, notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig complexConfig = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(complexConfig.complexData, notNullValue()); + assertThat(complexConfig.complexData, is("complexData")); + + assertThat(complexConfig.complexProperties, notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + } + + @Configuration + @EnableSimpleTest + static class SimpleConfig extends SimpleTestConfigurerAdapter { + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + } + + + @Configuration + @EnableComplexTest + static class ComplexConfig extends ComplexTestConfigurerAdapter { + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java new file mode 100644 index 000000000..359ca8090 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java @@ -0,0 +1,121 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest2; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class SimpleAnnotationConfiguration2Tests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig config = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(config.simpleData, notNullValue()); + assertThat(config.simpleData, is("simpleData")); + + assertThat(config.simpleProperties, notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertThat(config.simpleBeanA, notNullValue()); + assertThat(config.simpleBeanA.dataA, notNullValue()); + assertThat(config.simpleBeanA.resources, notNullValue()); + + assertThat(config.simpleBeanA.dataA, is("simpleDataA")); + assertThat(config.simpleBeanA.resources.size(), is(2)); + Iterator iterator = config.simpleBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("simpleResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("simpleResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("simpleResourceA1")); + assertThat(fileNames[1], is("simpleResourceA2")); + +// assertTrue(ctx.containsBean("simpleConfigData")); +// assertTrue(ctx.containsBean("simpleConfigBeanB")); +// SimpleTestConfigBeanB beanB = ctx.getBean("simpleConfigBeanB", SimpleTestConfigBeanB.class); +// assertThat(beanB.dataB, is("simpleDataB")); +// assertThat(beanB.dataBB, is("simpleDataBB")); + } + + @Configuration + @EnableSimpleTest2 + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java new file mode 100644 index 000000000..4f3091dab --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java @@ -0,0 +1,121 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class SimpleAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig config = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(config.simpleData, notNullValue()); + assertThat(config.simpleData, is("simpleData")); + + assertThat(config.simpleProperties, notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertThat(config.simpleBeanA, notNullValue()); + assertThat(config.simpleBeanA.dataA, notNullValue()); + assertThat(config.simpleBeanA.resources, notNullValue()); + + assertThat(config.simpleBeanA.dataA, is("simpleDataA")); + assertThat(config.simpleBeanA.resources.size(), is(2)); + Iterator iterator = config.simpleBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("simpleResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("simpleResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("simpleResourceA1")); + assertThat(fileNames[1], is("simpleResourceA2")); + + assertTrue(ctx.containsBean("simpleConfigData")); + assertTrue(ctx.containsBean("simpleConfigBeanB")); + SimpleTestConfigBeanB beanB = ctx.getBean("simpleConfigBeanB", SimpleTestConfigBeanB.class); + assertThat(beanB.dataB, is("simpleDataB")); + assertThat(beanB.dataBB, is("simpleDataBB")); + } + + @Configuration + @EnableSimpleTest + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java new file mode 100644 index 000000000..65a5f0834 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java @@ -0,0 +1,96 @@ +/* + * 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.common.annotation; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for using java config and importing xml config + * with dependency for beans created from a javaconfig. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class XmlImportDependenciesTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testDependencyBeanFromXml() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + assertTrue(ctx.containsBean("simpleConfigBeanB")); + assertTrue(ctx.containsBean("dependencyBean")); + + DependencyBean dependencyBean = ctx.getBean(DependencyBean.class); + assertThat(dependencyBean, notNullValue()); + assertThat(dependencyBean.getBeanB(), notNullValue()); + } + + @Configuration + @EnableSimpleTest + @ImportResource("org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml") + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java new file mode 100644 index 000000000..57c638e71 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java @@ -0,0 +1,49 @@ +/* + * 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.common.annotation.complex; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; + +/** + * Main pojo used to collect together configs. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfig { + + public String complexData; + public Properties complexProperties; + public ComplexTestConfigBeanA complexBeanA; + public ComplexTestConfigBeanB complexBeanB; + public SimpleTestConfig simpleTestConfig; + + public ComplexTestConfig(String config, Properties properties) { + complexData = config; + complexProperties = properties; + } + + public void setSimpleBeanB(ComplexTestConfigBeanB complexBeanB) { + this.complexBeanB = complexBeanB; + } + + public void setSimpleBeanA(ComplexTestConfigBeanA complexBeanA) { + this.complexBeanA = complexBeanA; + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java new file mode 100644 index 000000000..1778c8c9c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java @@ -0,0 +1,33 @@ +/* + * 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.common.annotation.complex; + +import java.util.Set; + +import org.springframework.core.io.Resource; + +/** + * Simple bean storing a set of resources and data. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanA { + + public String dataA; + public Set resources; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java new file mode 100644 index 000000000..f37ac44dc --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java @@ -0,0 +1,67 @@ +/* + * 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.common.annotation.complex; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurerAware; + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfigBeanA}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanABuilder + extends AbstractConfiguredAnnotationBuilder + implements ResourceConfigurerAware { + + private String data; + private Set resources = new HashSet(); + + @Override + protected ComplexTestConfigBeanA performBuild() throws Exception { + ComplexTestConfigBeanA bean = new ComplexTestConfigBeanA(); + bean.dataA = data; + bean.resources = resources; + return bean; + } + + @Override + public void configureResources(Set resources) { + this.resources.addAll(resources); + } + + public Set getResources() { + return resources; + } + + public ComplexTestConfigBeanABuilder setData(String data) { + this.data = data; + return this; + } + + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java new file mode 100644 index 000000000..13a1f19fe --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java @@ -0,0 +1,29 @@ +/* + * 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.common.annotation.complex; + +/** + * Simple bean storing data. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanB { + + public String dataB; + public String dataBB; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java new file mode 100644 index 000000000..2853c30b8 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java @@ -0,0 +1,61 @@ +/* + * 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.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfigBeanB}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanBBuilder + extends AbstractConfiguredAnnotationBuilder + implements ComplexTestConfigBeanBConfigurer { + + private String dataB; + private String dataBB; + + @Override + protected ComplexTestConfigBeanB performBuild() throws Exception { + ComplexTestConfigBeanB bean = new ComplexTestConfigBeanB(); + bean.dataB = dataB; + bean.dataBB = dataBB; + return bean; + } + + @Override + public ComplexTestConfigBeanBConfigurer setData(String data) { + this.dataB = data; + return this; + } + + @Override + public ComplexTestConfigBeanBConfigurer setDataBB(String data) { + this.dataBB = data; + return this; + } + + @Override + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java new file mode 100644 index 000000000..5a5bfd68f --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java @@ -0,0 +1,26 @@ +/* + * 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.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +public interface ComplexTestConfigBeanBConfigurer { + + ComplexTestConfigBeanBConfigurer setData(String data); + ComplexTestConfigBeanBConfigurer setDataBB(String data); + ResourceConfigurer withResources() throws Exception; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java new file mode 100644 index 000000000..9004aa72a --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.common.annotation.complex; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultPropertiesConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.PropertiesConfigurerAware; + + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfig}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBuilder extends AbstractConfiguredAnnotationBuilder + implements PropertiesConfigurerAware { + + private final Properties properties = new Properties(); + + @Override + protected ComplexTestConfig performBuild() throws Exception { + ComplexTestConfig bean = new ComplexTestConfig("complexData", properties); + bean.complexBeanA = getSharedObject(ComplexTestConfigBeanABuilder.class).build(); + bean.complexBeanB = getSharedObject(ComplexTestConfigBeanBBuilder.class).build(); + return bean; + } + + @Override + public void configureProperties(Properties properties) { + getProperties().putAll(properties); + } + + public Properties getProperties() { + return properties; + } + + public DefaultPropertiesConfigurer withProperties() throws Exception { + return getOrApply(new DefaultPropertiesConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java new file mode 100644 index 000000000..45d36a87d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java @@ -0,0 +1,71 @@ +/* + * 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.common.annotation.complex; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; + +/** + * @{@link Configuration} which is imported from @{@ EnableSimpleTest}. + * + * @author Janne Valkealahti + * + */ +@Configuration +public class ComplexTestConfiguration extends AbstractAnnotationConfiguration { + + ComplexTestConfigBuilder builder = new ComplexTestConfigBuilder(); + + @Autowired(required=false) + @Qualifier("simpleConfig") + SimpleTestConfig simpleTestConfig; + + @Bean(name="complexConfig") + public ComplexTestConfig complexTestConfig() { + ComplexTestConfig config = builder.getOrBuild(); + config.simpleTestConfig = simpleTestConfig; + return config; + } + + @Bean(name="complexConfigData") + public String complexTestConfigData() { + ComplexTestConfig config = builder.getOrBuild(); + return config.complexData; + } + + @Bean(name="complexConfigBeanB") + public ComplexTestConfigBeanB complexTestConfigBeanB() { + ComplexTestConfig config = builder.getOrBuild(); + return config.complexBeanB; + } + + @Override + protected void onConfigurers(List> configurers) throws Exception { + for (AnnotationConfigurer configurer : configurers) { + if (configurer.isAssignable(builder)) { + builder.apply(configurer); + } + } + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java new file mode 100644 index 000000000..20ed3e664 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java @@ -0,0 +1,32 @@ +/* + * 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.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * Example of an interface used in {@link ComplexTestConfigurerAdapter}. + * + * @author Janne Valkealahti + * + */ +public interface ComplexTestConfigurer extends AnnotationConfigurer { + + void configure(ComplexTestConfigBeanABuilder a) throws Exception; + + void configure(ComplexTestConfigBeanBConfigurer b) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java new file mode 100644 index 000000000..87958cc3b --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java @@ -0,0 +1,74 @@ +/* + * 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.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Generic adapter example which user would extend + * in @{@link org.springframework.context.annotation.Configuration} + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigurerAdapter implements ComplexTestConfigurer { + + private ComplexTestConfigBeanABuilder beanABuilder; + private ComplexTestConfigBeanBBuilder beanBBuilder; + + @Override + public final void init(ComplexTestConfigBuilder config) throws Exception { + config.setSharedObject(String.class, "complexSharedData"); + config.setSharedObject(ComplexTestConfigBeanABuilder.class, getSimpleTestConfigBeanABuilder()); + config.setSharedObject(ComplexTestConfigBeanBBuilder.class, getSimpleTestConfigBeanBBuilder()); + } + + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + } + + @Override + public void configure(ComplexTestConfigBeanABuilder a) throws Exception { + } + + @Override + public void configure(ComplexTestConfigBeanBConfigurer b) throws Exception { + } + + protected final ComplexTestConfigBeanBBuilder getSimpleTestConfigBeanBBuilder() throws Exception { + if (beanBBuilder != null) { + return beanBBuilder; + } + beanBBuilder = new ComplexTestConfigBeanBBuilder(); + configure(beanBBuilder); + return beanBBuilder; + } + + protected final ComplexTestConfigBeanABuilder getSimpleTestConfigBeanABuilder() throws Exception { + if (beanABuilder != null) { + return beanABuilder; + } + beanABuilder = new ComplexTestConfigBeanABuilder(); + configure(beanABuilder); + return beanABuilder; + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return builder instanceof ComplexTestConfigBuilder; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java new file mode 100644 index 000000000..c0da46f5c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java @@ -0,0 +1,41 @@ +/* + * 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.common.annotation.complex; + +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; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({ComplexTestConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableComplexTest { +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java new file mode 100644 index 000000000..8422d75d1 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java @@ -0,0 +1,41 @@ +/* + * 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.common.annotation.simple; + +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; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({SimpleTestConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableSimpleTest { +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java new file mode 100644 index 000000000..1493c1fd8 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java @@ -0,0 +1,44 @@ +/* + * 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.common.annotation.simple; + +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; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({SimpleTestConfiguration2.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableSimpleTest2 { + + String[] name() default {"simpleConfig"}; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java new file mode 100644 index 000000000..c1d255f1d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java @@ -0,0 +1,46 @@ +/* + * 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.common.annotation.simple; + +import java.util.Properties; + +/** + * Main pojo used to collect together configs. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfig { + + public String simpleData; + public Properties simpleProperties; + public SimpleTestConfigBeanA simpleBeanA; + public SimpleTestConfigBeanB simpleBeanB; + + public SimpleTestConfig(String config, Properties properties) { + simpleData = config; + simpleProperties = properties; + } + + public void setSimpleBeanB(SimpleTestConfigBeanB simpleBeanB) { + this.simpleBeanB = simpleBeanB; + } + + public void setSimpleBeanA(SimpleTestConfigBeanA simpleBeanA) { + this.simpleBeanA = simpleBeanA; + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java new file mode 100644 index 000000000..29024a096 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java @@ -0,0 +1,33 @@ +/* + * 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.common.annotation.simple; + +import java.util.Set; + +import org.springframework.core.io.Resource; + +/** + * Simple bean storing a set of resources and data. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanA { + + public String dataA; + public Set resources; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java new file mode 100644 index 000000000..6ec533214 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java @@ -0,0 +1,67 @@ +/* + * 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.common.annotation.simple; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurerAware; + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfigBeanA}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanABuilder + extends AbstractConfiguredAnnotationBuilder + implements ResourceConfigurerAware { + + private String data; + private Set resources = new HashSet(); + + @Override + protected SimpleTestConfigBeanA performBuild() throws Exception { + SimpleTestConfigBeanA bean = new SimpleTestConfigBeanA(); + bean.dataA = data; + bean.resources = resources; + return bean; + } + + @Override + public void configureResources(Set resources) { + this.resources.addAll(resources); + } + + public Set getResources() { + return resources; + } + + public SimpleTestConfigBeanABuilder setData(String data) { + this.data = data; + return this; + } + + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java new file mode 100644 index 000000000..879a48e6e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java @@ -0,0 +1,29 @@ +/* + * 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.common.annotation.simple; + +/** + * Simple bean storing data. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanB { + + public String dataB; + public String dataBB; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java new file mode 100644 index 000000000..b72c5605c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java @@ -0,0 +1,61 @@ +/* + * 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.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfigBeanB}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanBBuilder + extends AbstractConfiguredAnnotationBuilder + implements SimpleTestConfigBeanBConfigurer { + + private String dataB; + private String dataBB; + + @Override + protected SimpleTestConfigBeanB performBuild() throws Exception { + SimpleTestConfigBeanB bean = new SimpleTestConfigBeanB(); + bean.dataB = dataB; + bean.dataBB = dataBB; + return bean; + } + + @Override + public SimpleTestConfigBeanBConfigurer setData(String data) { + this.dataB = data; + return this; + } + + @Override + public SimpleTestConfigBeanBConfigurer setDataBB(String data) { + this.dataBB = data; + return this; + } + + @Override + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java new file mode 100644 index 000000000..0d7b82042 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java @@ -0,0 +1,26 @@ +/* + * 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.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +public interface SimpleTestConfigBeanBConfigurer { + + SimpleTestConfigBeanBConfigurer setData(String data); + SimpleTestConfigBeanBConfigurer setDataBB(String data); + ResourceConfigurer withResources() throws Exception; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java new file mode 100644 index 000000000..2f47d69fe --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.common.annotation.simple; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultPropertiesConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.PropertiesConfigurerAware; + + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfig}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBuilder extends AbstractConfiguredAnnotationBuilder + implements PropertiesConfigurerAware { + + private final Properties properties = new Properties(); + + @Override + protected SimpleTestConfig performBuild() throws Exception { + SimpleTestConfig bean = new SimpleTestConfig("simpleData", properties); + bean.simpleBeanA = getSharedObject(SimpleTestConfigBeanABuilder.class).build(); + bean.simpleBeanB = getSharedObject(SimpleTestConfigBeanBBuilder.class).build(); + return bean; + } + + @Override + public void configureProperties(Properties properties) { + getProperties().putAll(properties); + } + + public Properties getProperties() { + return properties; + } + + public DefaultPropertiesConfigurer withProperties() throws Exception { + return getOrApply(new DefaultPropertiesConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java new file mode 100644 index 000000000..d726bf0c0 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java @@ -0,0 +1,63 @@ +/* + * 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.common.annotation.simple; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * @{@link Configuration} which is imported from @{@ EnableSimpleTest}. + * + * @author Janne Valkealahti + * + */ +@Configuration +public class SimpleTestConfiguration extends AbstractAnnotationConfiguration { + + private final SimpleTestConfigBuilder builder = new SimpleTestConfigBuilder(); + + @Bean(name="simpleConfig") + public SimpleTestConfig simpleTestConfig() { + SimpleTestConfig config = builder.getOrBuild(); + return config; + } + + @Bean(name="simpleConfigData") + public String simpleTestConfigData() { + SimpleTestConfig config = builder.getOrBuild(); + return config.simpleData; + } + + @Bean(name="simpleConfigBeanB") + public SimpleTestConfigBeanB simpleTestConfigBeanB() { + SimpleTestConfig config = builder.getOrBuild(); + return config.simpleBeanB; + } + + @Override + protected void onConfigurers(List> configurers) throws Exception { + for (AnnotationConfigurer configurer : configurers) { + if (configurer.isAssignable(builder)) { + builder.apply(configurer); + } + } + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java new file mode 100644 index 000000000..269a15af8 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java @@ -0,0 +1,67 @@ +/* + * 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.common.annotation.simple; + +import java.lang.annotation.Annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +@Configuration +public class SimpleTestConfiguration2 extends AbstractImportingAnnotationConfiguration { + + private final SimpleTestConfigBuilder builder = new SimpleTestConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(SimpleTestConfigDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableSimpleTest2.class; + } + + private static class SimpleTestConfigDelegatingFactoryBean extends BeanDelegatingFactoryBean { + + public SimpleTestConfigDelegatingFactoryBean(SimpleTestConfigBuilder builder) { + super(builder); + } + + @Override + public Class getObjectType() { + return SimpleTestConfig.class; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer configurer : getConfigurers()) { + if (configurer.isAssignable(getBuilder())) { + getBuilder().apply(configurer); + } + } + setObject(getBuilder().getOrBuild()); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java new file mode 100644 index 000000000..1e309cb4e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java @@ -0,0 +1,32 @@ +/* + * 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.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * Example of an interface used in {@link SimpleTestConfigurerAdapter}. + * + * @author Janne Valkealahti + * + */ +public interface SimpleTestConfigurer extends AnnotationConfigurer { + + void configure(SimpleTestConfigBeanABuilder a) throws Exception; + + void configure(SimpleTestConfigBeanBConfigurer b) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java new file mode 100644 index 000000000..7e93c9e43 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java @@ -0,0 +1,74 @@ +/* + * 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.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Generic adapter example which user would extend + * in @{@link org.springframework.context.annotation.Configuration} + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigurerAdapter implements SimpleTestConfigurer { + + private SimpleTestConfigBeanABuilder beanABuilder; + private SimpleTestConfigBeanBBuilder beanBBuilder; + + @Override + public final void init(SimpleTestConfigBuilder config) throws Exception { + config.setSharedObject(String.class, "simpleSharedData"); + config.setSharedObject(SimpleTestConfigBeanABuilder.class, getSimpleTestConfigBeanABuilder()); + config.setSharedObject(SimpleTestConfigBeanBBuilder.class, getSimpleTestConfigBeanBBuilder()); + } + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + } + + @Override + public void configure(SimpleTestConfigBeanABuilder a) throws Exception { + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer b) throws Exception { + } + + protected final SimpleTestConfigBeanBBuilder getSimpleTestConfigBeanBBuilder() throws Exception { + if (beanBBuilder != null) { + return beanBBuilder; + } + beanBBuilder = new SimpleTestConfigBeanBBuilder(); + configure(beanBBuilder); + return beanBBuilder; + } + + protected final SimpleTestConfigBeanABuilder getSimpleTestConfigBeanABuilder() throws Exception { + if (beanABuilder != null) { + return beanABuilder; + } + beanABuilder = new SimpleTestConfigBeanABuilder(); + configure(beanABuilder); + return beanABuilder; + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return builder instanceof SimpleTestConfigBuilder; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java new file mode 100644 index 000000000..cf959bbcc --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java @@ -0,0 +1,172 @@ +/* + * 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.guard; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.AbstractStateMachineTests.TestAction; +import org.springframework.statemachine.AbstractStateMachineTests.TestEvents; +import org.springframework.statemachine.AbstractStateMachineTests.TestGuard; +import org.springframework.statemachine.AbstractStateMachineTests.TestStates; +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; + +/** + * Tests for state machine guards. + * + * @author Janne Valkealahti + * + */ +public class GuardTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardEvaluated() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + assertThat(testGuard, notNullValue()); + assertThat(testAction, notNullValue()); + + machine.start(); + machine.sendEvent(TestEvents.E1); + assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + + ctx.close(); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardDenyAction() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config2.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + assertThat(testGuard, notNullValue()); + assertThat(testAction, notNullValue()); + + machine.start(); + assertThat(machine.getState().getId(), is(TestStates.S1)); + + machine.sendEvent(TestEvents.E1); + assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(false)); + assertThat(machine.getState().getId(), is(TestStates.S1)); + + ctx.close(); + } + + @Configuration + @EnableStateMachine + public 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) + .action(testAction()) + .guard(testGuard()); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(true); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + @Configuration + @EnableStateMachine + public 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) + .action(testAction()) + .guard(testGuard()); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(false); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java new file mode 100644 index 000000000..b0a52dd25 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java @@ -0,0 +1,105 @@ +/* + * 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.guard; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +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; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.support.DefaultStateContext; + +/** + * Tests for using spel expressions in guards. + * + * @author Janne Valkealahti + * + */ +public class SpelExpressionGuardTests extends AbstractStateMachineTests { + + @Test + public void testSimpleSpel() { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + Expression expression = parser.parseExpression("messageHeaders.get('foo')=='bar'"); + SpelExpressionGuard guard = new SpelExpressionGuard(expression); + Map map = new HashMap(); + map.put("foo", "bar"); + MessageHeaders headers = new MessageHeaders(map); + DefaultStateContext stateContext = new DefaultStateContext(headers, null); + + assertThat(guard.evaluate(stateContext), is(true)); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardDenyStateChange() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S1)); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public 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) + .guardExpression("false"); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java new file mode 100644 index 000000000..fdddac9cf --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java @@ -0,0 +1,173 @@ +/* + * 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.listener; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +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; +import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.state.State; + +/** + * Tests for state machine listener functionality. + * + * @author Janne Valkealahti + * + */ +public class ListenerTests extends AbstractStateMachineTests { + + @Test + public void testStateEvents() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + @SuppressWarnings("unchecked") + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + TestStateMachineListener listener = new TestStateMachineListener(); + machine.addStateListener(listener); + + assertThat(machine, notNullValue()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build()); + assertThat(listener.states.size(), is(1)); + assertThat(listener.states.get(0).from.getId(), is(TestStates.S1)); + assertThat(listener.states.get(0).to.getId(), is(TestStates.S2)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build()); + assertThat(listener.states.size(), is(2)); + assertThat(listener.states.get(1).from.getId(), is(TestStates.S2)); + assertThat(listener.states.get(1).to.getId(), is(TestStates.S3)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build()); + assertThat(listener.states.size(), is(2)); + + ctx.close(); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); + } + + } + + private static class TestStateMachineListener implements StateMachineListener, TestEvents> { + + ArrayList states = new ArrayList(); + + @Override + public void stateChanged(State from, State to) { + states.add(new Holder(from, to)); + } + + static class Holder { + State from; + State to; + public Holder(State from, State to) { + this.from = from; + this.to = to; + } + } + + } + + @Configuration + @EnableStateMachine + static class Config extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3, TestEvents.E4) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(loggingAction()) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S4) + .target(TestStates.S3) + .event(TestEvents.E4) + .action(loggingAction()); + } + + @Bean + public LoggingAction loggingAction() { + return new LoggingAction("as bean"); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java new file mode 100644 index 000000000..ba37a50c2 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java @@ -0,0 +1,109 @@ +/* + * 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.state; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +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; + +/** + * Tests for state entry and exit actions. + * + * @author Janne Valkealahti + * + */ +public class StateActionTests extends AbstractStateMachineTests { + + @Test + @SuppressWarnings("unchecked") + public void testStateEntryExit() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestExitAction testExitAction = ctx.getBean("testExitAction", TestExitAction.class); + TestEntryAction testEntryAction = ctx.getBean("testEntryAction", TestEntryAction.class); + assertThat(testExitAction, notNullValue()); + assertThat(testEntryAction, notNullValue()); + + machine.start(); + machine.sendEvent(TestEvents.E1); + assertThat(testExitAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testEntryAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + Collection entryActions = Arrays.asList(testEntryAction()); + Collection exitActions = Arrays.asList(testExitAction()); + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1, null, exitActions) + .state(TestStates.S2, entryActions, null); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + + @Bean + public Action testEntryAction() { + return new TestEntryAction(); + } + + @Bean + public Action testExitAction() { + return new TestExitAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java new file mode 100644 index 000000000..eacf4e1f6 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java @@ -0,0 +1,174 @@ +/* + * 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.transition; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +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.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +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; + +/** + * Tests for state machine transitions. + * + * @author Janne Valkealahti + * + */ +public class TransitionTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testTriggerlessTransition() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S3)); + ctx.close(); + + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testInternalTransition() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config2.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + TestExitAction testExitAction = ctx.getBean("testExitAction", TestExitAction.class); + TestEntryAction testEntryAction = ctx.getBean("testEntryAction", TestEntryAction.class); + TestAction externalTestAction = ctx.getBean("externalTestAction", TestAction.class); + TestAction internalTestAction = ctx.getBean("internalTestAction", TestAction.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + + machine.sendEvent(TestEvents.E1); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(internalTestAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + + machine.sendEvent(TestEvents.E2); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(externalTestAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + + assertThat(machine.getState().getId(), is(TestStates.S2)); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public 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) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + Collection entryActions = Arrays.asList(testEntryAction()); + Collection exitActions = Arrays.asList(testExitAction()); + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1, null, exitActions) + .state(TestStates.S2, entryActions, null); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withInternal() + .source(TestStates.S1) + .event(TestEvents.E1) + .action(internalTestAction()) + .and() + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E2) + .action(externalTestAction()); + } + + @Bean + public Action testEntryAction() { + return new TestEntryAction(); + } + + @Bean + public Action testExitAction() { + return new TestExitAction(); + } + + @Bean + public Action externalTestAction() { + return new TestAction(); + } + + @Bean + public Action internalTestAction() { + return new TestAction(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml b/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml new file mode 100644 index 000000000..e651bb31e --- /dev/null +++ b/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml @@ -0,0 +1,10 @@ + + + + + + + +