spring-projects / spring-statemachine

Spring Statemachine is a framework for application developers to use state machine concepts with Spring.
1.57k stars 613 forks source link

Cannot define unnamed choice: "Initial state must be set" error #591

Closed don-vip closed 5 years ago

don-vip commented 6 years ago

I'm trying to implement with 2.0.2 a simple state machine which begins by a choice. I am unable to do so and always face this error:

java.lang.IllegalArgumentException: Initial state must be set
    at org.springframework.util.Assert.notNull(Assert.java:193)
    at org.springframework.statemachine.support.AbstractStateMachine.onInit(AbstractStateMachine.java:236)
    at org.springframework.statemachine.support.LifecycleObjectSupport.afterPropertiesSet(LifecycleObjectSupport.java:67)
    at org.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:324)
    at org.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:140)
    at org.springframework.statemachine.config.configuration.StateMachineConfiguration$StateMachineDelegatingFactoryBean.afterPropertiesSet(StateMachineConfiguration.java:192)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1758)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1695)
    ... 21 more

I tried two variants of it, as I wondered if having two transitions towards the final state are allowed or not in UML: bug3

<?xml version="1.0" encoding="UTF-8"?>
<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_QJ9KwKuYEeihzLPJstSt-Q" name="StateMachineModel" URI="">
  <packageImport xmi:type="uml:PackageImport" xmi:id="_QaLYEKuYEeihzLPJstSt-Q">
    <importedPackage xmi:type="uml:Model" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#_0"/>
  </packageImport>
  <packagedElement xmi:type="uml:StateMachine" xmi:id="_QMmPEKuYEeihzLPJstSt-Q" name="StateMachine">
    <region xmi:type="uml:Region" xmi:id="_QOKVUKuYEeihzLPJstSt-Q" name="RegionRoot">
      <transition xmi:type="uml:Transition" xmi:id="_xuSyMKuaEeihzLPJstSt-Q" guard="_K1WHIK0EEeirou_2-u6x8w" source="_ozCHYKuaEeihzLPJstSt-Q" target="_CrZB8KuaEeihzLPJstSt-Q">
        <ownedRule xmi:type="uml:Constraint" xmi:id="_K1WHIK0EEeirou_2-u6x8w">
          <specification xmi:type="uml:OpaqueExpression" xmi:id="_K1WHIa0EEeirou_2-u6x8w">
            <language>bean</language>
            <body>myGuard</body>
          </specification>
        </ownedRule>
      </transition>
      <transition xmi:type="uml:Transition" xmi:id="_-iQz8KuaEeihzLPJstSt-Q" source="_ozCHYKuaEeihzLPJstSt-Q" target="_Q3waAKuaEeihzLPJstSt-Q"/>
      <transition xmi:type="uml:Transition" xmi:id="_AiMHcKxjEeiqCYIiCoZ3ZQ" source="_AtULsKuaEeihzLPJstSt-Q" target="_8rhX4KxiEeiqCYIiCoZ3ZQ"/>
      <transition xmi:type="uml:Transition" xmi:id="_BkiVQKxjEeiqCYIiCoZ3ZQ" source="_8rhX4KxiEeiqCYIiCoZ3ZQ" target="_ozCHYKuaEeihzLPJstSt-Q">
        <trigger xmi:type="uml:Trigger" xmi:id="_H3iNAK-HEeiMjcJy6nCivw" event="_lP_aIK0pEeivA8KwXNbRgw"/>
      </transition>
      <transition xmi:type="uml:Transition" xmi:id="_00cH0K-MEeiMjcJy6nCivw" source="_CrZB8KuaEeihzLPJstSt-Q" target="_UejloK-MEeiMjcJy6nCivw"/>
      <transition xmi:type="uml:Transition" xmi:id="_1mR0cK-MEeiMjcJy6nCivw" source="_Q3waAKuaEeihzLPJstSt-Q" target="_UejloK-MEeiMjcJy6nCivw"/>
      <transition xmi:type="uml:Transition" xmi:id="_2nvRcK-MEeiMjcJy6nCivw" source="_UejloK-MEeiMjcJy6nCivw" target="_B2-icKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:Pseudostate" xmi:id="_AtULsKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:FinalState" xmi:id="_B2-icKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:State" xmi:id="_CrZB8KuaEeihzLPJstSt-Q" name="S1"/>
      <subvertex xmi:type="uml:State" xmi:id="_Q3waAKuaEeihzLPJstSt-Q" name="S2"/>
      <subvertex xmi:type="uml:Pseudostate" xmi:id="_ozCHYKuaEeihzLPJstSt-Q" kind="choice"/>
      <subvertex xmi:type="uml:State" xmi:id="_8rhX4KxiEeiqCYIiCoZ3ZQ" name="S0"/>
      <subvertex xmi:type="uml:State" xmi:id="_UejloK-MEeiMjcJy6nCivw" name="S9"/>
    </region>
  </packagedElement>
  <packagedElement xmi:type="uml:Signal" xmi:id="_jr38AK0pEeivA8KwXNbRgw" name="E1"/>
  <packagedElement xmi:type="uml:SignalEvent" xmi:id="_lP_aIK0pEeivA8KwXNbRgw" name="E1Event" signal="_jr38AK0pEeivA8KwXNbRgw"/>
  <profileApplication xmi:type="uml:ProfileApplication" xmi:id="_1Wu4gKuaEeihzLPJstSt-Q">
    <eAnnotations xmi:type="ecore:EAnnotation" xmi:id="_1W4CcKuaEeihzLPJstSt-Q" source="http://www.eclipse.org/uml2/2.0.0/UML">
      <references xmi:type="ecore:EPackage" href="pathmap://PAPYRUS_ACTIONLANGUAGE_PROFILE/ActionLanguage-Profile.profile.uml#_Kv8EIKFXEeS_KNX0nfvIVQ"/>
    </eAnnotations>
    <appliedProfile xmi:type="uml:Profile" href="pathmap://PAPYRUS_ACTIONLANGUAGE_PROFILE/ActionLanguage-Profile.profile.uml#ActionLanguage"/>
  </profileApplication>
</uml:Model>

bug4

<?xml version="1.0" encoding="UTF-8"?>
<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_QJ9KwKuYEeihzLPJstSt-Q" name="StateMachineModel" URI="">
  <packageImport xmi:type="uml:PackageImport" xmi:id="_QaLYEKuYEeihzLPJstSt-Q">
    <importedPackage xmi:type="uml:Model" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#_0"/>
  </packageImport>
  <packagedElement xmi:type="uml:StateMachine" xmi:id="_QMmPEKuYEeihzLPJstSt-Q" name="StateMachine">
    <region xmi:type="uml:Region" xmi:id="_QOKVUKuYEeihzLPJstSt-Q" name="RegionRoot">
      <transition xmi:type="uml:Transition" xmi:id="_xuSyMKuaEeihzLPJstSt-Q" guard="_K1WHIK0EEeirou_2-u6x8w" source="_ozCHYKuaEeihzLPJstSt-Q" target="_CrZB8KuaEeihzLPJstSt-Q">
        <ownedRule xmi:type="uml:Constraint" xmi:id="_K1WHIK0EEeirou_2-u6x8w">
          <specification xmi:type="uml:OpaqueExpression" xmi:id="_K1WHIa0EEeirou_2-u6x8w">
            <language>bean</language>
            <body>myGuard</body>
          </specification>
        </ownedRule>
      </transition>
      <transition xmi:type="uml:Transition" xmi:id="_-iQz8KuaEeihzLPJstSt-Q" source="_ozCHYKuaEeihzLPJstSt-Q" target="_Q3waAKuaEeihzLPJstSt-Q"/>
      <transition xmi:type="uml:Transition" xmi:id="_AiMHcKxjEeiqCYIiCoZ3ZQ" source="_AtULsKuaEeihzLPJstSt-Q" target="_8rhX4KxiEeiqCYIiCoZ3ZQ"/>
      <transition xmi:type="uml:Transition" xmi:id="_BkiVQKxjEeiqCYIiCoZ3ZQ" source="_8rhX4KxiEeiqCYIiCoZ3ZQ" target="_ozCHYKuaEeihzLPJstSt-Q">
        <trigger xmi:type="uml:Trigger" xmi:id="_H3iNAK-HEeiMjcJy6nCivw" event="_lP_aIK0pEeivA8KwXNbRgw"/>
      </transition>
      <transition xmi:type="uml:Transition" xmi:id="_Y3oMwK-OEeiMjcJy6nCivw" source="_CrZB8KuaEeihzLPJstSt-Q" target="_B2-icKuaEeihzLPJstSt-Q"/>
      <transition xmi:type="uml:Transition" xmi:id="_Zx9HAK-OEeiMjcJy6nCivw" source="_Q3waAKuaEeihzLPJstSt-Q" target="_B2-icKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:Pseudostate" xmi:id="_AtULsKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:FinalState" xmi:id="_B2-icKuaEeihzLPJstSt-Q"/>
      <subvertex xmi:type="uml:State" xmi:id="_CrZB8KuaEeihzLPJstSt-Q" name="S1"/>
      <subvertex xmi:type="uml:State" xmi:id="_Q3waAKuaEeihzLPJstSt-Q" name="S2"/>
      <subvertex xmi:type="uml:Pseudostate" xmi:id="_ozCHYKuaEeihzLPJstSt-Q" kind="choice"/>
      <subvertex xmi:type="uml:State" xmi:id="_8rhX4KxiEeiqCYIiCoZ3ZQ" name="S0"/>
    </region>
  </packagedElement>
  <packagedElement xmi:type="uml:Signal" xmi:id="_jr38AK0pEeivA8KwXNbRgw" name="E1"/>
  <packagedElement xmi:type="uml:SignalEvent" xmi:id="_lP_aIK0pEeivA8KwXNbRgw" name="E1Event" signal="_jr38AK0pEeivA8KwXNbRgw"/>
  <profileApplication xmi:type="uml:ProfileApplication" xmi:id="_1Wu4gKuaEeihzLPJstSt-Q">
    <eAnnotations xmi:type="ecore:EAnnotation" xmi:id="_1W4CcKuaEeihzLPJstSt-Q" source="http://www.eclipse.org/uml2/2.0.0/UML">
      <references xmi:type="ecore:EPackage" href="pathmap://PAPYRUS_ACTIONLANGUAGE_PROFILE/ActionLanguage-Profile.profile.uml#_Kv8EIKFXEeS_KNX0nfvIVQ"/>
    </eAnnotations>
    <appliedProfile xmi:type="uml:Profile" href="pathmap://PAPYRUS_ACTIONLANGUAGE_PROFILE/ActionLanguage-Profile.profile.uml#ActionLanguage"/>
  </profileApplication>
</uml:Model>

But the result is the same. Here is my Java config:

@Configuration
@EnableStateMachine
public class SpringStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config
            .withConfiguration()
            .autoStartup(true)
            .listener(stateMachineLogListener());
    }

    @Override
    public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
        model
            .withModel()
            .factory(modelFactory());
    }

    @Bean
    public StateMachineModelFactory<String, String> modelFactory() {
        return new UmlStateMachineModelFactory("classpath:statemachines/bug/StateMachineModel.uml");
    }

    @Bean
    public StateMachineLogListener stateMachineLogListener() {
        return new StateMachineLogListener();
    }

    @Bean
    public Guard<String, String> myGuard() {
        return context -> {
            BusinessObject bo = new BusinessObject(); // TODO: retrieve object from context
            return "foo".equals(bo.getBar());
        };
    }
}

Is my diagram somewhat invalid or is it a bug? The message is not clear, as I have defined an initial state.

jvalkeal commented 6 years ago

Could you give that test project so that I could run it myself. I don't immediately where things might go wrong.

don-vip commented 6 years ago

Sure, I'm working on producing a standalone project. I started debugging the problem and noticed my machine initialization results in two internal machines created in AbstractStateMachineFactory.getStateMachine(UUID, String):

jvalkeal commented 6 years ago

Something goes wrong when that uml file is parsed and it's sometimes difficult to point exact issue until you follow debugs. I didn't see issue straight out from uml why parsing things there are states associated with multiple machines, as that would then try to create multiple machines. Think I find it quickly if I can run this project.

jvalkeal commented 6 years ago

Right, looks like a some sort of dump bug in a parser. Looks like when I added a name to a choice, it doesn't fail anymore.

<subvertex xmi:type="uml:Pseudostate" xmi:id="_ozCHYKuaEeihzLPJstSt-Q" kind="choice" name="CHOICE"/>

At least when I manually added this file to uml parsing tests. Can you verify this as well as I think you didn't name that choice field in papyrus. I remember doing some auto-naming for some fields but it may be that forget to check these pseudostates.

don-vip commented 6 years ago

I created a small Spring Boot project showing the problem. Simply run the application (it fails to start): spring-statemachine-issue591.zip

don-vip commented 6 years ago

Thanks for your quick analysis! I confirm adding a name to the choice solves the problem :)