diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineConfigurerAdapter.java index 71602361..4be2c21b 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineConfigurerAdapter.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineConfigurerAdapter.java @@ -16,6 +16,8 @@ package org.springframework.statemachine.config; import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateBuilder; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; @@ -36,17 +38,23 @@ public abstract class AbstractStateMachineConfigurerAdapter implements Sta private StateMachineTransitionBuilder transitionBuilder; private StateMachineStateBuilder stateBuilder; + private StateMachineConfigurationBuilder configurationBuilder; @Override public final void init(StateMachineConfigBuilder config) throws Exception { config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); + config.setSharedObject(StateMachineConfigurationBuilder.class, getStateMachineConfigurationBuilder()); } @Override public void configure(StateMachineConfigBuilder config) throws Exception { } + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + } + @Override public void configure(StateMachineStateConfigurer states) throws Exception { } @@ -78,4 +86,13 @@ public abstract class AbstractStateMachineConfigurerAdapter implements Sta return stateBuilder; } + protected final StateMachineConfigurationBuilder getStateMachineConfigurationBuilder() throws Exception { + if (configurationBuilder != null) { + return configurationBuilder; + } + configurationBuilder = new StateMachineConfigurationBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(configurationBuilder); + return configurationBuilder; + } + } 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 45d11442..df23585b 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 @@ -25,12 +25,15 @@ import java.util.Map.Entry; import java.util.Stack; import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; import org.springframework.messaging.Message; +import org.springframework.scheduling.TaskScheduler; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.access.StateMachineAccess; import org.springframework.statemachine.access.StateMachineFunction; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.builders.StateMachineTransitions.ChoiceData; @@ -77,16 +80,20 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS private final StateMachineStates stateMachineStates; + private final StateMachineConfigurationConfig stateMachineConfigurationConfig; + private Boolean contextEvents; /** * Instantiates a new enum state machine factory. * + * @param stateMachineConfigurationConfig the state machine generic config * @param stateMachineTransitions the state machine transitions * @param stateMachineStates the state machine states */ - public AbstractStateMachineFactory(StateMachineTransitions stateMachineTransitions, - StateMachineStates stateMachineStates) { + public AbstractStateMachineFactory(StateMachineConfigurationConfig stateMachineConfigurationConfig, + StateMachineTransitions stateMachineTransitions, StateMachineStates stateMachineStates) { + this.stateMachineConfigurationConfig = stateMachineConfigurationConfig; this.stateMachineTransitions = stateMachineTransitions; this.stateMachineStates = stateMachineStates; } @@ -144,7 +151,8 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS if (initialCount > 1) { for (Collection> regionStateDatas : regionsStateDatas) { machine = buildMachine(machineMap, stateMap, regionStateDatas, transitionsData, getBeanFactory(), - contextEvents, defaultExtendedState, stateMachineTransitions); + contextEvents, defaultExtendedState, stateMachineTransitions, getTaskExecutor(), + getTaskScheduler()); regionStack.push(new MachineStackItem(machine)); } @@ -163,12 +171,13 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS states.add(rstate); Transition initialTransition = new InitialTransition(rstate); StateMachine m = buildStateMachineInternal(states, new ArrayList>(), rstate, - initialTransition, null, defaultExtendedState, null, contextEvents, getBeanFactory()); + initialTransition, null, defaultExtendedState, null, contextEvents, getBeanFactory(), + getTaskExecutor(), getTaskScheduler()); machine = m; } } else { machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), - contextEvents, defaultExtendedState, stateMachineTransitions); + contextEvents, defaultExtendedState, stateMachineTransitions, getTaskExecutor(), getTaskScheduler()); if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) { machineMap.put(peek.getParent(), machine); } @@ -281,7 +290,8 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS Map> machineMap, Map> stateMap, Collection> stateDatas, Collection> transitionsData, BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState, - StateMachineTransitions stateMachineTransitions) { + StateMachineTransitions stateMachineTransitions, TaskExecutor taskExecutor, + TaskScheduler taskScheduler) { State state = null; State initialState = null; PseudoState historyState = null; @@ -451,13 +461,15 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS Transition initialTransition = new InitialTransition(initialState, initialAction); StateMachine machine = buildStateMachineInternal(states, transitions, initialState, initialTransition, null, - defaultExtendedState, historyState, contextEvents, beanFactory); + defaultExtendedState, historyState, contextEvents, beanFactory, taskExecutor, taskScheduler); return machine; } - protected abstract StateMachine buildStateMachineInternal(Collection> states, Collection> transitions, - State initialState, Transition initialTransition, - Message initialEvent, ExtendedState extendedState, PseudoState historyState, Boolean contextEventsEnabled, BeanFactory beanFactory); + protected abstract StateMachine buildStateMachineInternal(Collection> states, + Collection> transitions, State initialState, Transition initialTransition, + Message initialEvent, ExtendedState extendedState, PseudoState historyState, + Boolean contextEventsEnabled, BeanFactory beanFactory, TaskExecutor taskExecutor, + TaskScheduler taskScheduler); protected abstract State buildStateInternal(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/ObjectStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/ObjectStateMachineFactory.java index 6784330e..6b2317f1 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/ObjectStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/ObjectStateMachineFactory.java @@ -18,11 +18,14 @@ package org.springframework.statemachine.config; import java.util.Collection; import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; import org.springframework.messaging.Message; +import org.springframework.scheduling.TaskScheduler; import org.springframework.statemachine.ObjectStateMachine; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.state.ObjectState; @@ -41,15 +44,17 @@ import org.springframework.statemachine.transition.Transition; */ public class ObjectStateMachineFactory extends AbstractStateMachineFactory { - public ObjectStateMachineFactory(StateMachineTransitions stateMachineTransitions, + public ObjectStateMachineFactory(StateMachineConfigurationConfig stateMachineConfigurationConfig, StateMachineTransitions stateMachineTransitions, StateMachineStates stateMachineStates) { - super(stateMachineTransitions, stateMachineStates); + super(stateMachineConfigurationConfig, stateMachineTransitions, stateMachineStates); } @Override protected StateMachine buildStateMachineInternal(Collection> states, Collection> transitions, State initialState, Transition initialTransition, - Message initialEvent, ExtendedState extendedState, PseudoState historyState, Boolean contextEventsEnabled, BeanFactory beanFactory) { + Message initialEvent, ExtendedState extendedState, PseudoState historyState, + Boolean contextEventsEnabled, BeanFactory beanFactory, TaskExecutor taskExecutor, + TaskScheduler taskScheduler) { ObjectStateMachine machine = new ObjectStateMachine(states, transitions, initialState, initialTransition, initialEvent, extendedState); machine.setHistoryState(historyState); @@ -59,6 +64,12 @@ public class ObjectStateMachineFactory extends AbstractStateMachineFactory if (beanFactory != null) { machine.setBeanFactory(beanFactory); } + if (taskExecutor != null) { + machine.setTaskExecutor(taskExecutor); + } + if (taskScheduler != null) { + machine.setTaskScheduler(taskScheduler);; + } machine.afterPropertiesSet(); return machine; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineBuilder.java new file mode 100644 index 00000000..44fe306e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineBuilder.java @@ -0,0 +1,207 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config; + +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachineException; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; + +/** + * {@code StateMachineBuilder} provides a builder pattern for + * {@link StateMachine} using a similar concepts found from a + * normal annotation based configuration. + * + * @author Janne Valkealahti + * + */ +public class StateMachineBuilder { + + /** + * Gets a builder for a {@link StateMachine}. + * + * @param the type of state + * @param the type of event + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * {@code Builder} implementation handling logic of building + * a {@link StateMachine} manually. + * + * @param the type of state + * @param the type of event + */ + public static class Builder { + + private StateMachineConfigBuilder builder; + private BuilderStateMachineConfigurerAdapter adapter; + + /** + * Instantiates a new builder. + */ + public Builder() { + adapter = new BuilderStateMachineConfigurerAdapter(); + builder = new StateMachineConfigBuilder(); + } + + /** + * Configure configuration. + * + * @return the state machine configuration configurer + */ + public StateMachineConfigurationConfigurer configureConfiguration() { + return adapter.configurationBuilder; + } + + /** + * Configure states. + * + * @return the state machine state configurer + */ + public StateMachineStateConfigurer configureStates() { + return adapter.stateBuilder; + } + + /** + * Configure transitions. + * + * @return the state machine transition configurer + */ + public StateMachineTransitionConfigurer configureTransitions() { + return adapter.transitionBuilder; + } + + /** + * Builds a {@link StateMachine}. + * + * @return the state machine + */ + public StateMachine build() { + try { + builder.apply(adapter); + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + StateMachineConfigurationConfig stateMachineConfigurationConfig = stateMachineConfig.getStateMachineConfigurationConfig(); + ObjectStateMachineFactory stateMachineFactory = new ObjectStateMachineFactory( + stateMachineConfigurationConfig, stateMachineTransitions, stateMachineStates); + + if (stateMachineConfigurationConfig.getBeanFactory() != null) { + stateMachineFactory.setBeanFactory(stateMachineConfigurationConfig.getBeanFactory()); + } + if (stateMachineConfigurationConfig.getTaskExecutor() != null) { + stateMachineFactory.setTaskExecutor(stateMachineConfigurationConfig.getTaskExecutor()); + } + if (stateMachineConfigurationConfig.getTaskScheculer() != null) { + stateMachineFactory.setTaskScheduler(stateMachineConfigurationConfig.getTaskScheculer()); + } + + return stateMachineFactory.getStateMachine(); + } catch (Exception e) { + throw new StateMachineException("Error building state machine", e); + } + } + + } + + private static class BuilderStateMachineConfigurerAdapter + implements StateMachineConfigurer { + + private StateMachineTransitionBuilder transitionBuilder; + private StateMachineStateBuilder stateBuilder; + private StateMachineConfigurationBuilder configurationBuilder; + + BuilderStateMachineConfigurerAdapter() { + try { + getStateMachineTransitionBuilder(); + getStateMachineStateBuilder(); + getStateMachineConfigurationBuilder(); + } catch (Exception e) { + throw new StateMachineException("Error instantiating builder adapter", e); + } + } + + @Override + public void init(StateMachineConfigBuilder config) throws Exception { + config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); + config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); + config.setSharedObject(StateMachineConfigurationBuilder.class, getStateMachineConfigurationBuilder()); + } + + @Override + public void configure(StateMachineConfigBuilder builder) throws Exception { + } + + @Override + public boolean isAssignable(AnnotationBuilder> builder) { + return false; + } + + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + } + + protected final StateMachineTransitionBuilder getStateMachineTransitionBuilder() throws Exception { + if (transitionBuilder != null) { + return transitionBuilder; + } + transitionBuilder = new StateMachineTransitionBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + return transitionBuilder; + } + + protected final StateMachineStateBuilder getStateMachineStateBuilder() throws Exception { + if (stateBuilder != null) { + return stateBuilder; + } + stateBuilder = new StateMachineStateBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + return stateBuilder; + } + + protected final StateMachineConfigurationBuilder getStateMachineConfigurationBuilder() throws Exception { + if (configurationBuilder != null) { + return configurationBuilder; + } + configurationBuilder = new StateMachineConfigurationBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + return configurationBuilder; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java index 38ef84c8..e6116944 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java @@ -15,20 +15,28 @@ */ package org.springframework.statemachine.config; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; public class StateMachineConfig { + public final StateMachineConfigurationConfig stateMachineConfigurationConfig; + public final StateMachineTransitions transitions; public final StateMachineStates states; - public StateMachineConfig(StateMachineTransitions transitions, StateMachineStates states) { + public StateMachineConfig(StateMachineConfigurationConfig stateMachineConfigurationConfig, StateMachineTransitions transitions, StateMachineStates states) { + this.stateMachineConfigurationConfig = stateMachineConfigurationConfig; this.transitions = transitions; this.states = states; } + public StateMachineConfigurationConfig getStateMachineConfigurationConfig() { + return stateMachineConfigurationConfig; + } + public StateMachineTransitions getTransitions() { return transitions; } @@ -36,4 +44,5 @@ public class StateMachineConfig { public StateMachineStates getStates() { return states; } + } \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java index df70a85b..1373b856 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java @@ -27,9 +27,11 @@ public class StateMachineConfigBuilder protected StateMachineConfig performBuild() throws Exception { StateMachineTransitionBuilder sharedObject = getSharedObject(StateMachineTransitionBuilder.class); StateMachineStateBuilder sharedObject2 = getSharedObject(StateMachineStateBuilder.class); - StateMachineStates states = (StateMachineStates) sharedObject2.build(); + StateMachineConfigurationBuilder sharedObject3 = getSharedObject(StateMachineConfigurationBuilder.class); StateMachineTransitions transitions = (StateMachineTransitions) sharedObject.build(); - StateMachineConfig bean = new StateMachineConfig(transitions, states); + StateMachineStates states = (StateMachineStates) sharedObject2.build(); + StateMachineConfigurationConfig config = (StateMachineConfigurationConfig) sharedObject3.build(); + StateMachineConfig bean = new StateMachineConfig(config, transitions, states); return bean; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationBuilder.java new file mode 100644 index 00000000..31eddfbc --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationBuilder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.builders; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.statemachine.config.configurers.ConfigurationConfigurer; +import org.springframework.statemachine.config.configurers.DefaultConfigurationConfigurer; + +/** + * {@link AnnotationBuilder} for {@link StateMachineStates}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineConfigurationBuilder + extends AbstractConfiguredAnnotationBuilder, StateMachineConfigurationConfigurer, StateMachineConfigurationBuilder> + implements StateMachineConfigurationConfigurer { + + private BeanFactory beanFactory; + private TaskExecutor taskExecutor; + private TaskScheduler taskScheculer; + + public StateMachineConfigurationBuilder() { + super(); + } + + public StateMachineConfigurationBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + public StateMachineConfigurationBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + public ConfigurationConfigurer withConfiguration() throws Exception { + return apply(new DefaultConfigurationConfigurer()); + } + + @Override + protected StateMachineConfigurationConfig performBuild() throws Exception { + return new StateMachineConfigurationConfig<>(beanFactory, taskExecutor, taskScheculer); + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public void setTaskExecutor(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + public void setTaskScheculer(TaskScheduler taskScheculer) { + this.taskScheculer = taskScheculer; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfig.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfig.java new file mode 100644 index 00000000..fa875399 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.builders; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; + +public class StateMachineConfigurationConfig { + + private final BeanFactory beanFactory; + private final TaskExecutor taskExecutor; + private final TaskScheduler taskScheculer; + + public StateMachineConfigurationConfig(BeanFactory beanFactory, TaskExecutor taskExecutor, + TaskScheduler taskScheculer) { + this.beanFactory = beanFactory; + this.taskExecutor = taskExecutor; + this.taskScheculer = taskScheculer; + } + + public BeanFactory getBeanFactory() { + return beanFactory; + } + + public TaskExecutor getTaskExecutor() { + return taskExecutor; + } + + public TaskScheduler getTaskScheculer() { + return taskScheculer; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfigurer.java new file mode 100644 index 00000000..75e95987 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurationConfigurer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.builders; + +import org.springframework.statemachine.config.configurers.ConfigurationConfigurer; + +/** + * Configurer interface exposing generic config. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineConfigurationConfigurer { + + /** + * Gets a configurer for generic config. + * + * @return {@link ConfigurationConfigurer} for chaining + * @throws Exception if configuration error happens + */ + ConfigurationConfigurer withConfiguration() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java index 7219d88a..44b31c34 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java @@ -20,7 +20,7 @@ import org.springframework.statemachine.config.common.annotation.AnnotationConfi /** * {@link AnnotationConfigurer} exposing configurers for states and transitions. - * + * * @author Janne Valkealahti * * @param the type of state @@ -29,9 +29,17 @@ import org.springframework.statemachine.config.common.annotation.AnnotationConfi public interface StateMachineConfigurer extends AnnotationConfigurer, StateMachineConfigBuilder> { + /** + * Callback for {@link StateMachineConfigurationConfigurer}. + * + * @param config the {@link StateMachineConfigurationConfigurer} + * @throws Exception if configuration error happens + */ + void configure(StateMachineConfigurationConfigurer config) throws Exception; + /** * Callback for {@link StateMachineStateConfigurer}. - * + * * @param states the {@link StateMachineStateConfigurer} * @throws Exception if configuration error happens */ @@ -39,7 +47,7 @@ public interface StateMachineConfigurer extends /** * Callback for {@link StateMachineTransitionConfigurer}. - * + * * @param transitions the {@link StateMachineTransitionConfigurer} * @throws Exception if configuration error happens */ diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java index 15c99c2e..d7420a39 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.ObjectStateMachineFactory; import org.springframework.statemachine.config.StateMachineConfig; import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; @@ -88,8 +89,9 @@ public class StateMachineConfiguration, E extends Enum> ext StateMachineConfig stateMachineConfig = getBuilder().getOrBuild(); StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + StateMachineConfigurationConfig stateMachineConfigurationConfig = stateMachineConfig.getStateMachineConfigurationConfig(); ObjectStateMachineFactory stateMachineFactory = new ObjectStateMachineFactory( - stateMachineTransitions, stateMachineStates); + stateMachineConfigurationConfig, stateMachineTransitions, stateMachineStates); stateMachineFactory.setBeanFactory(getBeanFactory()); stateMachineFactory.setContextEventsEnabled(contextEvents); setObject(stateMachineFactory.getStateMachine()); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java index eef8d188..36853d86 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java @@ -35,6 +35,7 @@ import org.springframework.statemachine.config.ObjectStateMachineFactory; import org.springframework.statemachine.config.StateMachineConfig; import org.springframework.statemachine.config.StateMachineFactory; import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; @@ -109,7 +110,8 @@ public class StateMachineFactoryConfiguration, E extends Enum< StateMachineConfig stateMachineConfig = builder.getOrBuild(); StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); StateMachineStates stateMachineStates = stateMachineConfig.getStates(); - ObjectStateMachineFactory enumStateMachineFactory = new ObjectStateMachineFactory(stateMachineTransitions, stateMachineStates); + StateMachineConfigurationConfig stateMachineConfigurationConfig = stateMachineConfig.getStateMachineConfigurationConfig(); + ObjectStateMachineFactory enumStateMachineFactory = new ObjectStateMachineFactory(stateMachineConfigurationConfig, stateMachineTransitions, stateMachineStates); enumStateMachineFactory.setBeanFactory(beanFactory); enumStateMachineFactory.setContextEventsEnabled(contextEvents); this.stateMachineFactory = enumStateMachineFactory; diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ConfigurationConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ConfigurationConfigurer.java new file mode 100644 index 00000000..82e8c976 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ConfigurationConfigurer.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.configurers; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +/** + * Base {@code ConfigConfigurer} interface for configuring generic config. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface ConfigurationConfigurer extends + AnnotationConfigurerBuilder> { + + /** + * Specify a {@link BeanFactory}. + * + * @param beanFactory the bean factory + * @return configurer for chaining + */ + ConfigurationConfigurer beanFactory(BeanFactory beanFactory); + + /** + * Specify a {@link TaskExecutor}. + * + * @param beanFactory the bean factory + * @return configurer for chaining + */ + ConfigurationConfigurer taskExecutor(TaskExecutor taskExecutor); + + /** + * Specify a {@link TaskScheduler}. + * + * @param beanFactory the bean factory + * @return configurer for chaining + */ + ConfigurationConfigurer taskScheduler(TaskScheduler taskScheduler); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultConfigurationConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultConfigurationConfigurer.java new file mode 100644 index 00000000..a4d11094 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultConfigurationConfigurer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.configurers; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +/** + * Default implementation of a {@link ConfigurationConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultConfigurationConfigurer + extends AnnotationConfigurerAdapter, StateMachineConfigurationConfigurer, StateMachineConfigurationBuilder> + implements ConfigurationConfigurer { + + private BeanFactory beanFactory; + private TaskExecutor taskExecutor; + private TaskScheduler taskScheculer; + + @Override + public void configure(StateMachineConfigurationBuilder builder) throws Exception { + builder.setBeanFactory(beanFactory); + builder.setTaskExecutor(taskExecutor); + builder.setTaskScheculer(taskScheculer); + } + + @Override + public ConfigurationConfigurer beanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + return this; + } + + @Override + public ConfigurationConfigurer taskExecutor(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + return this; + } + + @Override + public ConfigurationConfigurer taskScheduler(TaskScheduler taskScheduler) { + this.taskScheculer = taskScheduler; + return this; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ManualBuilderTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ManualBuilderTests.java new file mode 100644 index 00000000..0d3a2407 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ManualBuilderTests.java @@ -0,0 +1,190 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineBuilder.Builder; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; + +public class ManualBuilderTests { + + @Test + public void testManualBuildConcept() throws Exception { + StateMachineConfigBuilder builder = new StateMachineConfigBuilder(); + Config config = new Config(); + builder.apply(config); + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + StateMachineConfigurationConfig stateMachineConfigurationConfig = stateMachineConfig.getStateMachineConfigurationConfig(); + ObjectStateMachineFactory stateMachineFactory = new ObjectStateMachineFactory( + stateMachineConfigurationConfig, stateMachineTransitions, stateMachineStates); + + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("taskExecutor", new SyncTaskExecutor()); + beanFactory.addBean("taskScheduler", new ConcurrentTaskScheduler()); + stateMachineFactory.setBeanFactory(beanFactory); + + TestListener listener = new TestListener(); + StateMachine stateMachine = stateMachineFactory.getStateMachine(); + stateMachine.addStateListener(listener); + stateMachine.start(); + + assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedCount, is(1)); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + } + + @Test + public void testManualBuildTaskViaBeanFactory() throws Exception { + Builder builder = StateMachineBuilder.builder(); + + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("taskExecutor", new SyncTaskExecutor()); + beanFactory.addBean("taskScheduler", new ConcurrentTaskScheduler()); + + builder.configureConfiguration() + .withConfiguration() + .beanFactory(beanFactory); + + builder.configureStates() + .withStates() + .initial("S1").state("S2"); + + builder.configureTransitions() + .withExternal() + .source("S1").target("S2").event("E1") + .and() + .withExternal() + .source("S2").target("S1").event("E2"); + + StateMachine stateMachine = builder.build(); + assertThat(stateMachine, notNullValue()); + TestListener listener = new TestListener(); + stateMachine.addStateListener(listener); + stateMachine.start(); + + assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedCount, is(1)); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + } + + @Test + public void testManualBuildExplicitTaskExecutorAndScheduler() throws Exception { + Builder builder = StateMachineBuilder.builder(); + + builder.configureConfiguration() + .withConfiguration() + .taskExecutor(new SyncTaskExecutor()) + .taskScheduler(new ConcurrentTaskScheduler()); + + builder.configureStates() + .withStates() + .initial("S1").state("S2"); + + builder.configureTransitions() + .withExternal() + .source("S1").target("S2").event("E1") + .and() + .withExternal() + .source("S2").target("S1").event("E2"); + + StateMachine stateMachine = builder.build(); + assertThat(stateMachine, notNullValue()); + TestListener listener = new TestListener(); + stateMachine.addStateListener(listener); + stateMachine.start(); + + assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedCount, is(1)); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + } + + static class Config extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("S1").state("S2"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source("S1").target("S2").event("E1") + .and() + .withExternal() + .source("S2").target("S1").event("E2"); + } + + } + + private static class TestListener extends StateMachineListenerAdapter { + + volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); + volatile CountDownLatch transitionLatch = new CountDownLatch(0); + volatile int stateChangedCount = 0; + + @Override + public void stateChanged(State from, State to) { + stateChangedCount++; + stateChangedLatch.countDown(); + } + + @Override + public void transition(Transition transition) { + transitionLatch.countDown(); + } + + public void reset(int c1) { + reset(c1, 0); + } + + public void reset(int c1, int c2) { + stateChangedLatch = new CountDownLatch(c1); + transitionLatch = new CountDownLatch(c2); + stateChangedCount = 0; + } + + } + +}