From ba1729c92814a5d571c3cfac1db1420f9a62e490 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 9 Apr 2016 16:38:41 +0100 Subject: [PATCH] Add base support for exit/entry points - New concepts of using exit and entry points to transition between sub-states. - Add hooks to annotation config model. - Relates to #192 --- .../config/AbstractStateMachineFactory.java | 58 +++++++- .../StateMachineTransitionBuilder.java | 91 ++++++++++++- .../StateMachineTransitionConfigurer.java | 17 +++ .../DefaultChoiceTransitionConfigurer.java | 2 +- .../DefaultEntryTransitionConfigurer.java | 54 ++++++++ .../DefaultExitTransitionConfigurer.java | 54 ++++++++ .../DefaultExternalTransitionConfigurer.java | 2 +- .../DefaultInternalTransitionConfigurer.java | 2 +- .../DefaultLocalTransitionConfigurer.java | 2 +- .../configurers/DefaultStateConfigurer.java | 30 +++-- .../EntryTransitionConfigurer.java | 49 +++++++ .../configurers/ExitTransitionConfigurer.java | 49 +++++++ .../config/configurers/StateConfigurer.java | 17 ++- .../statemachine/config/model/EntryData.java | 56 ++++++++ .../statemachine/config/model/ExitData.java | 56 ++++++++ .../config/model/TransitionsData.java | 29 +++- .../statemachine/state/EntryPseudoState.java | 59 +++++++++ .../statemachine/state/ExitPseudoState.java | 69 ++++++++++ .../statemachine/state/PseudoStateKind.java | 9 +- .../statemachine/state/StateHolder.java | 56 ++++++++ .../support/AbstractStateMachine.java | 8 ++ .../config/model/StateMachineModelTests.java | 2 +- .../state/ExitEntryStateTests.java | 124 ++++++++++++++++++ 23 files changed, 863 insertions(+), 32 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultEntryTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExitTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/EntryTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExitTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/EntryData.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ExitData.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EntryPseudoState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ExitPseudoState.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateHolder.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ExitEntryStateTests.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java index 117dfb13..2b401f38 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java @@ -38,6 +38,8 @@ import org.springframework.statemachine.access.StateMachineAccess; import org.springframework.statemachine.access.StateMachineFunction; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.model.ChoiceData; +import org.springframework.statemachine.config.model.EntryData; +import org.springframework.statemachine.config.model.ExitData; import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.config.model.StateMachineModel; import org.springframework.statemachine.config.model.TransitionData; @@ -52,6 +54,8 @@ import org.springframework.statemachine.state.AbstractState; import org.springframework.statemachine.state.ChoicePseudoState; import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData; import org.springframework.statemachine.state.DefaultPseudoState; +import org.springframework.statemachine.state.EntryPseudoState; +import org.springframework.statemachine.state.ExitPseudoState; import org.springframework.statemachine.state.ForkPseudoState; import org.springframework.statemachine.state.HistoryPseudoState; import org.springframework.statemachine.state.JoinPseudoState; @@ -59,6 +63,7 @@ import org.springframework.statemachine.state.PseudoState; import org.springframework.statemachine.state.PseudoStateKind; import org.springframework.statemachine.state.RegionState; import org.springframework.statemachine.state.State; +import org.springframework.statemachine.state.StateHolder; import org.springframework.statemachine.state.StateMachineState; import org.springframework.statemachine.support.DefaultExtendedState; import org.springframework.statemachine.support.LifecycleObjectSupport; @@ -141,6 +146,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS Stack> regionStack = new Stack>(); Stack> stateStack = new Stack>(); Map> machineMap = new HashMap>(); + Map> holderMap = new HashMap>(); Iterator>> iterator = buildStateDataIterator(); while (iterator.hasNext()) { @@ -176,7 +182,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS if (initialCount > 1) { for (Collection> regionStateDatas : regionsStateDatas) { - machine = buildMachine(machineMap, stateMap, regionStateDatas, transitionsData, resolveBeanFactory(), + machine = buildMachine(machineMap, stateMap, holderMap, regionStateDatas, transitionsData, resolveBeanFactory(), contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(), resolveTaskScheduler(), machineId); regionStack.push(new MachineStackItem(machine)); @@ -204,7 +210,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS machine = m; } } else { - machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, resolveBeanFactory(), contextEvents, + machine = buildMachine(machineMap, stateMap, holderMap, stateDatas, transitionsData, resolveBeanFactory(), contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(), resolveTaskScheduler(), machineId); if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) { @@ -261,6 +267,12 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS machine.addStateListener(listener); } + // go through holders and fix state references which + // were not known at a time holder was created + for (Entry> holder : holderMap.entrySet()) { + holder.getValue().setState(stateMap.get(holder.getKey())); + } + return delegateAutoStartup(machine); } @@ -397,12 +409,10 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS } - private StateMachine buildMachine( - Map> machineMap, Map> stateMap, - Collection> stateDatas, Collection> transitionsData, + private StateMachine buildMachine(Map> machineMap, Map> stateMap, + Map> holderMap, Collection> stateDatas, Collection> transitionsData, BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState, - TransitionsData stateMachineTransitions, TaskExecutor taskExecutor, - TaskScheduler taskScheduler, String machineId) { + TransitionsData stateMachineTransitions, TaskExecutor taskExecutor, TaskScheduler taskScheduler, String machineId) { State state = null; State initialState = null; PseudoState historyState = null; @@ -456,6 +466,10 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS continue; } else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) { continue; + } else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) { + continue; + } else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) { + continue; } state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), stateData.getExitActions(), pseudoState); @@ -482,6 +496,36 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS stateData.getExitActions(), pseudoState); states.add(state); stateMap.put(stateData.getState(), state); + } else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) { + S s = stateData.getState(); + Collection> entrys = stateMachineTransitions.getEntrys(); + for (EntryData entry : entrys) { + if (s.equals(entry.getSource())) { + PseudoState pseudoState = new EntryPseudoState(stateMap.get(entry.getTarget())); + state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), + stateData.getExitActions(), pseudoState); + states.add(state); + stateMap.put(stateData.getState(), state); + break; + } + } + } else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) { + S s = stateData.getState(); + Collection> exits = stateMachineTransitions.getExits(); + for (ExitData entry : exits) { + if (s.equals(entry.getSource())) { + StateHolder holder = new StateHolder(stateMap.get(entry.getTarget())); + if (holder.getState() == null) { + holderMap.put(entry.getTarget(), holder); + } + PseudoState pseudoState = new ExitPseudoState(holder); + state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), + stateData.getExitActions(), pseudoState); + states.add(state); + stateMap.put(stateData.getState(), state); + break; + } + } } else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) { S s = stateData.getState(); List list = stateMachineTransitions.getForks().get(s); 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 index 5d70ad86..79654676 100644 --- 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 @@ -27,11 +27,15 @@ import org.springframework.statemachine.config.common.annotation.AnnotationBuild import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultChoiceTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultEntryTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultForkTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultJoinTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.EntryTransitionConfigurer; +import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; @@ -39,6 +43,8 @@ import org.springframework.statemachine.config.configurers.JoinTransitionConfigu import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer; import org.springframework.statemachine.config.model.ChoiceData; import org.springframework.statemachine.config.model.ConfigurationData; +import org.springframework.statemachine.config.model.EntryData; +import org.springframework.statemachine.config.model.ExitData; import org.springframework.statemachine.config.model.TransitionsData; import org.springframework.statemachine.config.model.TransitionData; import org.springframework.statemachine.guard.Guard; @@ -62,23 +68,39 @@ public class StateMachineTransitionBuilder private final Map>> choices = new HashMap>>(); private final Map> forks = new HashMap>(); private final Map> joins = new HashMap>(); + private final Collection> entryData = new ArrayList>(); + private final Collection> exitData = new ArrayList>(); + /** + * Instantiates a new state machine transition builder. + */ public StateMachineTransitionBuilder() { super(); } + /** + * Instantiates a new state machine transition builder. + * + * @param objectPostProcessor the object post processor + * @param allowConfigurersOfSameType the allow configurers of same type + */ public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor, boolean allowConfigurersOfSameType) { super(objectPostProcessor, allowConfigurersOfSameType); } + /** + * Instantiates a new state machine transition builder. + * + * @param objectPostProcessor the object post processor + */ public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor) { super(objectPostProcessor); } @Override protected TransitionsData performBuild() throws Exception { - return new TransitionsData(transitionData, choices, forks, joins); + return new TransitionsData(transitionData, choices, forks, joins, entryData, exitData); } @Override @@ -111,7 +133,31 @@ public class StateMachineTransitionBuilder return apply(new DefaultJoinTransitionConfigurer()); } - public void add(S source, S target, S state, E event, Long period, Integer count, Collection> actions, + @Override + public EntryTransitionConfigurer withEntry() throws Exception { + return apply(new DefaultEntryTransitionConfigurer()); + } + + @Override + public ExitTransitionConfigurer withExit() throws Exception { + return apply(new DefaultExitTransitionConfigurer()); + } + + /** + * Adds the transition. + * + * @param source the source + * @param target the target + * @param state the state + * @param event the event + * @param period the period + * @param count the count + * @param actions the actions + * @param guard the guard + * @param kind the kind + * @param securityRule the security rule + */ + public void addTransition(S source, S target, S state, E event, Long period, Integer count, Collection> actions, Guard guard, TransitionKind kind, SecurityRule securityRule) { // if rule not given, get it from global if (securityRule == null) { @@ -122,16 +168,53 @@ public class StateMachineTransitionBuilder transitionData.add(new TransitionData(source, target, state, event, period, count, actions, guard, kind, securityRule)); } - public void add(S source, List> choices) { + /** + * Adds the choice. + * + * @param source the source + * @param choices the choices + */ + public void addChoice(S source, List> choices) { this.choices.put(source, choices); } + /** + * Adds the entry. + * + * @param source the source + * @param target the target + */ + public void addEntry(S source, S target) { + this.entryData.add(new EntryData(source, target)); + } + + /** + * Adds the exit. + * + * @param source the source + * @param target the target + */ + public void addExit(S source, S target) { + this.exitData.add(new ExitData(source, target)); + } + + /** + * Adds the fork. + * + * @param source the source + * @param targets the targets + */ public void addFork(S source, List targets) { this.forks.put(source, targets); } + /** + * Adds the join. + * + * @param target the target + * @param sources the sources + */ public void addJoin(S target, List sources) { this.joins.put(target, sources); } - } 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 index fd7362af..63e0adfd 100644 --- 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 @@ -16,6 +16,8 @@ package org.springframework.statemachine.config.builders; import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer; +import org.springframework.statemachine.config.configurers.EntryTransitionConfigurer; +import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; @@ -88,4 +90,19 @@ public interface StateMachineTransitionConfigurer { */ JoinTransitionConfigurer withJoin() throws Exception; + /** + * Gets a configurer for transition from an entrypoint pseudostate. + * + * @return {@link EntryTransitionConfigurer} for chaining + * @throws Exception if configuration error happens + */ + EntryTransitionConfigurer withEntry() throws Exception; + + /** + * Gets a configurer for transition from an exitpoint pseudostate. + * + * @return {@link ExitTransitionConfigurer} for chaining + * @throws Exception if configuration error happens + */ + ExitTransitionConfigurer withExit() throws Exception; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java index 0fe21ff2..0efe60a4 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultChoiceTransitionConfigurer.java @@ -52,7 +52,7 @@ public class DefaultChoiceTransitionConfigurer if (last != null) { choices.add(last); } - builder.add(source, choices); + builder.addChoice(source, choices); } @Override diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultEntryTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultEntryTransitionConfigurer.java new file mode 100644 index 00000000..97a94fe7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultEntryTransitionConfigurer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.config.model.TransitionsData; + +/** + * Default implementation of a {@link EntryTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultEntryTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements EntryTransitionConfigurer { + + private S source; + private S target; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.addEntry(source, target); + } + + @Override + public EntryTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public EntryTransitionConfigurer target(S target) { + this.target = target; + return this; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExitTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExitTransitionConfigurer.java new file mode 100644 index 00000000..65d8d5b7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExitTransitionConfigurer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.config.model.TransitionsData; + +/** + * Default implementation of a {@link ExitTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultExitTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements ExitTransitionConfigurer { + + private S source; + private S target; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.addExit(source, target); + } + + @Override + public ExitTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public ExitTransitionConfigurer target(S target) { + this.target = target; + return this; + } +} 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 index edd9c103..71d6d0bf 100644 --- 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 @@ -38,7 +38,7 @@ public class DefaultExternalTransitionConfigurer extends AbstractTransitio @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { - builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL, + builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL, getSecurityRule()); } 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 index 54e7ae8d..22c8e7d3 100644 --- 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 @@ -38,7 +38,7 @@ public class DefaultInternalTransitionConfigurer extends AbstractTransitio @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { - builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL, + builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL, getSecurityRule()); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java index 40bb1deb..c3f2f3b0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java @@ -37,7 +37,7 @@ public class DefaultLocalTransitionConfigurer extends AbstractTransitionCo @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { - builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL, + builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL, getSecurityRule()); } 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 index ef9408d3..410d1e5a 100644 --- 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 @@ -44,26 +44,18 @@ public class DefaultStateConfigurer implements StateConfigurer { private Object parent; - private final Object region = UUID.randomUUID().toString(); - private final Map> incomplete = new HashMap>(); - private S initialState; - private Action initialAction; - private S end; - private S history; - private History historyType; - private final Collection choices = new ArrayList(); - private final Collection forks = new ArrayList(); - private final Collection joins = new ArrayList(); + private final Collection exits = new ArrayList(); + private final Collection entrys = new ArrayList(); @Override public void configure(StateMachineStateBuilder builder) throws Exception { @@ -86,6 +78,10 @@ public class DefaultStateConfigurer s.setPseudoStateKind(PseudoStateKind.FORK); } else if (joins.contains(s.getState())) { s.setPseudoStateKind(PseudoStateKind.JOIN); + } else if (entrys.contains(s.getState())) { + s.setPseudoStateKind(PseudoStateKind.ENTRY); + } else if (exits.contains(s.getState())) { + s.setPseudoStateKind(PseudoStateKind.EXIT); } if (s.getState() == history) { if (History.SHALLOW == historyType) { @@ -199,6 +195,20 @@ public class DefaultStateConfigurer return this; } + @Override + public StateConfigurer entry(S entry) { + state(entry); + entrys.add(entry); + return this; + } + + @Override + public StateConfigurer exit(S exit) { + state(exit); + exits.add(exit); + return this; + } + private void addIncomplete(Object parent, S state, Collection deferred, Collection> entryActions, Collection> exitActions) { StateData stateData = incomplete.get(state); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/EntryTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/EntryTransitionConfigurer.java new file mode 100644 index 00000000..a596dd99 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/EntryTransitionConfigurer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 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.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring {@link Transition} + * with an entrypoint pseudo state. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface EntryTransitionConfigurer + extends AnnotationConfigurerBuilder> { + + /** + * Specify a source state {@code S} for this {@link Transition}. + * + * @param source the source state {@code S} + * @return configurer for chaining + */ + EntryTransitionConfigurer source(S source); + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + EntryTransitionConfigurer target(S target); +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExitTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExitTransitionConfigurer.java new file mode 100644 index 00000000..2e2f0917 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExitTransitionConfigurer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 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.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring {@link Transition} + * with an exitpoint pseudo state. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface ExitTransitionConfigurer + extends AnnotationConfigurerBuilder> { + + /** + * Specify a source state {@code S} for this {@link Transition}. + * + * @param source the source state {@code S} + * @return configurer for chaining + */ + ExitTransitionConfigurer source(S source); + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + ExitTransitionConfigurer target(S target); +} 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 index a15b700d..62424a19 100644 --- 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 @@ -149,6 +149,22 @@ public interface StateConfigurer extends */ StateConfigurer history(S history, History type); + /** + * Specify a state {@code S} to be entrypoint pseudo state. + * + * @param entry the entrypoint pseudo state + * @return configurer for chaining + */ + StateConfigurer entry(S entry); + + /** + * Specify a state {@code S} to be exitpoint pseudo state. + * + * @param exit the exitpoint pseudo state + * @return configurer for chaining + */ + StateConfigurer exit(S exit); + /** * Enumeration of a possible history pseudostate type. */ @@ -166,5 +182,4 @@ public interface StateConfigurer extends */ DEEP } - } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/EntryData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/EntryData.java new file mode 100644 index 00000000..b65fb89c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/EntryData.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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.model; + +/** + * A simple data object keeping entrypoint related configs in a same place. + * + * @param the type of state + * @param the type of event + */ +public class EntryData { + private final S source; + private final S target; + + /** + * Instantiates a new entry data. + * + * @param source the source + * @param target the target + */ + public EntryData(S source, S target) { + this.source = source; + this.target = target; + } + + /** + * Gets the source. + * + * @return the source + */ + public S getSource() { + return source; + } + + /** + * Gets the target. + * + * @return the target + */ + public S getTarget() { + return target; + } +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ExitData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ExitData.java new file mode 100644 index 00000000..a7bafc9f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ExitData.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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.model; + +/** + * A simple data object keeping exitpoint related configs in a same place. + * + * @param the type of state + * @param the type of event + */ +public class ExitData { + private final S source; + private final S target; + + /** + * Instantiates a new exit data. + * + * @param source the source + * @param target the target + */ + public ExitData(S source, S target) { + this.source = source; + this.target = target; + } + + /** + * Gets the source. + * + * @return the source + */ + public S getSource() { + return source; + } + + /** + * Gets the target. + * + * @return the target + */ + public S getTarget() { + return target; + } +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java index 36186330..f28ef153 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java @@ -33,6 +33,8 @@ public class TransitionsData { private final Map>> choices; private final Map> forks; private final Map> joins; + private final Collection> entrys; + private final Collection> exits; /** * Instantiates a new transitions data. @@ -40,7 +42,7 @@ public class TransitionsData { * @param transitionsData the transitions data */ public TransitionsData(Collection> transitionsData) { - this(transitionsData, null, null, null); + this(transitionsData, null, null, null, null, null); } /** @@ -50,13 +52,17 @@ public class TransitionsData { * @param choices the choices * @param forks the forks * @param joins the joins + * @param entrys the entrys + * @param exits the exits */ - public TransitionsData(Collection> transitionsData, - Map>> choices, Map> forks, Map> joins) { + public TransitionsData(Collection> transitionsData, Map>> choices, Map> forks, + Map> joins, Collection> entrys, Collection> exits) { this.transitions = transitionsData; this.choices = choices; this.forks = forks; this.joins = joins; + this.entrys = entrys; + this.exits = exits; } /** @@ -95,4 +101,21 @@ public class TransitionsData { return joins; } + /** + * Gets the entrys. + * + * @return the entrys + */ + public Collection> getEntrys() { + return entrys; + } + + /** + * Gets the exits. + * + * @return the exits + */ + public Collection> getExits() { + return exits; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EntryPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EntryPseudoState.java new file mode 100644 index 00000000..2be256a1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EntryPseudoState.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 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 org.springframework.statemachine.StateContext; + +/** + * Entrypoint implementation of a {@link PseudoState}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EntryPseudoState implements PseudoState { + + private final State state; + + /** + * Instantiates a new entry pseudo state. + * + * @param state the state + */ + public EntryPseudoState(State state) { + this.state = state; + } + + @Override + final public PseudoStateKind getKind() { + return PseudoStateKind.ENTRY; + } + + @Override + public State entry(StateContext context) { + return state; + } + + @Override + public void exit(StateContext context) { + } + + @Override + public void addPseudoStateListener(PseudoStateListener listener) { + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ExitPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ExitPseudoState.java new file mode 100644 index 00000000..6c76e6d8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ExitPseudoState.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 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 org.springframework.statemachine.StateContext; +import org.springframework.util.Assert; + +/** + * Exitpoint implementation of a {@link PseudoState}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class ExitPseudoState implements PseudoState { + + private StateHolder state; + + /** + * Instantiates a new exit pseudo state. + * + * @param state the state + */ + public ExitPseudoState(StateHolder state) { + Assert.notNull(state, "Holder must be set"); + this.state = state; + } + + @Override + final public PseudoStateKind getKind() { + return PseudoStateKind.EXIT; + } + + @Override + public State entry(StateContext context) { + return state.getState(); + } + + @Override + public void exit(StateContext context) { + } + + @Override + public void addPseudoStateListener(PseudoStateListener listener) { + } + + /** + * Gets the state holder. + * + * @return the state holder + */ + public StateHolder getStateHolder() { + return state; + } +} 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 index cba27e30..c2f5ff4c 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 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. @@ -43,6 +43,11 @@ public enum PseudoStateKind { FORK, /** Join kind */ - JOIN + JOIN, + /** Entrypoint kind */ + ENTRY, + + /** Exitpoint kind */ + EXIT } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateHolder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateHolder.java new file mode 100644 index 00000000..53f01fdd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateHolder.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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; + +/** + * Utility class using holder pattern to keep a {@link State} reference. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateHolder { + + private State state; + + /** + * Instantiates a new state holder. + * + * @param state the state + */ + public StateHolder(State state) { + this.state = state; + } + + /** + * Gets the state. + * + * @return the state + */ + public State getState() { + return state; + } + + /** + * Sets the state. + * + * @param state the state + */ + public void setState(State state) { + this.state = 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 30a683de..30aa124b 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 @@ -733,6 +733,14 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo callPreStateChangeInterceptors(toState, message, transition, stateMachine); } + setCurrentState(toState, message, transition, true, stateMachine); + } else if (kind == PseudoStateKind.ENTRY) { + StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); + State toState = state.getPseudoState().entry(stateContext); + setCurrentState(toState, message, transition, true, stateMachine); + } else if (kind == PseudoStateKind.EXIT) { + StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); + State toState = state.getPseudoState().entry(stateContext); setCurrentState(toState, message, transition, true, stateMachine); } else if (kind == PseudoStateKind.FORK) { ForkPseudoState fps = (ForkPseudoState) state.getPseudoState(); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java index a4cf81c1..4ae25f7b 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java @@ -76,7 +76,7 @@ public class StateMachineModelTests { Map>> choices = new HashMap<>(); Map> forks = new HashMap<>(); Map> joins = new HashMap<>(); - TransitionsData transitionsData = new TransitionsData<>(transitions, choices, forks, joins); + TransitionsData transitionsData = new TransitionsData<>(transitions, choices, forks, joins, null, null); StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData); ObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(stateMachineModel); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ExitEntryStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ExitEntryStateTests.java new file mode 100644 index 00000000..e0a1b7bf --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ExitEntryStateTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 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.contains; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +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.StateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class ExitEntryStateTests extends AbstractStateMachineTests { + + @SuppressWarnings("unchecked") + @Test + public void testSimpleEntryExit() { + context.register(Config1.class); + context.refresh(); + StateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + assertThat(machine, notNullValue()); + machine.start(); + assertThat(machine.getState().getIds(), contains("S1")); + machine.sendEvent("ENTRY1"); + assertThat(machine.getState().getIds(), contains("S2", "S22")); + machine.sendEvent("EXIT1"); + assertThat(machine.getState().getIds(), contains("S4")); + } + + @Configuration + @EnableStateMachine + static class Config1 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + // entry point takes to S22 instead of initial S21 + // exit point takes to S4 instead from S2 to S3 + states + .withStates() + .initial("S1") + .state("S2") + .state("S3") + .state("S4") + .state("S5") + .and() + .withStates() + .parent("S2") + .initial("S21") + .entry("S2ENTRY1") + .entry("S2ENTRY2") + .exit("S2EXIT1") + .exit("S2EXIT2") + .state("S22") + .state("S23"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source("S1").target("S2") + .event("E1") + .and() + .withExternal() + .source("S2").target("S3") + .event("E2") + .and() + .withExternal() + .source("S1").target("S2ENTRY1") + .event("ENTRY1") + .and() + .withExternal() + .source("S1").target("S2ENTRY2") + .event("ENTRY2") + .and() + .withExternal() + .source("S22").target("S2EXIT1") + .event("EXIT1") + .and() + .withExternal() + .source("S22").target("S2EXIT2") + .event("EXIT2") + .and() + .withEntry() + .source("S2ENTRY1").target("S22") + .and() + .withEntry() + .source("S2ENTRY2").target("S23") + .and() + .withExit() + .source("S2EXIT1").target("S4") + .and() + .withExit() + .source("S2EXIT2").target("S5"); + } + } + + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } +}