From b6a62b2509ce485a1035bf6b1c91822b11fabcc3 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Thu, 5 Feb 2015 14:30:29 +0000 Subject: [PATCH] Add base support for pseudostates - resolves #3 by adding base pseudostate support for initial state. - doesn't yet provide full support for any other pseudostate expect initial. - Also added initial event which is used when statemachine starts and activates initial state. --- build.gradle | 1 + .../statemachine/StateMachineException.java | 71 +++++++++++ .../config/EnumStateMachineFactory.java | 11 +- .../state/AbstractPseudoState.java | 42 +++++++ .../statemachine/state/AbstractState.java | 73 +++++++++-- .../state/DefaultPseudoState.java | 36 ++++++ .../statemachine/state/EnumState.java | 9 ++ .../statemachine/state/PseudoState.java | 41 ++++++ .../statemachine/state/PseudoStateKind.java | 30 +++++ .../statemachine/state/State.java | 9 ++ .../support/AbstractStateMachine.java | 44 +++++-- .../support/LifecycleObjectSupport.java | 2 +- .../AbstractStateMachineTests.java | 21 ++++ .../statemachine/state/InitialStateTests.java | 119 ++++++++++++++++++ 14 files changed, 490 insertions(+), 19 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineException.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractPseudoState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/DefaultPseudoState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoStateKind.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java diff --git a/build.gradle b/build.gradle index 637a0a4f..c2ab7a4a 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,7 @@ project('spring-statemachine-core') { description = "Spring State Machine Core" dependencies { + compile "org.springframework:spring-tx:$springVersion" compile "org.springframework:spring-messaging:$springVersion" testCompile "org.springframework:spring-test:$springVersion" diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineException.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineException.java new file mode 100644 index 00000000..fbd5b0d3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineException.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; + +import java.io.IOException; +import java.rmi.RemoteException; + +import org.springframework.dao.NonTransientDataAccessException; + +/** + * General exception indicating a problem in interacting with statemachine. + * + * @author Janne Valkealahti + * + */ +public class StateMachineException extends NonTransientDataAccessException { + + private static final long serialVersionUID = 4485522802268496000L; + + /** + * Constructs a generic StateMachineException. + * + * @param e the {@link RemoteException} + */ + public StateMachineException(IOException e) { + super(e.getMessage(), e); + } + + /** + * Constructs a generic StateMachineException. + * + * @param message the message + * @param e the exception + */ + public StateMachineException(String message, Exception e) { + super(message, e); + } + + /** + * Constructs a generic StateMachineException. + * + * @param message the message + * @param cause the throwable cause + */ + public StateMachineException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a generic StateMachineException. + * + * @param message the message + */ + public StateMachineException(String message) { + super(message); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index 2dc61697..4c126077 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -26,7 +26,10 @@ 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.DefaultPseudoState; import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.PseudoState; +import org.springframework.statemachine.state.PseudoStateKind; import org.springframework.statemachine.state.State; import org.springframework.statemachine.support.LifecycleObjectSupport; import org.springframework.statemachine.transition.DefaultExternalTransition; @@ -68,8 +71,14 @@ public class EnumStateMachineFactory, E extends Enum> exten public StateMachine, E> stateMachine() { Map> stateMap = new HashMap>(); for (StateData stateData : stateMachineStates.getStates()) { + + // TODO: doesn't feel right to tweak initial kind like this + PseudoState pseudoState = null; + if (stateData.getState() == stateMachineStates.getInitialState()) { + pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); + } stateMap.put(stateData.getState(), new EnumState(stateData.getState(), stateData.getDeferred(), - stateData.getEntryActions(), stateData.getExitActions())); + stateData.getEntryActions(), stateData.getExitActions(), pseudoState)); } Collection> transitions = new ArrayList>(); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractPseudoState.java new file mode 100644 index 00000000..8d03b93a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractPseudoState.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; + +/** + * Base implementation of a {@link PseudoState}. + * + * @author Janne Valkealahti + * + */ +public abstract class AbstractPseudoState implements PseudoState { + + private final PseudoStateKind kind; + + /** + * Instantiates a new abstract pseudo state. + * + * @param kind the kind + */ + public AbstractPseudoState(PseudoStateKind kind) { + this.kind = kind; + } + + @Override + public PseudoStateKind getKind() { + return kind; + } + +} 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 index c17f4daf..2556b239 100644 --- 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 @@ -19,32 +19,89 @@ import java.util.Collection; import org.springframework.statemachine.action.Action; +/** + * Base implementation of a {@link State}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ public abstract class AbstractState implements State { - private S id; - private Collection deferred; - private Collection entryActions; - private Collection exitActions; + private final S id; + private final PseudoState pseudoState; + private final Collection deferred; + private final Collection entryActions; + private final Collection exitActions; + /** + * Instantiates a new abstract state. + * + * @param id the id + */ public AbstractState(S id) { - this(id, null); + this(id, null, null, null, null); } + /** + * Instantiates a new abstract state. + * + * @param id the id + * @param pseudoState the pseudo state + */ + public AbstractState(S id, PseudoState pseudoState) { + this(id, null, null, null, pseudoState); + } + + /** + * Instantiates a new abstract state. + * + * @param id the id + * @param deferred the deferred + */ public AbstractState(S id, Collection deferred) { this(id, deferred, null, null); } + /** + * Instantiates a new abstract state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + */ public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + this(id, deferred, entryActions, exitActions, null); + } + + /** + * Instantiates a new abstract state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + */ + public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions, PseudoState pseudoState) { this.id = id; this.deferred = deferred; this.entryActions = entryActions; this.exitActions = exitActions; + this.pseudoState = pseudoState; } @Override public S getId() { return id; } + + @Override + public PseudoState getPseudoState() { + return pseudoState; + } @Override public Collection getDeferredEvents() { @@ -63,8 +120,8 @@ public abstract class AbstractState implements State { @Override public String toString() { - return "AbstractState [id=" + id + ", deferred=" + deferred + ", entryActions=" + entryActions - + ", exitActions=" + exitActions + "]"; + return "AbstractState [id=" + id + ", pseudoState=" + pseudoState + ", deferred=" + deferred + + ", entryActions=" + entryActions + ", exitActions=" + exitActions + "]"; } - + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/DefaultPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/DefaultPseudoState.java new file mode 100644 index 00000000..26cd595f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/DefaultPseudoState.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.state; + +/** + * Default implementation of a {@link PseudoState} which is a simple passthrough + * via {@link AbstractPseudoState}. + * + * @author Janne Valkealahti + * + */ +public class DefaultPseudoState extends AbstractPseudoState { + + /** + * Instantiates a new default pseudo state. + * + * @param kind the kind + */ + public DefaultPseudoState(PseudoStateKind kind) { + super(kind); + } + +} 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 index 4c85cbde..ce791b8c 100644 --- 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 @@ -25,6 +25,10 @@ public class EnumState, E extends Enum> extends AbstractSta super(id); } + public EnumState(S id, PseudoState pseudoState) { + super(id, pseudoState); + } + public EnumState(S id, Collection deferred) { super(id, deferred); } @@ -33,6 +37,11 @@ public class EnumState, E extends Enum> extends AbstractSta super(id, deferred, entryActions, exitActions); } + public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState) { + super(id, deferred, entryActions, exitActions, pseudoState); + } + @Override public String toString() { return "EnumState [getId()=" + getId() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoState.java new file mode 100644 index 00000000..61452493 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoState.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.state; + +/** + * A {@code PseudoState} is an abstraction that encompasses different types of + * transient states or vertices in the state machine. + * + *

+ * Pseudostates are typically used to connect multiple transitions into more + * complex state transitions paths. For example, by combining a transition + * entering a fork pseudostate with a set of transitions exiting the fork + * pseudostate, we get a compound transition that leads to a set of orthogonal + * target states. + * + * @author Janne Valkealahti + * + */ +public interface PseudoState { + + /** + * Gets the pseudostate kind. + * + * @return the pseudostate kind + */ + PseudoStateKind getKind(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoStateKind.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoStateKind.java new file mode 100644 index 00000000..f14bc4be --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/PseudoStateKind.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.state; + +/** + * Defines enumeration of a {@link PseudoState} kind. This is uses within a + * transitive states indicating its kind. + * + * @author Janne Valkealahti + * + */ +public enum PseudoStateKind { + + /** Indicates an initial kind. */ + INITIAL + +} 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 index 4055a227..15e835e3 100644 --- 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 @@ -36,6 +36,15 @@ public interface State { */ S getId(); + /** + * Gets a {@link PseudoState} attached to a {@code State}. + * {@link PseudoState} is not required and thus this method return + * {@code NULL} if it's not set. + * + * @return pseudostate or null if state doesn't have one + */ + PseudoState getPseudoState(); + /** * Gets the deferred events for this state. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 6d8efece..439be491 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -45,6 +45,7 @@ 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.PseudoStateKind; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; import org.springframework.statemachine.transition.TransitionKind; @@ -70,6 +71,8 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private final State initialState; + private final Message initialEvent; + private final ExtendedState extendedState; private final Queue> eventQueue = new ConcurrentLinkedQueue>(); @@ -79,7 +82,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private final CompositeStateMachineListener stateListener = new CompositeStateMachineListener(); private volatile State currentState; - + private volatile Runnable task; /** @@ -93,7 +96,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport State initialState) { this(states, transitions, initialState, new DefaultExtendedState()); } - + /** * Instantiates a new abstract state machine. * @@ -104,10 +107,25 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport */ public AbstractStateMachine(Collection> states, Collection> transitions, State initialState, ExtendedState extendedState) { + this(states, transitions, initialState, null, extendedState); + } + + /** + * 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 initialEvent the initial event of this machine + * @param extendedState the extended state of this machine + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState, Message initialEvent, ExtendedState extendedState) { super(); this.states = states; this.transitions = transitions; this.initialState = initialState; + this.initialEvent = initialEvent; this.extendedState = extendedState; } @@ -137,10 +155,19 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport sendEvent(MessageBuilder.withPayload(event).build()); } + @Override + protected void onInit() throws Exception { + super.onInit(); + Assert.notNull(initialState, "Initial state must be set"); + Assert.state(initialState.getPseudoState() != null + && initialState.getPseudoState().getKind() == PseudoStateKind.INITIAL, + "Initial state's pseudostate kind must be INITIAL"); + } + @Override protected void doStart() { super.doStart(); - switchToState(initialState, null); + switchToState(initialState, initialEvent); } @Override @@ -157,7 +184,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport public Collection> getStates() { return Collections.unmodifiableCollection(states); } - + private void switchToState(State state, Message event) { log.info("Moving into state=" + state + " from " + currentState); @@ -205,7 +232,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } } } - + private void processEventQueue() { log.debug("Process event queue"); Message queuedEvent = null; @@ -269,7 +296,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport 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( @@ -279,8 +306,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } } - - + private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { StateMachineRuntime runtime = new StateMachineRuntime() { @Override @@ -294,7 +320,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } return results; } - + private List getStateMachineHandlers(State sourceState, State targetState) { BeanFactory beanFactory = getBeanFactory(); 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 index 7776aebf..ea4987c6 100644 --- 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 @@ -41,7 +41,7 @@ public abstract class LifecycleObjectSupport implements InitializingBean, SmartL private static final Log log = LogFactory.getLog(LifecycleObjectSupport.class); // fields for lifecycle - private volatile boolean autoStartup = true; + private volatile boolean autoStartup = false; private volatile int phase = 0; private volatile boolean running; 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 index 4d95cb52..5e8aa5f8 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -17,6 +17,9 @@ package org.springframework.statemachine; import java.util.concurrent.CountDownLatch; +import org.junit.After; +import org.junit.Before; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SyncTaskExecutor; @@ -33,6 +36,24 @@ import org.springframework.statemachine.guard.Guard; */ public abstract class AbstractStateMachineTests { + protected AnnotationConfigApplicationContext context; + + @Before + public void setup() { + context = buildContext(); + } + + @After + public void clean() { + if (context != null) { + context.close(); + } + } + + protected AnnotationConfigApplicationContext buildContext() { + return null; + } + public enum TestStates { SI,S1,S2,S3,S4 } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java new file mode 100644 index 00000000..fc857111 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java @@ -0,0 +1,119 @@ +/* + * 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.Matchers.is; +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.Configuration; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for functionality around state machine initial state. + * + * @author Janne Valkealahti + * + */ +public class InitialStateTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testInitialStateTransition() throws Exception { + context.register(BaseConfig.class, Config1.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + } + + @Test(expected = Exception.class) + public void testInitialStateMissingFailure() throws Exception { + context.register(BaseConfig.class, Config2.class); + context.refresh(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S1) + .and() + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S1); + } + + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + +}