diff --git a/build.gradle b/build.gradle index d95bfb5f..bb24ac21 100644 --- a/build.gradle +++ b/build.gradle @@ -212,6 +212,43 @@ project('spring-statemachine-cluster') { } } +project('spring-statemachine-uml') { + description = "Spring State Machine Uml" + + dependencies { + compile project(":spring-statemachine-core") + optional "org.springframework.security:spring-security-core:$springSecurityVersion" + + // these eclipse maven deps are simply broken + compile("org.eclipse.uml2:uml:$eclipseUml2UmlVersion") { dep -> + exclude group: "org.eclipse.core", module: "runtime" + exclude group: "org.eclipse.emf", module: "ecore" + exclude group: "org.eclipse.emf.ecore", module: "xmi" + exclude group: "org.eclipse.emf.mapping", module: "ecore2xml" + exclude group: "org.eclipse.uml2", module: "common" + exclude group: "org.eclipse.uml2", module: "types" + } + compile("org.eclipse.uml2:types:$eclipseUml2TypesVersion") { dep -> + exclude group: "org.eclipse.core", module: "runtime" + exclude group: "org.eclipse.emf", module: "ecore" + exclude group: "org.eclipse.uml2", module: "common" + } + compile("org.eclipse.uml2:common:$eclipseUml2CommonVersion") { dep -> + exclude group: "org.eclipse.core", module: "runtime" + exclude group: "org.eclipse.emf", module: "ecore" + } + compile "org.eclipse.emf:org.eclipse.emf.ecore.xmi:$eclipseEmfXmiVersion" + compile "org.eclipse.emf:org.eclipse.emf.ecore:$eclipseEmfEcoreVersion" + compile "org.eclipse.emf:org.eclipse.emf.common:$eclipseEmfCommonVersion" + + testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" + testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" + testCompile "junit:junit:$junitVersion" + testRuntime "log4j:log4j:$log4jVersion" + } +} + configure(recipeProjects()) { dependencies { compile project(":spring-statemachine-recipes-common") diff --git a/gradle.properties b/gradle.properties index 98848d99..7835fdcf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,4 +17,10 @@ tomcatEmbedVersion=8.0.30 servletApiVersion=3.0.1 commonsPool2Version=2.4.2 hsqlVersion=2.3.1 +eclipseUml2UmlVersion=5.0.0-v20140602-0749 +eclipseUml2TypesVersion=2.0.0-v20140602-0749 +eclipseUml2CommonVersion=2.0.0-v20140602-0749 +eclipseEmfXmiVersion=2.11.1-v20150805-0538 +eclipseEmfEcoreVersion=2.11.1-v20150805-0538 +eclipseEmfCommonVersion=2.11.0-v20150805-0538 diff --git a/settings.gradle b/settings.gradle index 42a66fc2..f37c6fb2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,7 @@ include 'spring-statemachine-kryo' include 'spring-statemachine-zookeeper' include 'spring-statemachine-redis' include 'spring-statemachine-cluster' +include 'spring-statemachine-uml' include 'spring-statemachine-recipes' 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 4be2c21b..8384ef5f 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 @@ -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. @@ -19,6 +19,8 @@ import org.springframework.statemachine.config.builders.StateMachineConfigBuilde 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.StateMachineModelBuilder; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateBuilder; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; @@ -36,12 +38,14 @@ import org.springframework.statemachine.config.common.annotation.ObjectPostProce */ public abstract class AbstractStateMachineConfigurerAdapter implements StateMachineConfigurer { + private StateMachineModelBuilder modelBuilder; private StateMachineTransitionBuilder transitionBuilder; private StateMachineStateBuilder stateBuilder; private StateMachineConfigurationBuilder configurationBuilder; @Override public final void init(StateMachineConfigBuilder config) throws Exception { + config.setSharedObject(StateMachineModelBuilder.class, getStateMachineModelBuilder()); config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); config.setSharedObject(StateMachineConfigurationBuilder.class, getStateMachineConfigurationBuilder()); @@ -51,6 +55,10 @@ public abstract class AbstractStateMachineConfigurerAdapter implements Sta public void configure(StateMachineConfigBuilder config) throws Exception { } + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + } + @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { } @@ -68,6 +76,15 @@ public abstract class AbstractStateMachineConfigurerAdapter implements Sta return builder instanceof StateMachineConfigBuilder; } + protected final StateMachineModelBuilder getStateMachineModelBuilder() throws Exception { + if (modelBuilder != null) { + return modelBuilder; + } + modelBuilder = new StateMachineModelBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(modelBuilder); + return modelBuilder; + } + protected final StateMachineTransitionBuilder getStateMachineTransitionBuilder() throws Exception { if (transitionBuilder != null) { return transitionBuilder; 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 index e7c8fda2..613aacc8 100644 --- 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 @@ -23,6 +23,8 @@ import org.springframework.statemachine.config.builders.StateMachineConfigBuilde 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.StateMachineModelBuilder; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateBuilder; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; @@ -75,6 +77,15 @@ public class StateMachineBuilder { builder = new StateMachineConfigBuilder(); } + /** + * Configure model. + * + * @return the state machine model configurer + */ + public StateMachineModelConfigurer configureModel() { + return adapter.modelBuilder; + } + /** * Configure configuration. * @@ -144,12 +155,14 @@ public class StateMachineBuilder { private static class BuilderStateMachineConfigurerAdapter implements StateMachineConfigurer { + private StateMachineModelBuilder modelBuilder; private StateMachineTransitionBuilder transitionBuilder; private StateMachineStateBuilder stateBuilder; private StateMachineConfigurationBuilder configurationBuilder; BuilderStateMachineConfigurerAdapter() { try { + getStateMachineModelBuilder(); getStateMachineTransitionBuilder(); getStateMachineStateBuilder(); getStateMachineConfigurationBuilder(); @@ -160,6 +173,7 @@ public class StateMachineBuilder { @Override public void init(StateMachineConfigBuilder config) throws Exception { + config.setSharedObject(StateMachineModelBuilder.class, getStateMachineModelBuilder()); config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); config.setSharedObject(StateMachineConfigurationBuilder.class, getStateMachineConfigurationBuilder()); @@ -174,6 +188,10 @@ public class StateMachineBuilder { return false; } + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + } + @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { } @@ -186,6 +204,15 @@ public class StateMachineBuilder { public void configure(StateMachineTransitionConfigurer transitions) throws Exception { } + protected final StateMachineModelBuilder getStateMachineModelBuilder() throws Exception { + if (modelBuilder != null) { + return modelBuilder; + } + modelBuilder = new StateMachineModelBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(modelBuilder); + return modelBuilder; + } + protected final StateMachineTransitionBuilder getStateMachineTransitionBuilder() throws Exception { if (transitionBuilder != null) { return transitionBuilder; @@ -209,7 +236,5 @@ public class StateMachineBuilder { configurationBuilder = new StateMachineConfigurationBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); return configurationBuilder; } - } - } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/ModelData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/ModelData.java new file mode 100644 index 00000000..42a9c784 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/ModelData.java @@ -0,0 +1,50 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.configurers.ModelConfigurer; +import org.springframework.statemachine.config.model.StateMachineModelFactory; + +/** + * Data object used return and build data from a {@link ModelConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class ModelData { + + private StateMachineModelFactory factory; + + /** + * Instantiates a new model data. + * + * @param factory the factory + */ + public ModelData(StateMachineModelFactory factory) { + this.factory = factory; + } + + /** + * Gets the factory. + * + * @return the factory + */ + public StateMachineModelFactory getFactory() { + return factory; + } +} 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 604f8bd2..14116deb 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 @@ -17,10 +17,21 @@ package org.springframework.statemachine.config.builders; import org.springframework.statemachine.config.StateMachineConfig; import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; import org.springframework.statemachine.config.model.ConfigurationData; +import org.springframework.statemachine.config.model.StateMachineModel; import org.springframework.statemachine.config.model.StatesData; import org.springframework.statemachine.config.model.TransitionsData; +/** + * {@link AnnotationBuilder} handling all shared builders which effectively contructs + * full configuration for {@link StateMachineConfig}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ public class StateMachineConfigBuilder extends AbstractConfiguredAnnotationBuilder, StateMachineConfigBuilder, StateMachineConfigBuilder> { @@ -28,17 +39,30 @@ public class StateMachineConfigBuilder @SuppressWarnings("unchecked") @Override protected StateMachineConfig performBuild() throws Exception { + // TODO: should prevent calling state/transition builder if model is given + StateMachineModelBuilder modelBuilder = getSharedObject(StateMachineModelBuilder.class); StateMachineConfigurationBuilder configurationBuilder = getSharedObject(StateMachineConfigurationBuilder.class); StateMachineTransitionBuilder transitionBuilder = getSharedObject(StateMachineTransitionBuilder.class); StateMachineStateBuilder stateBuilder = getSharedObject(StateMachineStateBuilder.class); - // build config first as it's shared with transition builder - ConfigurationData config = (ConfigurationData) configurationBuilder.build(); - transitionBuilder.setSharedObject(ConfigurationData.class, config); - TransitionsData transitions = (TransitionsData) transitionBuilder.build(); - StatesData states = (StatesData) stateBuilder.build(); - StateMachineConfig bean = new StateMachineConfig(config, transitions, states); - return bean; - } + ModelData model = (ModelData) modelBuilder.build(); + ConfigurationData stateMachineConfigurationConfig = null; + TransitionsData transitions = null; + StatesData states = null; + if (model.getFactory() != null) { + StateMachineModel stateMachineModel = model.getFactory().build(); + transitions = stateMachineModel.getTransitionsData(); + states = stateMachineModel.getStatesData(); + } + stateMachineConfigurationConfig = (ConfigurationData) configurationBuilder.build(); + if (transitions == null) { + transitionBuilder.setSharedObject(ConfigurationData.class, stateMachineConfigurationConfig); + transitions = (TransitionsData) transitionBuilder.build(); + } + if (states == null) { + states = (StatesData) stateBuilder.build(); + } + return new StateMachineConfig(stateMachineConfigurationConfig, transitions, states); + } } 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 44b31c34..b52bbf29 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 @@ -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. @@ -29,6 +29,14 @@ import org.springframework.statemachine.config.common.annotation.AnnotationConfi public interface StateMachineConfigurer extends AnnotationConfigurer, StateMachineConfigBuilder> { + /** + * Callback for {@link StateMachineModelConfigurer}. + * + * @param model the {@link StateMachineModelConfigurer} + * @throws Exception if configuration error happens + */ + void configure(StateMachineModelConfigurer model) throws Exception; + /** * Callback for {@link StateMachineConfigurationConfigurer}. * @@ -52,5 +60,4 @@ public interface StateMachineConfigurer extends * @throws Exception if configuration error happens */ void configure(StateMachineTransitionConfigurer transitions) throws Exception; - } \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelBuilder.java new file mode 100644 index 00000000..50a14025 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelBuilder.java @@ -0,0 +1,79 @@ +/* + * 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.builders; + +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.DefaultModelConfigurer; +import org.springframework.statemachine.config.configurers.ModelConfigurer; +import org.springframework.statemachine.config.model.StateMachineModelFactory; + +/** + * {@link AnnotationBuilder} for {@link ModelData}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineModelBuilder + extends AbstractConfiguredAnnotationBuilder, StateMachineModelConfigurer, StateMachineModelBuilder> + implements StateMachineModelConfigurer { + + private StateMachineModelFactory factory; + + /** + * Instantiates a new state machine model builder. + */ + public StateMachineModelBuilder() { + super(); + } + + /** + * Instantiates a new state machine model builder. + * + * @param objectPostProcessor the object post processor + * @param allowConfigurersOfSameType the allow configurers of same type + */ + public StateMachineModelBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + /** + * Instantiates a new state machine model builder. + * + * @param objectPostProcessor the object post processor + */ + public StateMachineModelBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + protected ModelData performBuild() throws Exception { + return new ModelData(factory); + } + + @Override + public ModelConfigurer withModel() throws Exception { + return apply(new DefaultModelConfigurer()); + } + + public void setStateMachineModelFactory(StateMachineModelFactory factory) { + this.factory = factory; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelConfigurer.java new file mode 100644 index 00000000..576b4107 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineModelConfigurer.java @@ -0,0 +1,37 @@ +/* + * 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.builders; + +import org.springframework.statemachine.config.configurers.ModelConfigurer; + +/** + * Configurer interface exposing model. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineModelConfigurer { + + /** + * Gets a configurer for model. + * + * @return {@link ModelConfigurer} for chaining + * @throws Exception if configuration error happens + */ + ModelConfigurer withModel() throws Exception; +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultModelConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultModelConfigurer.java new file mode 100644 index 00000000..7521d133 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultModelConfigurer.java @@ -0,0 +1,48 @@ +/* + * 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.ModelData; +import org.springframework.statemachine.config.builders.StateMachineModelBuilder; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.config.model.StateMachineModelFactory; + +/** + * Default implementation of a {@link ModelConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultModelConfigurer + extends AnnotationConfigurerAdapter, StateMachineModelConfigurer, StateMachineModelBuilder> + implements ModelConfigurer { + + private StateMachineModelFactory factory; + + @Override + public void configure(StateMachineModelBuilder builder) throws Exception { + builder.setStateMachineModelFactory(factory); + } + + @Override + public ModelConfigurer factory(StateMachineModelFactory factory) { + this.factory = factory; + return this; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ModelConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ModelConfigurer.java new file mode 100644 index 00000000..19b222e5 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ModelConfigurer.java @@ -0,0 +1,40 @@ +/* + * 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.StateMachineModelConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.config.model.StateMachineModelFactory; + +/** + * Base {@code ModelConfigurer} interface for configuring state machine model. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface ModelConfigurer extends + AnnotationConfigurerBuilder> { + + /** + * Specify a state machine model factory. + * + * @param factory the state machine model factory + * @return configurer for chaining + */ + ModelConfigurer factory(StateMachineModelFactory factory); +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/AbstractStateMachineModelFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/AbstractStateMachineModelFactory.java new file mode 100644 index 00000000..6f61982f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/AbstractStateMachineModelFactory.java @@ -0,0 +1,118 @@ +/* + * 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; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; + +/** + * Base implementation of a {@link StateMachineModelFactory} providing + * some common grounds for various implementations. + * + * This factory is able to resolve {@link Action}s or {@link Guard}s either from + * a {@link BeanFactory} if knows, or from manually registered instances. Manually + * registered actions or guards are needed if those are not created as beans or if + * whole state machine is working outside of an application context. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractStateMachineModelFactory implements StateMachineComponentResolver, BeanFactoryAware { + + private BeanFactory beanFactory; + private final Map> registeredActions = new HashMap<>(); + private final Map> registeredGuards = new HashMap<>(); + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Register {@link Action} into factory with a given id. + * + * @param id the id + * @param action the action + */ + public void registerAction(String id, Action action) { + registeredActions.put(id, action); + } + + /** + * Register {@link Guard} into factory with a given id. + * + * @param id the id + * @param guard the guard + */ + public void registerGuard(String id, Guard guard) { + registeredGuards.put(id, guard); + } + + /** + * Gets the bean factory. + * + * @return the bean factory + */ + protected final BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Resolve action. + * + * @param id the id + * @return the action + */ + @SuppressWarnings("unchecked") + public Action resolveAction(String id) { + Action a = null; + a = registeredActions.get(id); + if (a == null && beanFactory != null) { + a = beanFactory.getBean(id, Action.class); + } + if (a == null) { + throw new RuntimeException("Can't resolve action with id " + id + " either from registered actions nor beanfactory"); + } + return a; + } + + /** + * Resolve guard. + * + * @param id the id + * @return the guard + */ + @SuppressWarnings("unchecked") + public Guard resolveGuard(String id) { + Guard g = null; + g = registeredGuards.get(id); + if (g == null && beanFactory != null) { + g = beanFactory.getBean(id, Guard.class); + } + if (g == null) { + throw new RuntimeException("Can't resolve guard with id " + id + " either from registered guards nor beanfactory"); + } + return g; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java index e0a9c71a..fc953c8e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java @@ -64,6 +64,18 @@ public class StateData { this(null, null, state, null, null, null, initial); } + /** + * Instantiates a new state data. + * + * @param parent the parent + * @param region the region + * @param state the state + * @param initial the initial + */ + public StateData(Object parent, Object region, S state, boolean initial) { + this(parent, region, state, null, null, null, initial); + } + /** * Instantiates a new state data. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineComponentResolver.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineComponentResolver.java new file mode 100644 index 00000000..147e1e83 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineComponentResolver.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; + +/** + * Strategy interface for registering and resolving state machine + * components by their id's + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineComponentResolver { + + /** + * Register {@link Action} into factory with a given id. + * + * @param id the id + * @param action the action + */ + void registerAction(String id, Action action); + + /** + * Register {@link Guard} into factory with a given id. + * + * @param id the id + * @param guard the guard + */ + void registerGuard(String id, Guard guard); + + /** + * Resolve action. + * + * @param id the id + * @return the action + */ + Action resolveAction(String id); + + /** + * Resolve guard. + * + * @param id the id + * @return the guard + */ + Guard resolveGuard(String id); +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineModelFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineModelFactory.java new file mode 100644 index 00000000..eff7f9ca --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateMachineModelFactory.java @@ -0,0 +1,34 @@ +/* + * 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 generic builder interface for building {@link StateMachineModel}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineModelFactory { + + /** + * Builds the state machine model. + * + * @return the state machine model + */ + StateMachineModel build(); +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelFactoryTests.java new file mode 100644 index 00000000..440da1c9 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelFactoryTests.java @@ -0,0 +1,145 @@ +/* + * 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; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.ObjectStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; + +public class StateMachineModelFactoryTests extends AbstractStateMachineTests { + + @Test + public void testResolvingFromContext() { + context.register(Config1.class); + context.refresh(); + + TestStateMachineModelFactory modelBuilder = new TestStateMachineModelFactory(); + modelBuilder.setBeanFactory(context); + ObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(modelBuilder.build()); + + StateMachine stateMachine = factory.getStateMachine(); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } + + @Test + public void testFromAnnotationConfig() { + context.register(Config2.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine stateMachine = context.getBean(StateMachine.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } + + @Configuration + static class Config1 { + @Bean + public Action action1() { + return new Action() { + @Override + public void execute(StateContext context) { + } + }; + } + } + + @Configuration + @EnableStateMachine + public static class Config2 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + return new TestStateMachineModelFactory(); + } + + @Bean + public Action action1() { + return new Action() { + @Override + public void execute(StateContext context) { + } + }; + } + } + + @SuppressWarnings("unchecked") + private static class TestStateMachineModelFactory implements StateMachineModelFactory, BeanFactoryAware { + private BeanFactory beanFactory; + + @Override + public StateMachineModel build() { + + Action action1 = beanFactory.getBean("action1", Action.class); + Collection> s2Actions = new ArrayList<>(); + s2Actions.add(action1); + + ConfigurationData configurationData = new ConfigurationData<>(); + + Collection> stateData = new ArrayList<>(); + stateData.add(new StateData("S1", true)); + stateData.add(new StateData(null, null, "S2", null, s2Actions, null)); + StatesData statesData = new StatesData<>(stateData); + + Collection> transitionData = new ArrayList<>(); + transitionData.add(new TransitionData("S1", "S2", "E1")); + TransitionsData transitionsData = new TransitionsData<>(transitionData); + + StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData); + return stateMachineModel; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } +} diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java new file mode 100644 index 00000000..87a2af22 --- /dev/null +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java @@ -0,0 +1,183 @@ +/* + * 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.uml; + +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.uml2.uml.Activity; +import org.eclipse.uml2.uml.Event; +import org.eclipse.uml2.uml.Model; +import org.eclipse.uml2.uml.PackageableElement; +import org.eclipse.uml2.uml.Region; +import org.eclipse.uml2.uml.Signal; +import org.eclipse.uml2.uml.SignalEvent; +import org.eclipse.uml2.uml.State; +import org.eclipse.uml2.uml.StateMachine; +import org.eclipse.uml2.uml.Transition; +import org.eclipse.uml2.uml.Trigger; +import org.eclipse.uml2.uml.UMLPackage; +import org.eclipse.uml2.uml.Vertex; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.model.StateData; +import org.springframework.statemachine.config.model.StateMachineComponentResolver; +import org.springframework.statemachine.config.model.StatesData; +import org.springframework.statemachine.config.model.TransitionData; +import org.springframework.statemachine.config.model.TransitionsData; +import org.springframework.util.Assert; + +/** + * Model parser which constructs states and transitions data out from + * an uml model. + * + * @author Janne Valkealahti + */ +public class UmlModelParser { + + private final Model model; + private final StateMachineComponentResolver resolver; + private final Collection> stateDatas = new ArrayList>(); + private final Collection> transitionDatas = new ArrayList>(); + + /** + * Instantiates a new uml model parser. + * + * @param model the model + * @param resolver the resolver + */ + public UmlModelParser(Model model, StateMachineComponentResolver resolver) { + Assert.notNull(model, "Model must be set"); + Assert.notNull(resolver, "Resolver must be set"); + this.model = model; + this.resolver = resolver; + } + + /** + * Parses the model. + * + * @return the data holder for states and transitions + */ + public DataHolder parseModel() { + EList packagedElements = model.getPackagedElements(); + // expect model having exactly one machine + StateMachine stateMachine = (StateMachine) EcoreUtil.getObjectByType(packagedElements, UMLPackage.Literals.STATE_MACHINE); + if (stateMachine == null) { + throw new IllegalArgumentException("Can't find statemachine from model"); + } + + for (Region region : stateMachine.getRegions()) { + handleRegion(region); + } + return new DataHolder(new StatesData<>(stateDatas), new TransitionsData(transitionDatas)); + } + + private void handleRegion(Region region) { + // build states + for (Vertex vertex : region.getSubvertices()) { + if (vertex instanceof State) { + State state = (State)vertex; + // find parent state if submachine state, root states have null parent + String parent = null; + if (state.getContainer().getOwner() instanceof State) { + parent = ((State)state.getContainer().getOwner()).getName(); + } + StateData stateData = handleActions( + new StateData(parent, null, state.getName(), UmlUtils.isInitialState(state)), state); + stateDatas.add(stateData); + // do recursive handling of regions + for (Region sub : state.getRegions()) { + handleRegion(sub); + } + } + } + + // build transitions + for (Transition transition : region.getTransitions()) { + for (Trigger trigger : transition.getTriggers()) { + Event event = trigger.getEvent(); + if (event instanceof SignalEvent) { + Signal signal = ((SignalEvent)event).getSignal(); + if (signal != null) { + transitionDatas.add(new TransitionData(transition.getSource().getName(), + transition.getTarget().getName(), signal.getName())); + } + } + } + } + + } + + private StateData handleActions(StateData stateData, State state) { + if (state.getEntry() instanceof Activity) { + String beanId = ((Activity)state.getEntry()).getName(); + Action bean = resolver.resolveAction(beanId); + if (bean != null) { + ArrayList> entrys = new ArrayList>(); + entrys.add(bean); + stateData.setEntryActions(entrys); + } + } + if (state.getExit() instanceof Activity) { + String beanId = ((Activity)state.getExit()).getName(); + Action bean = resolver.resolveAction(beanId); + if (bean != null) { + ArrayList> exits = new ArrayList>(); + exits.add(bean); + stateData.setExitActions(exits); + } + } + return stateData; + } + + /** + * Holder object for results returned from uml parser. + */ + protected class DataHolder { + private final StatesData statesData; + private final TransitionsData transitionsData; + + /** + * Instantiates a new data holder. + * + * @param statesData the states data + * @param transitionsData the transitions data + */ + public DataHolder(StatesData statesData, TransitionsData transitionsData) { + this.statesData = statesData; + this.transitionsData = transitionsData; + } + + /** + * Gets the states data. + * + * @return the states data + */ + public StatesData getStatesData() { + return statesData; + } + + /** + * Gets the transitions data. + * + * @return the transitions data + */ + public TransitionsData getTransitionsData() { + return transitionsData; + } + } +} diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlStateMachineModelFactory.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlStateMachineModelFactory.java new file mode 100644 index 00000000..932cf4e7 --- /dev/null +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlStateMachineModelFactory.java @@ -0,0 +1,64 @@ +/* + * 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.uml; + +import java.io.IOException; + +import org.eclipse.uml2.uml.Model; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.model.AbstractStateMachineModelFactory; +import org.springframework.statemachine.config.model.ConfigurationData; +import org.springframework.statemachine.config.model.DefaultStateMachineModel; +import org.springframework.statemachine.config.model.StateMachineModel; +import org.springframework.statemachine.config.model.StateMachineModelFactory; +import org.springframework.statemachine.uml.UmlModelParser.DataHolder; +import org.springframework.util.Assert; + +/** + * {@link StateMachineModelFactory} which builds {@link StateMachineModel} from + * uml representation. + * + * @author Janne Valkealahti + */ +public class UmlStateMachineModelFactory extends AbstractStateMachineModelFactory + implements StateMachineModelFactory { + + private final Resource resource; + + /** + * Instantiates a new uml state machine model factory. + * + * @param resource the resource + */ + public UmlStateMachineModelFactory(Resource resource) { + Assert.notNull(resource, "Resource must be set"); + this.resource = resource; + } + + @Override + public StateMachineModel build() { + Model model = null; + try { + model = UmlUtils.getModel(resource.getURI().getPath()); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot build build model from resource " + resource, e); + } + UmlModelParser parser = new UmlModelParser(model, this); + DataHolder dataHolder = parser.parseModel(); + ConfigurationData configurationData = new ConfigurationData<>(); + return new DefaultStateMachineModel(configurationData, dataHolder.getStatesData(), dataHolder.getTransitionsData()); + } +} diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlUtils.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlUtils.java new file mode 100644 index 00000000..302c26d8 --- /dev/null +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlUtils.java @@ -0,0 +1,72 @@ +/* + * 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.uml; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.uml2.uml.Model; +import org.eclipse.uml2.uml.Pseudostate; +import org.eclipse.uml2.uml.PseudostateKind; +import org.eclipse.uml2.uml.State; +import org.eclipse.uml2.uml.Transition; +import org.eclipse.uml2.uml.UMLPackage; +import org.eclipse.uml2.uml.resource.UMLResource; + +/** + * Utilities for uml model processing. + * + * @author Janne Valkealahti + */ +public abstract class UmlUtils { + + /** + * Gets the model. + * + * @param modelPath the model path + * @return the model + */ + public static Model getModel(String modelPath) { + URI modelUri = URI.createFileURI(modelPath); + ResourceSet resourceSet = new ResourceSetImpl(); + resourceSet.getPackageRegistry().put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION, UMLResource.Factory.INSTANCE); + resourceSet.createResource(modelUri); + Resource resource = resourceSet.getResource(modelUri, true); + Model m = (Model) EcoreUtil.getObjectByType(resource.getContents(), UMLPackage.Literals.MODEL); + return m; + } + + /** + * Checks if {@link State} is an initial state by checking if + * it has incoming transition from UML's initial literal. + * + * @param state the state + * @return true, if is initial state + */ + public static boolean isInitialState(State state) { + for (Transition t : state.getIncomings()) { + if (t.getSource() instanceof Pseudostate) { + if (((Pseudostate)t.getSource()).getKind() == PseudostateKind.INITIAL_LITERAL) { + return true; + } + } + } + return false; + } +} diff --git a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/AbstractUmlTests.java b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/AbstractUmlTests.java new file mode 100644 index 00000000..79080582 --- /dev/null +++ b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/AbstractUmlTests.java @@ -0,0 +1,43 @@ +/* + * 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.uml; + +import org.junit.After; +import org.junit.Before; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public abstract class AbstractUmlTests { + + protected AnnotationConfigApplicationContext context; + + @Before + public void setup() { + context = buildContext(); + } + + @After + public void clean() { + if (context != null) { + context.close(); + } + context = null; + } + + protected AnnotationConfigApplicationContext buildContext() { + return null; + } + +} diff --git a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java new file mode 100644 index 00000000..4d0f71dc --- /dev/null +++ b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java @@ -0,0 +1,187 @@ +/* + * 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.uml; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; +import org.springframework.statemachine.config.model.StateData; +import org.springframework.statemachine.config.model.StateMachineModel; +import org.springframework.statemachine.config.model.StateMachineModelFactory; + +public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Test + public void testSimpleFlat() { + context.refresh(); + Resource model1 = new ClassPathResource("org/springframework/statemachine/uml/simple-flat.uml"); + UmlStateMachineModelFactory builder = new UmlStateMachineModelFactory(model1); + builder.registerAction("action1", new LatchAction()); + builder.setBeanFactory(context); + assertThat(model1.exists(), is(true)); + StateMachineModel stateMachineModel = builder.build(); + assertThat(stateMachineModel, notNullValue()); + Collection> stateDatas = stateMachineModel.getStatesData().getStateData(); + assertThat(stateDatas.size(), is(2)); + for (StateData stateData : stateDatas) { + if (stateData.getState().equals("S1")) { + assertThat(stateData.isInitial(), is(true)); + } else if (stateData.getState().equals("S2")) { + assertThat(stateData.isInitial(), is(false)); + } else { + throw new IllegalArgumentException(); + } + } + } + + @Test + public void testSimpleSubmachine() { + context.refresh(); + Resource model1 = new ClassPathResource("org/springframework/statemachine/uml/simple-submachine.uml"); + UmlStateMachineModelFactory builder = new UmlStateMachineModelFactory(model1); + builder.setBeanFactory(context); + assertThat(model1.exists(), is(true)); + StateMachineModel stateMachineModel = builder.build(); + assertThat(stateMachineModel, notNullValue()); + Collection> stateDatas = stateMachineModel.getStatesData().getStateData(); + assertThat(stateDatas.size(), is(4)); + for (StateData stateData : stateDatas) { + if (stateData.getState().equals("S1")) { + assertThat(stateData.isInitial(), is(true)); + assertThat(stateData.getParent(), nullValue()); + } else if (stateData.getState().equals("S2")) { + assertThat(stateData.isInitial(), is(false)); + assertThat(stateData.getParent(), nullValue()); + } else if (stateData.getState().equals("S11")) { + assertThat(stateData.isInitial(), is(true)); + assertThat(stateData.getParent(), is("S1")); + } else if (stateData.getState().equals("S12")) { + assertThat(stateData.isInitial(), is(false)); + assertThat(stateData.getParent(), is("S1")); + } else { + throw new IllegalArgumentException(); + } + } + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleFlatMachine() throws Exception { + context.register(Config2.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + LatchAction action1 = context.getBean("action1", LatchAction.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1")); + stateMachine.sendEvent("E1"); + assertThat(action1.latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleSubmachineMachine() throws Exception { + context.register(Config3.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1", "S11")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), contains("S1", "S12")); + stateMachine.sendEvent("E2"); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } + + @Configuration + @EnableStateMachine + public static class Config2 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/simple-flat.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public Action action1() { + return new LatchAction(); + } + } + + @Configuration + @EnableStateMachine + public static class Config3 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/simple-submachine.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public Action action1() { + return new LatchAction(); + } + } + + public static class LatchAction implements Action { + CountDownLatch latch = new CountDownLatch(1); + @Override + public void execute(StateContext context) { + latch.countDown(); + } + } +} diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.notation new file mode 100644 index 00000000..e3750905 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.notation @@ -0,0 +1,105 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.uml new file mode 100644 index 00000000..84409c92 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-flat.uml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.notation new file mode 100644 index 00000000..e9199ccf --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.notation @@ -0,0 +1,175 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.uml new file mode 100644 index 00000000..b5ef98bc --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-root-regions.uml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.notation new file mode 100644 index 00000000..d23b0a17 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.notation @@ -0,0 +1,178 @@ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.uml new file mode 100644 index 00000000..30279d1f --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-submachine.uml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +