Ajax, Spring Web Flow and Spring State Machine

Foreword:
A while ago, I developed a feasibility study application to research potential methods to reduces the chaos induced from the extreme Ajax usage in the Web Applications. The technology stack that was used, were Spring Web Flow and State Machine. In my opinion it produced good results and proved that it was feasible to use Spring Web Flow and State Machines in modern Web Applications. One point that this methodic was criticized based on comments I got, was its reliance to the custom implemented State Machine. Any organization/person considered to use this solution was skeptical about the custom State Machine and asked guarantees about it continued support. Those are naturally valid concerns, when people will invest for such an Enterprise Web Application, they needed assurances that technology will not disappear in next 2 months and continued support will be there. Well to calm those concerns I can now happily say that there is now a reliable open source implementation of a State Machine, Spring State Machine.

I don’t know where the Spring Organization found the motivation to develop a State Machine, may be they also see some valid points in my previous blog? :).

Positively surprised with the existence of the Spring State Machine, I decided to convert the feasibility study to use it from this point you will read the story about how it is gone. You can see this blog also a good example implementation for Spring State Machine.

Content:

Why to use this solution:
In the timeframe that I developed this feasibility study, State Machines were a quite unknown in Java development world. On one specific discussion I had about my previous blog with very renown Software Architect and book author, who developed systems for US Navy ships based on State Machines and reached %99.99 error free operation time (on 2 errors found on its system were hardware errors :)) quoted to me that ‘Too long Java developers made believe that State Machines were too complex for them, and it was better for them to stay away’. For this reason, there was no practically usable State Machine for Web Applications. There were implementations, but they required specific DSLs and customized too much for the special requirement of industry branched they are developed for. There was none following the principle of UML State Machine closely which are really compatible with the requirements of a modern Web Application. Luckily that days are over, we have now Spring State Machine, considering how mainstream Spring Projects are, I am sure that State Machine ideas will find some traction.

Tooling:
One side note I want to make here, for the people who read the previous blog. I used there an Open Source Modeling Tool (it is important for me that every technology used in this project should be Open Source to prevent that if somebody wants to use it, it will not be blocked for cost reasons from the management). The UML Modeling tool that I used in the previous blog, Topcased,  this project transformed and further developed. It now exist under the name of Papyrus, mostly compatible product to Topcased but there several small difference that I will mention.

So lets make our hands dirty.

Model-To-Spring State Machine:
First, I have to state that I am a big fan of Model Driven Software Development. Lately with the abuse of the Agile concepts, specification of software became a rare thing and Agile practicers are normally the haters of the modeling. I agree that a model that will be outdated and useless in 2 weeks is wasted effort but what about if our model lives with the project and evolves every iteration of the project.

A State Machine modeled in an UML tool guarantees that, if you want to further develop your State Machine you can not leave the UML Model to be outdated in a corner, it has to evolve with the State Machine and the Project . Unfortunately Spring State Machine expects at the moment to be configured via Java code. For a small project that might be Ok but for a complex projects, this Java files will be to complex to have an overview and understanding.

Fortunately Spring State Machine follows the concepts of the UML State Machine really close, so an UML State Machine Diagram can easily be converted to runnable Java code with technologies like Eclipse M2T XPand.

A state machine configuration looks like this for Spring State Machine….

@Configuration
@EnableStateMachineFactory(name = "TechDemoSM")
public class TechDemoSMConfiguration
		extends
			EnumStateMachineConfigurerAdapter<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> {
	private static final Logger LOG = Logger.getLogger(TechDemoSMConfiguration.class);

	@Autowired
	private TechDemoSMActionContainer techDemoSMActionContainer;

	@Autowired
	private TechDemoSMGuardContainer techDemoSMGuardContainer;

	@Autowired
	private TechDemoSMControlObjectLocator controlObjectLocator;

	@Override
	public void configure(
			StateMachineConfigurationConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> config)
					throws Exception {
		config.withConfiguration().listener(listener());
	}

	@Override
	public void configure(
			StateMachineStateConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> states)
					throws Exception {
		states.withStates()
				.initial(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING, initialStateTechDemoSMAction())
				.states(EnumSet.allOf(TechDemoSM_StateEnumerationImpl.class));
	}

	@Override
	public void configure(
			StateMachineTransitionConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> transitions)
					throws Exception {
		transitions

				//STATE - CUSTOMERSEARCH_RUNNING
				.withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
				.target(TechDemoSM_StateEnumerationImpl.End).event(TechDemoSM_EventEnumerationImpl.onEnd)

				.and()
				//STATE - CUSTOMERSEARCH_RUNNING
				.withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
				.target(TechDemoSM_StateEnumerationImpl.TIMEOUT).event(TechDemoSM_EventEnumerationImpl.onTimeout)

				.and()
				//STATE - CUSTOMERSEARCH_RUNNING
				.withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
				.target(TechDemoSM_StateEnumerationImpl.SERVICE_NOT_AVAILABLE)
				.event(TechDemoSM_EventEnumerationImpl.onServiceNotAvailable)

		;
	}

	public StateMachineListener<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> listener() {
		return new StateMachineListenerAdapter<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl>() {
			@Override
			public void stateChanged(State<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> from,
					State<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> to) {
				LOG.info("State change to " + to.getId());
			}

			@Override
			public void eventNotAccepted(Message<TechDemoSM_EventEnumerationImpl> event) {
				LOG.warn("The event " + event.toString() + " is not accepted!");
			}
		};
	}

	@Bean
	public Action<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> initialStateTechDemoSMAction() {
		return new Action<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl>() {
			@Override
			public void execute(
					StateContext<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> context) {
				AbstractTechDemoControlObject controlObject = controlObjectLocator.getControlObject();
				((ControlObject) controlObject).resetStateMachine();
				context.getExtendedState().getVariables().put("TechDemoSMControlObject", controlObject);
			}
		};
	}
}

Snippet 1

as you can see above the interface EnumStateMachineConfigurerAdapter over several configure methods (one for State Machine, one for States, one for Transitions) configures our Spring State Machine.

Above State Machine is created from the following UML Diagram.

TechDemoSM
Picture 1

It is quite a simple State Machine demonstrating the concepts how the States, Events, Transitions, Guards taken from the UML model and transferred to the Java Code and Spring State Machine configuration.

What we have here is an UML Model created with Eclipse Papyrus, corresponding XMI which interpreted with Eclipse M2T Framework and converted to Java code with XPand.

Sounds simple isn’t it, actually it really is.

EnumStateMachineConfigurerAdapter interface configure the State Machine in chunks.

First block is the definition of the State Machine,

	@Override
	public void configure(
			StateMachineConfigurationConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> config)
					throws Exception {
		config.withConfiguration().listener(listener());
	}

Snippet 2

in which we define the basic functionality of the State Machine, it’s listeners(which I will explain later) or it will auto start or not, for ex.

Next we inform to the State Machine about its States,

        @Override
	public void configure(
			StateMachineStateConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> states)
					throws Exception {
		states.withStates()
				.initial(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING, initialStateTechDemoSMAction())
				.states(EnumSet.allOf(TechDemoSM_StateEnumerationImpl.class));
	}

Snippet 3

which is nothing more then passing the State Enumeration created via Eclipse M2T XPand from the UML Model as a Template Parameter and letting the State Machine read the enumeration.

Third step is introducing the State Transitions of the State Machine,

	@Override
	public void configure(
			StateMachineTransitionConfigurer<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> transitions)
					throws Exception {
		transitions
                //STATE - CUSTOMERSEARCH_RUNNING
		.withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
		.target(TechDemoSM_StateEnumerationImpl.End).event(TechDemoSM_EventEnumerationImpl.onEnd)
		.and()
		//STATE - CUSTOMERSEARCH_RUNNING
		/**   
		        We received an error Event and we are transitioning to the TIMEOUT state.				    
		*/
                .withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
		.target(TechDemoSM_StateEnumerationImpl.TIMEOUT).event(TechDemoSM_EventEnumerationImpl.onTimeout)
		.and()
		//STATE - CUSTOMERSEARCH_RUNNING
		.withExternal().source(TechDemoSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
		.target(TechDemoSM_StateEnumerationImpl.SERVICE_NOT_AVAILABLE)
		.event(TechDemoSM_EventEnumerationImpl.onServiceNotAvailable)
		;
	}

Snippet 4

in my previous blog, when I used Topcased as UML modeling tool, while Topcased has no representation of transition activities or guards, I had to use custom made UML Steorotypes to define the Activities and Guard conditions on transition. Eclipse Papyrus further developed in this area and men can express on the UML model the Transition Activities and Guards making the UML Stereotypes obsolete. As you can see I created the Action and Guard in Eclipse Papyrus and Eclipse M2T XPand is able to detect those.

guard_action
Picture 2

So when Eclipse M2T Xpand traverse the model and detect Transition Activities and Guards creates Spring Beans to be Auto-Wired (Above was a simplistic example that doesn’t have dedicated Activity and Guard objects, further in the Blog, I will show you a State Machine that have custom Activities and Guard Conditions.), so State Machine realize the Transition without executing any Activity or checking Guard Condition.

The configuration of the listeners for our State Machine, we want to receive Event from the State Machine to be able observe its behavior during development and production.

	public StateMachineListener<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> listener() {
		return new StateMachineListenerAdapter<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl>() {
			@Override
			public void stateChanged(State<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> from,
					State<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> to) {
				LOG.info("State change to " + to.getId());
			}

			@Override
			public void eventNotAccepted(Message<TechDemoSM_EventEnumerationImpl> event) {
				LOG.warn("The event " + event.toString() + " is not accepted!");
			}
		};
	}

Snippet 5
For this purpose, we have one stateChanged and one eventNotAccepted Listeners telling us from an to States and in a case that State Machine does not accept a event, the name of the Event.

Now lets look how UML Model structure transferred to Java file structure for Spring State Machine Configuration, Enumerations, Action and Guards.

Directory Structure for CustomerSearchSM
Picture 3

Eclipse M2T XPand reads the name of the State Machine, Package names from the UML Model and creates the directory structure accordingly. I organized so that Actions and Guards placed under the Java packages which names are originated from State name that they are linked. I think, that will make easier to relate the Java code to the UML Model in development and production phases.

And finally a method, arranging the initial transition activity, which is setting the Control Object of the State Machine.

	public Action<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> initialStateTechDemoSMAction() {
		return new Action<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl>() {
			@Override
			public void execute(
					StateContext<TechDemoSM_StateEnumerationImpl, TechDemoSM_EventEnumerationImpl> context) {
				AbstractTechDemoControlObject controlObject = controlObjectLocator.getControlObject();
				((ControlObject) controlObject).resetStateMachine();
				context.getExtendedState().getVariables().put("TechDemoSMControlObject", controlObject);
			}
		};
	}

Snippet 6

There are few interesting points there.

If you read my previous blog, you know that I am a strong advocate that a State Machine should protect the variables that are representing its State, this area can be only accessible over the Event Mechanism of the State Machines, outside of that it should be only available as read only. Spring State Machine has loose concept of protecting its variables, it uses a HashMap ‘context.getExtendedState().getVariables()’ for this purpose, which can be modified from several sources in an uncontrolled fashion.

For this reason, I modeled in the UML Model specific objects called ControlObjects (at the end of the day we are doing Model Driven Software Development) and those has to be initialized at the initial Transition of a State Machine.

-A more complex State Machine:
For the warm ups, I choose the previous example from a simple State Machine.

Now we can go in details with a more complex State Machine, in my previous blog I configured the State Machines via Spring configuration files. Spring State Machine instead use Auto-wiring for dependency injection. This causes some complications, I will, after this blog, research the possibilities of configuring the Spring State Machine via configuration files, but let me show how I did it for this blog.

First, I have to tell, one of the biggest advantage of developing a Web Application via State Machines is that you can completely test it in a ‘Test Rig‘ all the business logic without attaching to any GUI Layer. That you can have %95 of your application tested without starting a Tomcat server and doing expansive round around trips.

For this purpose, State Machine Activities and Guard Condition should be representable as real implementations and test versions. This is really easy to do with Spring configuration files but to achieve that by Auto-Wiring we have to create some indirections.

Below is the picture of CustomerSearchSM State Machine, it is relatively more complex then TechDemoSM.

CustomerSearchSM
Picture 4

Main changes occur under the method….

        @Override
	public void configure(
			StateMachineTransitionConfigurer<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> transitions)

Snippet 7

now transitions having Activities and Guard Conditions…

//STATE - CUSTOMER_AUTHENTICATED
.withExternal()
.source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
.target(CustomerSearchSM_StateEnumerationImpl.ORDERS_LOADING)
.event(CustomerSearchSM_EventEnumerationImpl.onCustomerJoinedClicked)
.guard(customerSearchSMGuardContainer
			.getCUSTOMER_AUTHENTICATED___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard()
					.CUSTOMER_AUTHENTICATED_ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard())
.action(customerSearchSMActionContainer
			.getCUSTOMER_AUTHENTICATED___ORDERS_LOADING_OrdersLoading_ProcessLoadingOrders_action()
					.CUSTOMER_AUTHENTICATED_ORDERS_LOADING_OrdersLoading_ProcessLoadingOrders_action())
.and()

Snippet 8

to ensure that we can have production and test implementations, we have to build an indirection for Spring Dependency Injection so Activities are defined on the ‘customerSearchSMActionContainer’ and Guard Conditions on the ‘customerSearchSMGuardContainer’.

‘CustomerSearchSMGuardContainer’ looks like following…

@Configuration
public class CustomerSearchSMGuardContainer {
	@Autowired
	private org.salgar.swf_statemachine.customersearch.configuration.customer_authenticated.guard.CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard customer_authenticated___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard;

	public org.salgar.swf_statemachine.customersearch.configuration.customer_authenticated.guard.CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard getCUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard() {
		return customer_authenticated___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard;
	}

	@Autowired
	private org.salgar.swf_statemachine.customersearch.configuration.customer_authenticated.guard.CUSTOMER_AUTHENTICATED___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard customer_authenticated___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard;

	public org.salgar.swf_statemachine.customersearch.configuration.customer_authenticated.guard.CUSTOMER_AUTHENTICATED___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard getCUSTOMER_AUTHENTICATED___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard() {
		return customer_authenticated___ORDERS_LOADING_OrdersLoading_isOrderSearchRunning_guard;
	}
}

Snippet 9

real implementation beans are initialized in the system (you will see that more detailed when I explain the project structure) and Auto-Wired here…

For ex, ‘CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard’ …..

@Configuration
public class CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard {
	@Autowired(required = false)
	private ICUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFoundGuard realImplementation;

	@Bean
	public Guard<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> CUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard() {
		return new Guard<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl>() {
			@Override
			public boolean evaluate(
					StateContext<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> context) {
				if (realImplementation != null) {
					return realImplementation.evaluate(context);
				}
				return false;
			}
		};
	}
	public interface ICUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFoundGuard {
		boolean evaluate(
				StateContext<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> context);
	}
}

Snippet 10

here we are trying to inject ‘realImplementation’ with the ‘@Autowired(required = false)’ annotation, ‘required = false’ is critical here because if we want to test the special part of the State Machine, that mean we should be able start the State Machine with partial configuration, when we are testing CustomerSearch Component, it should be possible to run the State Machine without OrderSearch Component.

The real implementation of the ‘ICUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFoundGuard’ will look like the following…

@Configuration
public class CUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoinedTransitionGuardImpl {
    @Bean
    public CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard.ICUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFoundGuard getCUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoinedTransitionGuard() {
        return new CUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard.ICUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFoundGuard() {
            @Override
            public boolean evaluate(StateContext<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> context) {
                CustomerSearchSMControlObject customerSearchSMControlObject = (CustomerSearchSMControlObject) CustomerSearchSMControlObjectAccessor.getControlObject(context.getStateMachine());

                if (FindOrdersSM_StateEnumerationImpl.ORDERS_FOUND
                        .equals(((StateMachine)customerSearchSMControlObject.getFindOrdersSlaveSM()).getState().getId())) {
                    return true;
                }
                return false;
            }
        };
    }
}

Snippet 11

in which we are checking that a Slave State Machine in a specific State is to let the transition proceed.

To say the truth Auto-Wired solution produces too much code clutter, I rather have the option to initialize the Spring State Machine over Spring configuration files and decide there what to inject or not inject, or which implementation to inject.

And if you want to proof that ‘Test Rig‘ concept that I discussed in the previous blog works, I converted the feasibility application from my custom State Machine implementation to the Spring State Machine only using the Unit Tests in the Test Rig. I didn’t started the Tomcat once to see the application works or not and when I did run the application in Tomcat, I didn’t have to fix anything other then adapting Spring Web Flow to use the Spring State Machine.

How the magic works:
-Project Structure:
I will try to explain here the project structure, maven, git configurations, try to explain how Eclipse M2T XPand/MWE works and finally how to run the feasibility application in Tomcat.

Project Structure
Picture 5

Most of the Maven projects are there for Eclipse M2T (Model to Text) translation, swf_statemachine is the main Maven project, swf_statemachine_eclipse_dependencies, swf_statemachine_fornax_extension, swf_statemachine_xpand, swf_statemachine_techdemo_domain are all preparation for Eclipse M2T, which mainly occurs at project swf_statemachine_sm_model. swf_statemachine_model_impl is where we have the Test Rig implementation and real implementations of Activites and Guard occurs and final swf_statemachine_techdemo is where everything put together to be deployed to Tomcat.

Other then swf_statemachine_sm_model, swf_statemachine_model_impl nearly all the functionality stayed similar to original blog, there you can find detailed explanation about their functionality. I will only explain here swf_statemachine_sm_model, swf_statemachine_model_impl.

As you might understand now, project swf_statemachine_sm_model contains all the UML Model and code generation mechanism, while we might want different implementations for production, test or even several different implementations for production, it is wiser to have a project that contains the implementation classes for the Guards and Conditions and this project is swf_statemachine_model_impl.

swf_statemachine_impl
Picture 6

In this picture, you can see that I organized the actions and guards implementation under the packages with their UML State Machine name and the naming convention that I used help to identify with first part of name is the source state, second part the target state and third is the transition name. This way we can have a quick correlation between UML Model to Java Code.

This project also contains the Unit Test that I mentioned for the ‘Test Rig‘, the following is small snippet from the Test class.

@Test(dependsOnMethods = { "initialisation" }, enabled = true)
public void startSearch() {
	StateMachine<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> stateMachine = StateMachineFactories.getInstance().getCustomerSearchSMFactory().getStateMachine();
	stateMachine.start();

	String customerNumber = "987654321";

	Assert.assertNotNull(stateMachine);
	Assert.assertEquals(CustomerSearchSM
			.getStateMachineName(), stateMachine.getState().getId().getStateMachineName().getStateMachineName());

	Assert.assertEquals(
			WAITING_CUSTOMERSEARCH_START.getStateName(),
				stateMachine.getState().getId().getStateName());
	Assert.assertEquals(Boolean.TRUE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchInput());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchRunning());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchAuthentication());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchFound());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerJoin());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerOrders());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerOrderLoading());

	CustomerSearchStartEventPayload customerSearchStartEventPayload = new CustomerSearchStartEventPayload();
	customerSearchStartEventPayload.setCustomerNumber(customerNumber);
	Message<CustomerSearchSM_EventEnumerationImpl> message = MessageBuilder.withPayload(CustomerSearchSM_EventEnumerationImpl.onStartSearch).setHeader("customerSearchStartEventPayload", customerSearchStartEventPayload).build();

	CustomerManager customerManager = (CustomerManager) this.applicationContext
			.getBean("customerManager");
	customerManager.findCustomer(anyObject(String.class),
			anyObject(Object.class));
	EasyMock.replay(customerManager);
	stateMachine.sendEvent(message);
	EasyMock.verify(customerManager);

	Assert.assertEquals(
			CUSTOMERSEARCH_RUNNING.getStateName(),
			stateMachine.getState().getId().getStateName());
	Assert.assertEquals(customerNumber,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getCustomerNumber());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchInput());
	Assert.assertEquals(Boolean.TRUE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchRunning());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchAuthentication());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerSearchFound());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerJoin());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerOrders());
	Assert.assertEquals(Boolean.FALSE,
			CustomerSearchSMControlObjectAccessor.getControlObject(stateMachine).getRenderCustomerOrderLoading());

Snippet 12

I will not go to deep analysis about unit test and Test Rig, I discussed those quite extensively in the previous blog, you can find it there, but what to pay attention here while we exactly know which values the GUI elements should have in every state, we can prove those in our unit tests and our application would be %95 tested even without starting Tomcat once.

-Eclipse M2T and MWE:
Let’s start with swf_statemachine_sm_model, this is the Maven project that we have our UML Model in. ‘swf_sm_model.di’ is the Eclipse Papyrus file (and underlying XMI UML representation – ‘swf_sm_model.uml’) which contains TechDemoSM, CustomerSearchSM, FindCustomerSM and FindOrdersSM.

The technology responsible for creating executable Java code from this UML Model is Eclipse M2T MWE(Model Workflow Engine) which is telling M2T to read which UML Model and use which XPand template to generate Java code.

It looks like following…

module swf_statemachine_sm_model

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.generator.*
import org.eclipse.xtext.ui.generator.*
import org.eclipse.xpand2.*

import org.salgar.swf_statemachine.uml2.model.ExtendedUML2Metamodel

import org.salgar.swf_statemachine.uml2.Setup
import org.eclipse.xtend.typesystem.uml2.*
import org.eclipse.xtend.typesystem.uml2.UML2MetaModel
import org.eclipse.xtend.typesystem.emf.XmiReader
import org.eclipse.xtend.typesystem.uml2.profile.ProfilingExtensions.XmiReader
import org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel
import org.eclipse.emf.mwe.utils.Reader
var targetDir = "src-gen"
var fileEncoding = "Cp1252"
var modelPath = "src/model"
var projectName = "swf_statemachine_sm_model"
var runtimeProject

var list.set.property = 'order'
var type_header_text = ""
var annotation_source_key = ""
var type_footer_text = ""
var javabasic_entities = ""
var classes_operation_implementation_strategy ="none"
var javabasic_generateSerialVersionUID = "true"
var use_overridden_equals_hashcode_toString= "true"
var java_version = "5"
var generate_additional_collection_methods = ""

Workflow {
	bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
		platformUri=".."
		projectMapping = { projectName = "${projectName}" path = "${runtimeProject}" }
		projectMapping = { projectName = "swf_statemachine_domain" path = "../swf_statemachine_domain" }
		logResourceUriMap = false
		scanClassPath = false
	}
	bean = org.salgar.swf_statemachine.uml2.Setup {
		standardUML2Setup = true
 	}

 	bean = org.eclipse.xtend.typesystem.uml2.UML2MetaModel : umlMM { }

 	bean = org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel : swf_statemachine {
 		profile = "platform:/resource/swf_statemachine_sm_model/src/main/resources/swf_statemachine.profile.uml"
 	}
 	
 	bean = org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel : datatype {
 		profile = "platform:/resource/swf_statemachine_domain/src/main/resources/model/Datatype.profile.uml"
 	}
 	
 	bean = org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel : java {
 		profile = "platform:/resource/swf_statemachine_domain/src/main/resources/model/Java.profile.uml"
 	}
 	
 	component = org.eclipse.emf.mwe.utils.Reader {
 		uri = "platform:/resource/swf_statemachine_sm_model/src/main/resources/model/swf_sm_model.uml"
 		modelSlot = "model"
 	}
 	
 	component = org.eclipse.xpand2.Generator : enumerationGenerator {
 		metaModel = umlMM
 		metaModel = swf_statemachine
 		
 		globalVarDef = { name = "list_set_property" value = "'${list.set.property}'" }
 		globalVarDef = { name = "type_header_text" value = "''" }
 		globalVarDef = { name = "annotation_source_key" value = "''" }
 		globalVarDef = { name = "type_footer_text" value = "''" }
 		globalVarDef = { name = "javabasic_entities" value = "''" }
 		globalVarDef = { name = "classes_operation_implementation_strategy" value = "'${classes_operation_implementation_strategy}'" }
 		globalVarDef = { name = "javabasic_generateSerialVersionUID" value = "'${javabasic_generateSerialVersionUID}'" }
 		globalVarDef = { name = "use_overridden_equals_hashcode_toString" value = "'${use_overridden_equals_hashcode_toString}'" }
 		globalVarDef = { name = "java_version" value = "${java_version}" }
 		
 		fileEncoding = "ISO-8859-1"
 		outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

		advice = "templates::advices::javaBasicAssociationAdvices"
		
		expand = "template::stateMachineEnumeration::Root FOR model"
 	}

 	component = org.eclipse.xpand2.Generator : javaGenerator {
 		metaModel = umlMM
 		metaModel = datatype
 		metaModel = java
 		globalVarDef = { name = "list_set_property" value = "'${list.set.property}'" }
 		globalVarDef = { name = "type_header_text" value = "''" }
 		globalVarDef = { name = "annotation_source_key" value = "''" }
 		globalVarDef = { name = "type_footer_text" value = "''" }
 		globalVarDef = { name = "javabasic_entities" value = "''" }
 		globalVarDef = { name = "classes_operation_implementation_strategy" value = "'${classes_operation_implementation_strategy}'" }
 		globalVarDef = { name = "javabasic_generateSerialVersionUID" value = "'${javabasic_generateSerialVersionUID}'" }
 		globalVarDef = { name = "use_overridden_equals_hashcode_toString" value = "'${use_overridden_equals_hashcode_toString}'" }
 		globalVarDef = { name = "java_version" value = "${java_version}" }

 		fileEncoding = "ISO-8859-1"
 		outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

		advice = "templates::advices::javaBasicAssociationAdvices"

		expand = "template::Root::Root FOR model"
 	}

 	component = org.eclipse.xpand2.Generator : papyrusGenerator {
     		metaModel = umlMM
     		metaModel = swf_statemachine

     		fileEncoding = "ISO-8859-1"
     		outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

    		expand = "template::papyrusSSM::Root FOR model"
     	}
}

Snippet 13

major components…

	bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
		platformUri=".."
		projectMapping = { projectName = "${projectName}" path = "${runtimeProject}" }
		projectMapping = { projectName = "swf_statemachine_domain" path = "../swf_statemachine_domain" }
		logResourceUriMap = false
		scanClassPath = false
	}

Snippet 14

which is configuring Eclipse environment to configure MWE and configure Eclipse Project dependencies to run under Maven…

	bean = org.salgar.swf_statemachine.uml2.Setup {
		standardUML2Setup = true
 	}

Snippet 15

this one was tricky…

Eclipse Papyrus use UML 5.0.0 version, that in XTend typesystem until now is not defined, so I have to define it manually in Setup class.

public class Setup
	extends org.eclipse.xtend.typesystem.uml2.Setup {
	private static final String UML2_500_NS_URI = "http://www.eclipse.org/uml2/5.0.0/UML";

	@Override
	public void setStandardUML2Setup(boolean b) {
		super.setStandardUML2Setup(b);
		EPackage.Registry.INSTANCE.put(UML2_500_NS_URI, EPackage.Registry.INSTANCE.get(UMLPackage.eINSTANCE.getNsURI()));
	}
}

Snippet 16

custom UML Profile defining Java types…

 	bean = org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel : java {
 		profile = "platform:/resource/swf_statemachine_domain/src/main/resources/model/Java.profile.uml"
 	}

Snippet 17

reading the UML Model

 	component = org.eclipse.emf.mwe.utils.Reader {
 		uri = "platform:/resource/swf_statemachine_sm_model/src/main/resources/model/swf_sm_model.uml"
 		modelSlot = "model"
 	}

Snippet 18

creating Enumeration for StateMachines, States, Events….

 component = org.eclipse.xpand2.Generator : enumerationGenerator {
 	metaModel = umlMM
 	metaModel = swf_statemachine
 		
 	globalVarDef = { name = "list_set_property" value = "'${list.set.property}'" }
 	globalVarDef = { name = "type_header_text" value = "''" }
 	globalVarDef = { name = "annotation_source_key" value = "''" }
 	globalVarDef = { name = "type_footer_text" value = "''" }
 	globalVarDef = { name = "javabasic_entities" value = "''" }
 	globalVarDef = { name = "classes_operation_implementation_strategy" value = "'${classes_operation_implementation_strategy}'" }
 	globalVarDef = { name = "javabasic_generateSerialVersionUID" value = "'${javabasic_generateSerialVersionUID}'" }
 	globalVarDef = { name = "use_overridden_equals_hashcode_toString" value = "'${use_overridden_equals_hashcode_toString}'" }
 	globalVarDef = { name = "java_version" value = "${java_version}" }
 		
 	fileEncoding = "UTF-8"
 	outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

	advice = "templates::advices::javaBasicAssociationAdvices"
		
	expand = "template::stateMachineEnumeration::Root FOR model"
 	}

Snippet 19

creating necessary domain object for interoperability between State Machine and TechDemo Web Application…

component = org.eclipse.xpand2.Generator : javaGenerator {
 	metaModel = umlMM
 	metaModel = datatype
 	metaModel = java
 	globalVarDef = { name = "list_set_property" value = "'${list.set.property}'" }
 	globalVarDef = { name = "type_header_text" value = "''" }
 	globalVarDef = { name = "annotation_source_key" value = "''" }
 	globalVarDef = { name = "type_footer_text" value = "''" }
 	globalVarDef = { name = "javabasic_entities" value = "''" }
 	globalVarDef = { name = "classes_operation_implementation_strategy" value = "'${classes_operation_implementation_strategy}'" }
 	globalVarDef = { name = "javabasic_generateSerialVersionUID" value = "'${javabasic_generateSerialVersionUID}'" }
 	globalVarDef = { name = "use_overridden_equals_hashcode_toString" value = "'${use_overridden_equals_hashcode_toString}'" }
 	globalVarDef = { name = "java_version" value = "${java_version}" }

 	fileEncoding = "UTF-8"
 	outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

	advice = "templates::advices::javaBasicAssociationAdvices"

	expand = "template::Root::Root FOR model"
 }

Snippet 20

and creating Spring State Machine

 component = org.eclipse.xpand2.Generator : papyrusGenerator {
	metaModel = umlMM
	metaModel = swf_statemachine

	fileEncoding = "UTF-8"
	outlet = { path = "${runtimeProject}/src/generated/java" postprocessor = org.eclipse.xpand2.output.JavaBeautifier {} }

	expand = "template::papyrusSSM::Root FOR model"
}

Snippet 21

-Eclipse M2T and XPand:
Now let’s look how the Eclipse M2T XPand template looks like for generate Spring State Machine from UML, papyrusSSM.xpt (this file lies in swf_statemachine_xpand project)

«IMPORT uml»

«EXTENSION utility»
«EXTENSION org::fornax::cartridges::uml2::javabasic::m2t::Helper»
«EXTENSION templates::extensions::SwfStatemachineExtensions»

«DEFINE Root FOR uml::Model»
	«EXPAND Root FOREACH (List[uml::Package])ownedElement»
«ENDDEFINE»

/**
* Creates all packages
*/
«DEFINE Root FOR uml::Package»
	«EXPAND Root FOREACH ownedType.typeSelect(uml::StateMachine)»
	«EXPAND Root FOREACH nestedPackage»
«ENDDEFINE»

«DEFINE Root FOR uml::PackageImport»
«ENDDEFINE»

«DEFINE Root FOR uml::ProfileApplication»
«ENDDEFINE»

«DEFINE Root FOR uml::StateMachine»
	«EXPAND BuildStateMachine»
	«EXPAND ActionContainer»
	«EXPAND GuardContainer»
	«EXPAND ControlObjectLocator»
«ENDDEFINE»

«DEFINE BuildStateMachine FOR uml::StateMachine»
	«FILE getFQNPackagePath() + "/configuration/" + this.name + "Configuration.java"»
		package «getFQNPackageName()».configuration;

		import org.salgar.statemachine.domain.ControlObject;
		import org.salgar.swf_statemachine.«removeSM(this.name).toLowerCase()».controlobject.Abstract«removeSM(this.name)»ControlObject;
		import «getFQNPackageName()».enumeration.event.«this.name»_EventEnumerationImpl;
		import «getFQNPackageName()».enumeration.state.«this.name»_StateEnumerationImpl;

		import org.springframework.beans.factory.annotation.Autowired;
		import org.springframework.context.annotation.Configuration;
		import org.springframework.context.annotation.Bean;
		import org.springframework.messaging.Message;
		import org.springframework.statemachine.config.EnableStateMachineFactory;
		import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
		import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
		import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
		import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
		import org.springframework.statemachine.StateContext;
		import org.springframework.statemachine.action.Action;
		import org.springframework.statemachine.listener.StateMachineListener;
		import org.springframework.statemachine.listener.StateMachineListenerAdapter;
		import org.springframework.statemachine.state.State;

		import java.util.EnumSet;

		import org.apache.log4j.Logger;

        «IF (this.ownedComment != null) && (!this.ownedComment.isEmpty)»
        /**
            «FOREACH this.ownedComment AS comment»
                «comment.body»
            «ENDFOREACH»
        */
        «ENDIF»
		@Configuration
		@EnableStateMachineFactory(name="«this.name»")
		public class «this.name»Configuration extends EnumStateMachineConfigurerAdapter<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> {
			private static final Logger LOG = Logger.getLogger(«this.name»Configuration.class);

			@Autowired
			private «this.name»ActionContainer «this.name.toFirstLower()»ActionContainer;

			@Autowired
			private «this.name»GuardContainer «this.name.toFirstLower()»GuardContainer;

			@Autowired
			private «this.name»ControlObjectLocator controlObjectLocator;

			@Override
			public void configure(StateMachineConfigurationConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>config)
					throws Exception {
				config
					.withConfiguration()
						.listener(listener());
			}

			@Override
			public void configure(StateMachineStateConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> states) throws Exception {
				states.withStates().initial(«this.name»_StateEnumerationImpl.«findIntialState(this.allOwnedElements().typeSelect(uml::Pseudostate)).name», initialState«this.name»Action())
						.states(EnumSet.allOf(«this.name»_StateEnumerationImpl.class));
			}

			@Override
			public void configure(StateMachineTransitionConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> transitions) throws Exception {
				transitions
				«FOREACH this.allOwnedElements().typeSelect(uml::State).getOutgoings() AS transition SEPARATOR '.and()'»
					//STATE - «transition.source.name»
					«IF (transition.ownedComment != null) && (!transition.ownedComment.isEmpty)»
					/**
					    «FOREACH transition.ownedComment AS comment»
                            «comment.body»
                        «ENDFOREACH»
					*/
					«ENDIF»
					.withExternal()
					.source(«this.name»_StateEnumerationImpl.«transition.source.name»)
					.target(«this.name»_StateEnumerationImpl.«transition.target.name»)
						.event(«this.name»_EventEnumerationImpl.«transition.trigger.first().name»)
						«IF transition.guard != null»
								.guard(«this.name.toFirstLower()»GuardContainer.get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard().«transition.source.name»_«transition.target.name»_«transition.name»_«transition.guard.name»_guard())
								«EXPAND GuardImpl(transition)»
						«ENDIF»
						«IF transition.effect != null»
							.action(«this.name.toFirstLower()»ActionContainer.get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action().«transition.source.name»_«transition.target.name»_«transition.name»_«transition.effect.name»_action())
							«EXPAND ActionImpl(transition)»
						«ENDIF»
				«ENDFOREACH»;
			}

			public StateMachineListener<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> listener() {
                return new StateMachineListenerAdapter<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {
                    @Override
                    public void stateChanged(State<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> from, State<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> to) {
                        LOG.info("State change to " + to.getId());
                    }

                    @Override
                    public void eventNotAccepted(Message<«this.name»_EventEnumerationImpl> event) {
                        LOG.warn("The event " + event.toString() + " is not accepted!" );
                    }
                };
            }

            @Bean
            public Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> initialState«this.name»Action() {
                return new Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {
                    @Override
                    public void execute(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context) {
                        Abstract«removeSM(this.name)»ControlObject controlObject = controlObjectLocator.getControlObject();
                        ((ControlObject)controlObject).resetStateMachine();
                        context.getExtendedState().getVariables().put("«this.name»ControlObject", controlObject);
                    }
                };
            }
		}
	«ENDFILE»
«ENDDEFINE»

«DEFINE GuardImpl(uml::Transition transition) FOR uml::StateMachine»
    «FILE getFQNPackagePath() + "/configuration/" + transition.source.name.toLowerCase() + "/guard/" +  transition.source.name + "___" + transition.target.name + "_" + transition.name + "_" + transition.guard.name + "_guard" + ".java"»
        package «getFQNPackageName()».configuration.«transition.source.name.toLowerCase()».guard;

        import «getFQNPackageName()».enumeration.event.«this.name»_EventEnumerationImpl;
        import «getFQNPackageName()».enumeration.state.«this.name»_StateEnumerationImpl;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Bean;
        import org.springframework.statemachine.StateContext;
        import org.springframework.statemachine.guard.Guard;


        @Configuration
        public class «transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard {
            @Autowired(required = false)
            private I«transition.source.name»_«transition.target.name»_«transition.name»_«transition.guard.name»Guard realImplementation;

            @Bean
            public Guard<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> «transition.source.name»_«transition.target.name»_«transition.name»_«transition.guard.name»_guard() {
                return new Guard<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {

                    @Override
                    public boolean evaluate(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context) {
                        if(realImplementation != null) {
                            return realImplementation.evaluate(context);
                        }
                        return false;
                    }
                };
            }
            public interface I«transition.source.name»_«transition.target.name»_«transition.name»_«transition.guard.name»Guard {
                boolean evaluate(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context);
            }
        }
    «ENDFILE»
«ENDDEFINE»

«DEFINE ActionImpl(uml::Transition transition) FOR uml::StateMachine»
    «FILE getFQNPackagePath() +"/configuration/" + transition.source.name.toLowerCase() + "/action/" +  transition.source.name + "___" + transition.target.name + "_" + transition.name + "_" + transition.effect.name + "_action" + ".java"»
        package «getFQNPackageName()».configuration.«transition.source.name.toLowerCase()».action;

        import «getFQNPackageName()».enumeration.event.«this.name»_EventEnumerationImpl;
        import «getFQNPackageName()».enumeration.state.«this.name»_StateEnumerationImpl;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Bean;
        import org.springframework.statemachine.StateContext;
        import org.springframework.statemachine.action.Action;

        import org.apache.log4j.Logger;

        @Configuration
        public class «transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action {
            private static final Logger LOG = Logger.getLogger(«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action.class);

            @Autowired(required = false)
            private I«transition.source.name»_«transition.target.name»_«transition.name»_«transition.effect.name»Action realImplementation;

            @Bean
            public Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> «transition.source.name»_«transition.target.name»_«transition.name»_«transition.effect.name»_action() {
                return new Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {

                    @Override
                    public void execute(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context) {
                        if (realImplementation != null) {
                            realImplementation.execute(context);
                        } else {
                            LOG.warn("In the UML Model for this Action the Steorotype defines an implementation but Spring could not find a concrete implementation class!");
                        }
                    }
                };
            }
            public interface I«transition.source.name»_«transition.target.name»_«transition.name»_«transition.effect.name»Action {
                void execute(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context);
            }
        }
    «ENDFILE»
«ENDDEFINE»

«DEFINE ActionContainer FOR uml::StateMachine»
    «FILE getFQNPackagePath() + "/configuration/" +  this.name + "ActionContainer.java"»
        package «getFQNPackageName()».configuration;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class «this.name»ActionContainer {
            «FOREACH this.allOwnedElements().typeSelect(uml::State) AS state»
                «FOREACH state.getOutgoings() AS transition»
                	«IF transition.effect != null»
						@Autowired
						private «getFQNPackageName()».configuration.«state.name.toLowerCase()».action.«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action «transition.source.name.toLowerCase()»___«transition.target.name»_«transition.name»_«transition.effect.name»_action;

						public «getFQNPackageName()».configuration.«state.name.toLowerCase()».action.«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action() {
							return «transition.source.name.toLowerCase()»___«transition.target.name»_«transition.name»_«transition.effect.name»_action;
						}
					«ENDIF»
                «ENDFOREACH»
            «ENDFOREACH»
        }
    «ENDFILE»
«ENDDEFINE»

«DEFINE GuardContainer FOR uml::StateMachine»
    «FILE getFQNPackagePath() + "/configuration/" +  this.name + "GuardContainer.java"»
        package «getFQNPackageName()».configuration;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class «this.name»GuardContainer {
            «FOREACH this.allOwnedElements().typeSelect(uml::State) AS state»
                «FOREACH state.getOutgoings() AS transition»
                	«IF transition.guard != null»
						@Autowired
						private «getFQNPackageName()».configuration.«state.name.toLowerCase()».guard.«transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard «transition.source.name.toLowerCase()»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard;

						public «getFQNPackageName()».configuration.«state.name.toLowerCase()».guard.«transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard() {
							return «transition.source.name.toLowerCase()»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard;
						}
					«ENDIF»
                «ENDFOREACH»
            «ENDFOREACH»
        }
    «ENDFILE»
«ENDDEFINE»

«DEFINE ControlObjectLocator FOR uml::StateMachine»
    «FILE getFQNPackagePath() + "/configuration/" + this.name + "ControlObjectLocator.java"»
        package «getFQNPackageName()».configuration;

        import org.salgar.swf_statemachine.«removeSM(this.name).toLowerCase()».controlobject.Abstract«removeSM(this.name)»ControlObject;
        import org.springframework.beans.factory.annotation.Lookup;
        import org.springframework.stereotype.Component;

        @Component
        public class «this.name»ControlObjectLocator {

            @Lookup
            public Abstract«removeSM(this.name)»ControlObject getControlObject() {
                return null;
            }
        }
    «ENDFILE»
«ENDDEFINE»

Snippet 22

If we divide to small pieces…

«DEFINE Root FOR uml::Model»
	«EXPAND Root FOREACH (List[uml::Package])ownedElement»
«ENDDEFINE»

Snippet 23
this is the entry point for a XPand template, it accepts an UML Model and iterate over its elements, here we are iterating over all UML Packages…

«DEFINE Root FOR uml::Package»
	«EXPAND Root FOREACH ownedType.typeSelect(uml::StateMachine)»
	«EXPAND Root FOREACH nestedPackage»
«ENDDEFINE»

Snippet 24
here, we are recursively searching all UML Package and also expanding UML State Machines if we encounter one…

«DEFINE Root FOR uml::StateMachine»
	«EXPAND BuildStateMachine»
	«EXPAND ActionContainer»
	«EXPAND GuardContainer»
	«EXPAND ControlObjectLocator»
«ENDDEFINE»

Snippet 25
this part expands the core State Machine, the classes that contains Actions, Guards for Auto-Wired injections and Control Objects..

«DEFINE BuildStateMachine FOR uml::StateMachine»
	«FILE getFQNPackagePath() + "/configuration/" + this.name + "Configuration.java"»
		package «getFQNPackageName()».configuration;

		import org.salgar.statemachine.domain.ControlObject;
		import org.salgar.swf_statemachine.«removeSM(this.name).toLowerCase()».controlobject.Abstract«removeSM(this.name)»ControlObject;
		import «getFQNPackageName()».enumeration.event.«this.name»_EventEnumerationImpl;
		import «getFQNPackageName()».enumeration.state.«this.name»_StateEnumerationImpl;

		import org.springframework.beans.factory.annotation.Autowired;
		import org.springframework.context.annotation.Configuration;
		import org.springframework.context.annotation.Bean;
		import org.springframework.messaging.Message;
		import org.springframework.statemachine.config.EnableStateMachineFactory;
		import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
		import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
		import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
		import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
		import org.springframework.statemachine.StateContext;
		import org.springframework.statemachine.action.Action;
		import org.springframework.statemachine.listener.StateMachineListener;
		import org.springframework.statemachine.listener.StateMachineListenerAdapter;
		import org.springframework.statemachine.state.State;

		import java.util.EnumSet;

		import org.apache.log4j.Logger;

        «IF (this.ownedComment != null) && (!this.ownedComment.isEmpty)»
        /**
            «FOREACH this.ownedComment AS comment»
                «comment.body»
            «ENDFOREACH»
        */
        «ENDIF»
		@Configuration
		@EnableStateMachineFactory(name="«this.name»")
		public class «this.name»Configuration extends EnumStateMachineConfigurerAdapter<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> {
			private static final Logger LOG = Logger.getLogger(«this.name»Configuration.class);

			@Autowired
			private «this.name»ActionContainer «this.name.toFirstLower()»ActionContainer;

			@Autowired
			private «this.name»GuardContainer «this.name.toFirstLower()»GuardContainer;

			@Autowired
			private «this.name»ControlObjectLocator controlObjectLocator;

			@Override
			public void configure(StateMachineConfigurationConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>config)
					throws Exception {
				config
					.withConfiguration()
						.listener(listener());
			}

			@Override
			public void configure(StateMachineStateConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> states) throws Exception {
				states.withStates().initial(«this.name»_StateEnumerationImpl.«findIntialState(this.allOwnedElements().typeSelect(uml::Pseudostate)).name», initialState«this.name»Action())
						.states(EnumSet.allOf(«this.name»_StateEnumerationImpl.class));
			}

			@Override
			public void configure(StateMachineTransitionConfigurer<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> transitions) throws Exception {
				transitions
				«FOREACH this.allOwnedElements().typeSelect(uml::State).getOutgoings() AS transition SEPARATOR '.and()'»
					//STATE - «transition.source.name»
					«IF (transition.ownedComment != null) && (!transition.ownedComment.isEmpty)»
					/**
					    «FOREACH transition.ownedComment AS comment»
                            «comment.body»
                        «ENDFOREACH»
					*/
					«ENDIF»
					.withExternal()
					.source(«this.name»_StateEnumerationImpl.«transition.source.name»)
					.target(«this.name»_StateEnumerationImpl.«transition.target.name»)
						.event(«this.name»_EventEnumerationImpl.«transition.trigger.first().name»)
						«IF transition.guard != null»
								.guard(«this.name.toFirstLower()»GuardContainer.get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.guard.name»_guard().«transition.source.name»_«transition.target.name»_«transition.name»_«transition.guard.name»_guard())
								«EXPAND GuardImpl(transition)»
						«ENDIF»
						«IF transition.effect != null»
							.action(«this.name.toFirstLower()»ActionContainer.get«transition.source.name»___«transition.target.name»_«transition.name»_«transition.effect.name»_action().«transition.source.name»_«transition.target.name»_«transition.name»_«transition.effect.name»_action())
							«EXPAND ActionImpl(transition)»
						«ENDIF»
				«ENDFOREACH»;
			}

			public StateMachineListener<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> listener() {
                return new StateMachineListenerAdapter<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {
                    @Override
                    public void stateChanged(State<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> from, State<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> to) {
                        LOG.info("State change to " + to.getId());
                    }

                    @Override
                    public void eventNotAccepted(Message<«this.name»_EventEnumerationImpl> event) {
                        LOG.warn("The event " + event.toString() + " is not accepted!" );
                    }
                };
            }

            @Bean
            public Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> initialState«this.name»Action() {
                return new Action<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl>() {
                    @Override
                    public void execute(StateContext<«this.name»_StateEnumerationImpl, «this.name»_EventEnumerationImpl> context) {
                        Abstract«removeSM(this.name)»ControlObject controlObject = controlObjectLocator.getControlObject();
                        ((ControlObject)controlObject).resetStateMachine();
                        context.getExtendedState().getVariables().put("«this.name»ControlObject", controlObject);
                    }
                };
            }
		}
	«ENDFILE»
«ENDDEFINE»

Snippet 26
This creates the core of the Spring State Machine configuration, it is called for the State Machine previously identified in the UML Model. It identifies the UML Packages, State Machine name, it States ‘public void configure(StateMachineStateConfigurer states)‘, transitions ‘«FOREACH this.allOwnedElements().typeSelect(uml::State).getOutgoings() AS transition SEPARATOR ‘.and()’»‘, guards ‘«IF transition.guard != null»‘ (you have to create a Guard instance on Transition in Papyrus, if you want to have custom Guard implementation), actions ‘«IF transition.effect != null»‘ (you have to create a Effect instance on Transition in Papyrus, if you want to have custom Activity implementation), listeners for the ‘State Changed‘, ‘Event Not Accepted‘ and finally an initial state action ‘public Action initialState«this.name»Action()‘ which is initializing the control objects for the state machine.

I will not clutter here with the details of how Guards and Actions are generated, if people further demand explanation, I can extend it here but with above information I think it should be understandable.

-Spring Web Flow and integration with Spring State Machine:
Integrating Spring Web Flow and State Machine was quite seamless action which happens in swf_statemachine_techdemo project.

I have to change flow definition (swf_statemachine/swf_statemachine_techdemo/src/main/webapp/WEB-INF/flows/customersearch/customersearch.xml) of the previous blog slightly to initialize the Spring State Machine.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
        
    <on-start>
    	<evaluate expression="stateMachineContainer.startStateMachine()"/>
    </on-start>
	<view-state id="customerSearch">
		<transition on="onStartCustomerSearch" history="invalidate">
			<evaluate expression="customerSearchInputBB.searchCustomer()"/>
		</transition>
		<transition on="onCustomerAuthenticated" history="invalidate">
			<evaluate expression="customerSearchAuthenticationBB.customerGivesAuthentication()"></evaluate>
		</transition>
		<transition on="onCustomerJoined" history="invalidate">
			<evaluate expression="customerSearchJoinBB.customerJoined()"></evaluate>
		</transition>		
	</view-state>
</flow>

Snippet 27
with the ‘on-start‘ event of the Spring Web Flow we initialize our Spring State Machine via ‘stateMachineContainer.startStateMachine()‘. StateMachineContainer is class that I use to introduce Spring Scope concept to Spring State Machine, at its current version Spring State Machine does not respect the Spring Scopes (it is in development) but I want that CustomerSearchSM State Machine to run in Flow Scope, while I want it to be valid only for this Flow. If the end user would start another Flow, it should receive another instance of the CustomerSearchSM State Machine, this is specially important for the multi tab behavior of the modern browsers. So the State of the 2 or more Browser Tabs should not mix.

For this reason,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
			 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
			 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="org.salgar" />

	<bean id="stateMachineContainer" class="org.salgar.swf_statemachine.techdemo.web.customersearch.container.StateMachineContainer" scope="flow" />
</beans>

Snippet 28

stateMachineContainer defined in Flow Scope here to keep a reference to the State Machine during the Flow and container get the instances of the State Machine over the Factory Pattern.

A word of caution here, it is stated the instance of Action and Guard beans are shared on every instances of one Spring State Machine. So please avoid to use instance variables in the implementation of the Action and Guards.

-Preparing Project Environment
You can the project from github with the following command.

git clone -b papyrus git@github.com:mehmetsalgar/swf_statemachine.git

depending on where did you get the project, you can go to the following directory

swf_statemachine/swf_statemachine

and execute the following command to start the Maven build

mvn clean install -Pfull-build

I have to point something here, while Eclipse M2T has dependencies to some Eclipse Plugins, we have to use Maven Tycho plugin, which will prepare some artifacts for us. For this reason, we have to build with ‘-Pfull-build‘ option, when Maven build runs once with this option and installs the artifacts the local Maven repository, in the subsequent runs we can use the ‘mvn clean install -o‘ to build the project, which will run in offline mode and considerably faster.

To run the Web Application, you have install an Apache Tomcat (it might run in other containers also but I didn’t test it), you can download the Tomcat here.

Apache Tomcat

For the Comet/Atmosphere functionality (Primefaces push/socket functionality), you have to configure something in Tomcat Setup, which explained in details here in my previous blog.

When the Maven build is complete, you can copy ‘swf_statemachine_techdemo.war’ from swf_statemachine_techdemeo/target directory to your $TOMCATE_HOME/webapps directory and start your tomcat with the following command ‘./catalina.sh start’ from $TOMCATE_HOME/bin directory.

After you successfully deploy the application and call the URL http://localhost:8080/swf_statemachine_techdemo-1.0-SNAPSHOT/spring/customersearch the application should look like this.

Conclusion:
If you read the previous version of my blog, the weak point was always custom implementation of the State Machine. Now with the existence of the Spring State Machine, this weak point does not exists anymore.

Second point was, State Machine concept is so unknown to Java Developers, it looks eccentric to them and create a reluctance to use it because years long, it is told to them that it is too complex. With the current Agile trends with software development, it is such a valuable tool which is making possible your software to learn iteratively your business cases. When you think in current IT chaos to reduce IT costs, more and more Enterprise go with Agile methods and to scarcer documentations, it is vital to have such a tool.

I think Spring Organisation saw this fact also and with them putting their name on a State Machine framework can give really traction to ideas that we discussed here.

Advertisements

About Mehmet Salgar

Mehmet Salgar
This entry was posted in Ajax, Asynchrounus Processing, Atmosphere, Comet, Fornax, Iterative, JSF, M2T, Maven, MDA, MDD, MDSD, MWE, MWE2, Native IO, Open Source, Papyrus, Primefaces, Software Development, Spring State Machine, Spring Web Flow, State Explosion, State Machine, Web Socket, XPand, XText. Bookmark the permalink.

2 Responses to Ajax, Spring Web Flow and Spring State Machine

  1. Pingback: Extremely Ajaxified Web Application with Spring Webflow, Primefaces and State Machines | Mehmet Salgar's Blog

  2. Pingback: XText, Domain Specific Language and Spring State Machine | Mehmet Salgar's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s