XText, Domain Specific Language and Spring State Machine

A while ago, after I discovered the existence of the Spring State Machine and I wrote a blog how to convert an UML State Machine model to a runnable Spring State Machine. After I finished that, I start thinking about creating a Domain Specific Language (DSL) for Spring State Machine to see that it would be viable to use instead of using the UML model. Well if you want to develop a Domain Specific Language for yourself, if you don’t want to the hard work, the starting point for you should be the Eclipse XText project, which is an excellent framework to develop your own DSL even with syntax highlighting for popular IDEs like Eclipse IDE or IntelliJ IDEA. After we created our DSL we should be able to create Java code from this DSL/model which we will do with another Eclipe Framework, Xtend. You can see this blog also a good example implementation for Spring State Machine.

Content

Introduction:
The basis for this blog would be the feasibility study that I developed in my previous blog. We would try to model the State Machine that I designed in the previous blog via UML now with our own DSL.

You can see in the below picture the State Machine UML Diagram we used there.

CustomerSearchSM
Picture 1

and the model we create with our Domain Specific Language will look like the following when it is finished.

package org.salgar.swf_statemachine.ssm {
    statemachine CustomerSearchSM initialState WAITING_CUSTOMERSEARCH_START
        control-object {
            attribute customerNumberInternal type=java.lang.String
            attribute flowId type=java.lang.String
            attribute sessionId type=java.lang.String
            attribute customerSearchInputRenderPanels type=java.lang.String
            attribute customerAuthenticatedInternal type=java.lang.Boolean
            attribute customerJoinedInternal type=java.lang.Boolean
            attribute customerInternal type="org.salgar.swf_statemachine.techdemo.domain.Customer"
            attribute customerOrdersInternal type=java.util.List
            attribute actualGuiState type="org.salgar.swf_statemachine.ssm.customersearchsm.guistate.CustomerSearchGuiState"
            attribute findCustomerSlaveSM type="org.springframework.statemachine.StateMachine"
            attribute findOrdersSlaveSM type="org.springframework.statemachine.StateMachine"
        }
        events {
            event onStartSearch
            event onCustomerFound
            event onCustomerAuthenticatedClicked
            event onCustomerJoinedClicked
            event onOrdersLoaded
        }
        states {
            state WAITING_CUSTOMERSEARCH_START {
                transition SearchRunning => CUSTOMERSEARCH_RUNNING {
                    trigger {
                        onStartSearch
                    }
                    action {
                        ProcessSearchStart
                    }
                }
            }
            state CUSTOMERSEARCH_RUNNING {
                transition CustomerFound => CUSTOMER_FOUND {
                    trigger {
                        onCustomerFound
                    }
                    action {
                        ProcessCustomerFound
                    }
                }
            }
            state CUSTOMER_FOUND {
                transition CustomerFound => CUSTOMER_AUTHENTICATED {
                    trigger {
                        onCustomerAuthenticatedClicked
                    }
                    action {
                        ProcessCustomerFound
                    }
                }
            }
            state CUSTOMER_AUTHENTICATED {
                transition CustomerJoined => CUSTOMER_JOINED {
                    trigger {
                        onCustomerJoinedClicked
                    }
                    guard {
                        isOrdersFound
                    }
                    action {
                        ProcessOrdersFoundCustomerJoined
                    }
                }
                transition OrdersLoading => ORDERS_LOADING {
                    trigger {
                        onCustomerJoinedClicked
                    }
                    guard {
                        isOrderSearchRunning
                    }
                    action {
                        ProcessLoadingOrders
                    }
                }
                transition CustomerAuthenticationRemoved => CUSTOMER_FOUND {
                    trigger {
                        onCustomerAuthenticatedClicked
                    }
                    action {
                        ProcessCustomerAuthenticationRemoved
                    }
                }
            }
            state CUSTOMER_JOINED {
                transition CustomerAuthenticationRemovedFromJoined => CUSTOMER_FOUND {
                    trigger {
                        onCustomerAuthenticatedClicked
                    }
                    action {
                        ProcessCustomerAuthenticationRemoved
                    }
                }
                transition CustomerJoinedClicked => CUSTOMER_AUTHENTICATED {
                    trigger {
                        onCustomerJoinedClicked
                    }
                    action {
                        ProcessCustomerJoinRemoved
                    }
                }
            }
            state ORDERS_LOADING {
                transition OrdersLoaded => CUSTOMER_JOINED {
                    trigger {
                        onOrdersLoaded
                    }
                    action {
                        ProcessOrdersLoaded
                    }
                }
                transition CustomerAuthenticationFromOrdersLoadingRemoved => CUSTOMER_FOUND {
                    trigger {
                        onCustomerAuthenticatedClicked
                    }
                    action {
                        ProcessCustomerJoinRemoved
                    }
                }
                transition CustomerJoinRemoved => CUSTOMER_AUTHENTICATED {
                    trigger {
                        onCustomerJoinedClicked
                    }
                    action {
                        ProcessCustomerJoinRemoved
                    }
                }

            }
        }
    statemachine FindCustomerSM initialState NOT_RUNNING
        control-object {
            attribute customerNumber type=java.lang.String
            attribute masterStateMachine type="org.springframework.statemachine.StateMachine"
        }
        events {
            event onStartSearch
            event onCustomerFound
        }
        states {
            state NOT_RUNNING {
                transition SearchStarting => SEARCH_RUNNING {
                    trigger {
                        onStartSearch
                    }
                    action {
                        ProcessSearchStart
                    }
                }
            }
            state SEARCH_RUNNING {
                transition CustomerFound => CUSTOMER_FOUND {
                    trigger {
                        onCustomerFound
                    }
                    action {
                        ProcessCustomerFound
                    }
                }
            }
            state CUSTOMER_FOUND {

            }
        }
    statemachine FindOrdersSM initialState NOT_RUNNING
        control-object {
            attribute customerNumber type=java.lang.String
            attribute masterStateMachine type="org.springframework.statemachine.StateMachine"
            attribute orders type=java.util.List
        }
        events {
            event onOrderSearchRunning
            event onOrdersFound
        }
        states {
            state NOT_RUNNING {
                transition OrderSearchRunning => ORDER_SEARCH_RUNNING {
                    trigger {
                        onOrderSearchRunning
                    }
                    action {
                        ProcessOrdersSearchStart
                    }
                }
            }
            state ORDER_SEARCH_RUNNING {
                transition OrdersFound => ORDERS_FOUND {
                    trigger {
                        onOrdersFound
                    }
                    action {
                        ProcessOrdersFound
                    }
                }
            }
            state ORDERS_FOUND {

            }
        }
}

Snippet 1

I will explain above snippet in a detailed fashion in this blog but it seems simple and elegant isn’t it, compared creating the all Spring State Machine configuration with Java code.

XText:
XText is a so called language development framework, which we will use it for the creation of our textual domain-specific language (DSL) .

Well, to create a Domain Specific Language with XText we have to first define our Grammar file, in our case the file doing this is called ‘StateMachineDsl.xtext’ and look like the following.

grammar org.salgar.swf_statemachine.xtext.StateMachineDsl with org.eclipse.xtext.common.Terminals

generate stateMachineDsl "http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl"

Model:
	(elements+=Base)*;

PackageDeclaration:
	'package' name = QualifiedName '{'
		(elements += Base)*
	'}';

StateMachine:
	'statemachine' name=ID  'initialState' initialState = [State]
	'control-object' controlObject = ControlObject
	('events' ('{'
		(events += Event)*
	'}'))
	('states' ('{'
		(states += State)*
	'}'))
;

Base: PackageDeclaration | StateMachine;

QualifiedName:
	ID ('.' ID)*;

State:
	'state' name=ID
	'{'
		(transitions += Transition)*
	'}';

Transition:
	'transition' name=ID
	'=>' target = [State]
	'{'
		('trigger' '{' trigger = [Event] '}')
		('guard' '{' guard = Guard '}')?
		('action' '{' action = Action '}')?
	'}';

Event:
	'event' name=ID;

Guard: {Guard}
	name=ID;

Action: {Action}
	name=ID;

ControlObject:
	'{'
		(attributes+=ControlObjectAttribute)*
	'}'
;

ControlObjectAttribute:
	'attribute' name=ID
	('type' '=' (
			(type=AttributeBase)
		)
	)
;

AttributeBase: ObjectType | SimpleType;

SimpleType: type=InternalType;

ObjectType: type=STRING;

enum InternalType:
	NONE = 'NONE' | BOOLEAN = 'java.lang.Boolean' | INTEGER = 'java.lang.Integer' | LONG = 'java.lang.Long' | FLOAT = 'java.lang.Float' | DECIMAL = 'java.lang.Decimal' |
	STRING = 'java.lang.String' | DATE = 'java.lang.Date' | DATETIME = 'java.lang.DateTime' | TIME = 'java.lang.Time' | LIST = 'java.util.List'
;

Snippet 2

Not too much for creating our programming language isn’t it? šŸ™‚

If we dissect it to small parts.

grammar org.salgar.swf_statemachine.xtext.StateMachineDsl with org.eclipse.xtext.common.Terminals

Snippet 3

This is the definition of our grammar, all the artifacts that will be created from via Maven with XText would be linked under this namespace and artifact name. Terminal is the base class defining basic structures that will be valid for every DSL, like ID, STRING, etc…

generate stateMachineDsl "http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl"

Snippet 4

Here, we are coupling ‘stateMachineDsl’ Domain Specific Language with namespace http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl, all the models that will use this DSL should reference to it.

Model:
	(elements+=Base)*;

Snippet 5

Here, we define the root element for our DSL (if doesn’t have to be called ‘Model’, it can be anything). The element ‘Model’ can have many child nodes called Base and all will be added to the ‘elements’ collection. ‘*’ states that there will be multiple occurrences of Base, ‘+=’ states every discovered Base will be added to the ‘elements’ collection.

PackageDeclaration:
	'package' name = QualifiedName '{'
		(elements += Base)*
	'}';

Snippet 6

This will add packaging support to our DSL, now the artifacts that we will create, will not have unwanted name clashes.

QualifiedName:
	ID ('.' ID)*
;

Snippet 7

QualifiedName is a pattern defining the Qualified name structures.

StateMachine:
	'statemachine' name=ID  'initialState' initialState = [State]
	'control-object' controlObject = ControlObject
	('events' ('{'
		(events += Event)*
	'}'))
	('states' ('{'
		(states += State)*
	'}'))
;

Snippet 8

This is the core of our DSL while we want to create Spring State Machines with it, the definition of ‘StateMachine’. It states we will have a keyword ‘statemachine’ in our model and next word to it will be its name, then we will have ‘initialState’ keyword which will define the initial state of our state machine. Now you will see a fundamental concept of XText, in XText we can reference object by declaration or by reference. If I use the notation ‘initialState = State’ that will mean at this moment I have to create a new instance of a State object instead, with the notation ‘initialState = [State]’ I will reference to a State object that is created somewhere else, which you will see quite soon.

Then we define our control objects, if you read my previous blogs, I defend the idea that the state machines needs a special memory area that is strictly controlled by them, Spring State Machine uses a HashMap but this is little loose for my taste, I like to have an object that only state machine has a write access and all the others has only read access (an object that can only be manipulated with the action objects on transitions or states). DSL grammar in the vicinity of the ‘control-object’ keyword there will be a ‘{‘ and a ā€˜}ā€˜ and all the attribute definitions will belong to the control object. Attribute definitions will looks like the following.

ControlObjectAttribute:
	'attribute' name=ID
	('type' '=' (
			(type=AttributeBase)
		)
	)
;

Snippet 9

‘attribute’ keyword will have name property next to it and a type attribute which will define it type in the java code. It can be an enumeration for base types

enum InternalType:
	NONE = 'NONE' | BOOLEAN = 'java.lang.Boolean' | INTEGER = 'java.lang.Integer' | LONG = 'java.lang.Long' | FLOAT = 'java.lang.Float' | DECIMAL = 'java.lang.Decimal' |
	STRING = 'java.lang.String' | DATE = 'java.lang.Date' | DATETIME = 'java.lang.DateTime' | TIME = 'java.lang.Time' | LIST = 'java.util.List'
;

Snippet 10

or a String field for complex types (like ‘org.salgar.swf_statemachine.customersearchsm.GuiState’).

ObjectType: type=STRING;

Snippet 11

which are going to be represented in AttributeBase, by the way, ‘|’ signifies that AttributeBase is based on either ObjectType or SimpleType.

AttributeBase: ObjectType | SimpleType;

SimpleType: type=InternalType;

ObjectType: type=STRING;

Snippet 12

So if we look back to the where we left the definition of the state machines, we are at events. when DSL parser encounters ‘events’ keyword under state machine, we should look to the area inside of the accolades ‘{‘, ‘}’ add every event object that we find to the events collection.

The definition of the Event look like this.

Event:
	'event' name=ID;

Snippet 13

So the keyword ‘event’ defines the event and next word to it, its name.

Then we have the ‘states’, when DSL parser encounters the ‘states’ keyword, it should look to the area inside of the accolades ‘{‘, ‘}’, it would add every State object to the states collection.

The definition of states look like the following.

State:
	'state' name=ID
	'{'
		(transitions += Transition)*
	'}'
;

Snippet 14

So DSL parser when it encounters ‘state’ keyword, the next word will define the State name and the area inside of the accolades ‘{‘, ‘}’ will defines the transitions, which will be added to transitions collection.

And the transitions looks like the following.

Transition:
	'transition' name=ID
	'=>' target = [State]
	'{'
		('trigger' '{' trigger = [Event] '}')
		('guard' '{' guard = Guard '}')?
		('action' '{' action = Action '}')?
	'}'
;

Snippet 15

‘transition’ keyword defines the transition object, next word is the name of the transition, then we have the ‘=>’ operation which defines the target state for this operation. What we have to pay attention here is the ‘[State]’, while we are using ‘[‘, ‘]’ we are not creating a new instance of a State object but we are referencing to an existing State object. When you are modeling a state machine and you reference here a State that is not defined in the state machine you will receive compile time problems with Maven.

Then we will have the definition of the triggers for the transition and please pay attention that we are again using ‘[‘, ‘]’ and that will mean we will reference Event object that already declared in the event section of the state machine.

We will have finally the Guard and Action objects.

Guard: {Guard}
	name=ID
;

Action: {Action}
	name=ID
;

Snippet 16

Those will complete the definition of our Domain Specific Language and as you can see it is enough to define the state machine I designed in my previous blog for the feasibility study.

Now XText must create necessary artifacts for us via Maven, that will happen inside of a MWE2 Workflow ‘GenerateStateMachineDsl.mwe2’ in project ‘swf_statemachine_domain_specific_language’, like the following.

module org.salgar.swf_statemachine.xtext.GenerateStateMachineDsl

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*

var rootPath = "../"

Workflow {


	bean =  StandardLanguage : languageSetup {
		name = "org.salgar.swf_statemachine.xtext.StateMachineDsl"
		fileExtensions = "ssm"

		serializer = {
			generateStub = false
		}
		validator = {
			// composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
		}
	}
	
	component = XtextGenerator {
		configuration = {
			project = StandardProjectConfig {
				baseName = "swf_statemachine_domain_specific_language"
				rootPath = rootPath
				ideaPlugin = {
					enabled = false
				}
				mavenLayout = true
			}
			code = {
				encoding = "UTF-8"
				fileHeader = "/*\n * generated by Xtext \${version}\n */"
			}
		}
		language = languageSetup
	}
}

Snippet 17

these are standard configuration created via IntelliJ or Eclipse plugin which I will explain in Tooling section. I like to draw your attention to ‘fileExtensions = “ssm”‘ notation, this tells XText/Xtend that the model files that will speak your Domain Specific Language must have the ‘.ssm’ extension and it will search the all classpath for the files with this extension. With the ‘StandardLanguage’ element in the workflow we configure the XText via convention over configuration and it is able to find all necessary artifacts.

Xtend:
Now we defined our DSL, you can ask what now? We have to create the Spring State Machine configuration, for that we have to create Java Code and for that we have to use Eclipse Xtend framework.

Xtend is statically-type programming language that creates JVM bytecode and it is extremely compatible with Java. I will not place here all the Xtend code as single block would be to complex, so I will go with small snippets and explain the functionality.

But first, we have to give the Xtend the ability to understand our Domain Specific Language and the will happen in a MWE2 workflow. I will explain more detailed in the Project structure section but in short we will have two Maven projects, one will be responsible to convert our grammar to a Domain Specific Language via MWE2 and XText components and another Maven project with MWE2 workflow to create the Java code.

When the first projects runs, it will create a ‘StateMachineDslStandaloneSetup’ artifact to configure the Maven to understand our language which will look like the following.

package org.salgar.swf_statemachine.xtext;

import com.google.inject.Guice;
import com.google.inject.Injector;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.ISetup;
import org.eclipse.xtext.common.TerminalsStandaloneSetup;
import org.eclipse.xtext.resource.IResourceFactory;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.salgar.swf_statemachine.xtext.stateMachineDsl.StateMachineDslPackage;

@SuppressWarnings("all")
public class StateMachineDslStandaloneSetupGenerated implements ISetup {

	@Override
	public Injector createInjectorAndDoEMFRegistration() {
		TerminalsStandaloneSetup.doSetup();

		Injector injector = createInjector();
		register(injector);
		return injector;
	}
	
	public Injector createInjector() {
		return Guice.createInjector(new StateMachineDslRuntimeModule());
	}
	
	public void register(Injector injector) {
		if (!EPackage.Registry.INSTANCE.containsKey("http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl")) {
			EPackage.Registry.INSTANCE.put("http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl", StateMachineDslPackage.eINSTANCE);
		}
		IResourceFactory resourceFactory = injector.getInstance(IResourceFactory.class);
		IResourceServiceProvider serviceProvider = injector.getInstance(IResourceServiceProvider.class);
		
		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("ssm", resourceFactory);
		IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put("ssm", serviceProvider);
	}
}

Snippet 18

This will introduce our namespace http://www.salgar.org/swf_statemachine/xtext/StateMachineDsl to EMF ecore package. This is critical for Xtend to understand our DSL.

The file ‘StateMachineDslGenerator.xtend’ will be responsible for creating our Java Code.

So if we start looking small snippets….

class StateMachineDslGenerator extends AbstractGenerator {

	@Inject extension IQualifiedNameProvider

Snippet 19

Do you remember the QualifiedName we used in our language definition, this injection will help us to have fully qualified names for our code generation (like package names and so).

override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
		fsa.generateFile(resource.getAllContents.findFirst(object | object instanceof PackageDeclaration).fullyQualifiedName.toString("/") + "/enumeration/StateMachineEnumerationImpl.java",
			resource.allContents.toIterable.filter(StateMachine).complileStateMachineEnumeration(resource.getAllContents.findFirst(object | object instanceof PackageDeclaration) as PackageDeclaration))

		for(e : resource.allContents.toIterable.filter(StateMachine)) {
			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase + "/enumeration/state/" + e.name + "_StateEnumerationImpl.java",
			e.eAllContents.toIterable.filter(State).compileState(e))

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase + "/enumeration/event/" + e.name + "_EventEnumerationImpl.java",
			e.eAllContents.toIterable.filter(Event).compileEvent(e))

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + e.name + "ControlObjectLocator.java",
			e.compileControlObjectLocator)

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + e.name + "GuardContainer.java",
			e.compileGuardContainer)

			for(state : e.states) {
				for(transition : state.transitions) {
					if(transition.guard != null) {
						fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + state.name.toLowerCase() + "/guard/" + state.name + "___" + transition.target.name + "_" + transition.name + "_" + transition.guard.name + "_guard.java",
						e.compileGuard(state, transition))
					}
					if(transition.action != null) {
						fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + state.name.toLowerCase() + "/action/" + state.name + "___" + transition.target.name + "_" + transition.name + "_" + transition.action.name + "_action.java",
						e.compileAction(state, transition))
					}
				}
			}

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + e.name + "ActionContainer.java",
			e.compileActionContainer)

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/controlobject/Abstract" + e.name + "ControlObject.java",
			e.compileControlObject)

			fsa.generateFile(e.eContainer.fullyQualifiedName.toString("/") + "/" + e.name.toLowerCase() + "/configuration/" + e.name + "Configuration.java",
			e.compileStateMachine)
		}
	}

Snippet 20

first part of this snippet

fsa.generateFile(resource.getAllContents.findFirst(object | object instanceof PackageDeclaration).fullyQualifiedName.toString("/") +      "/enumeration/StateMachineEnumerationImpl.java",			resource.allContents.toIterable.filter(StateMachine).complileStateMachineEnumeration(resource.getAllContents.findFirst(object | object instanceof PackageDeclaration) as PackageDeclaration))

Snippet 21

is little bit this feasibility study specific, you will see that I only process first package declaration for the package creation but the language grammar declaration via XText does not say anything about that we will only have one single package declaration. We could have several, if you need to process more then one package, you can take this ‘as an exercise to the reader’ and try to improve this.

This snippet is selecting the first package in the model and creating an enumeration containing all the state machines in the model (careful eyes will notice here that Xtend support lambdas ‘findFirst(object | object instanceof PackageDeclaration)’) via calling the ‘complileStateMachineEnumeration’ method.

The rest of the methods using the same pattern, we select state machines, states, events, transition, guards, action, control objects and create them via the methods ‘compileState’, ‘compileEvent’, ‘compileControlObjectLocator’, ‘compileGuardContainer’, ‘compileGuard’, ‘compileAction’, ‘compileActionContainer’, ‘compileControlObject’ and ‘compileStateMachine’.

I will explain here the core method ‘compileStateMachine’, all the other ones are some repetition of the same concept.

If you like to understand why I have configure some elements in Spring State Machine the way I did, you have to read my previous blog, it explains the motivations behind it.

       def compileStateMachine(StateMachine e) '''
		package Ā«e.eContainer.fullyQualifiedNameĀ».Ā«e.name.toLowerCase()Ā».configuration;

		import org.salgar.statemachine.domain.ControlObject;
		import Ā«e.eContainer.fullyQualifiedNameĀ».Ā«e.name.toLowerCase()Ā».controlobject.AbstractĀ«e.nameĀ»ControlObject;
		import Ā«e.eContainer.fullyQualifiedNameĀ».Ā«e.name.toLowerCase()Ā».enumeration.event.Ā«e.nameĀ»_EventEnumerationImpl;
		import Ā«e.eContainer.fullyQualifiedNameĀ».Ā«e.name.toLowerCase()Ā».enumeration.state.Ā«e.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;

		@Configuration
		@EnableStateMachineFactory(name = "Ā«e.nameĀ»")
		public class Ā«e.nameĀ»Configuration extends
			EnumStateMachineConfigurerAdapter<Ā«e.nameĀ»_StateEnumerationImpl, Ā«e.nameĀ»_EventEnumerationImpl> {
			private static final Logger LOG = Logger.getLogger(Ā«e.nameĀ»Configuration.class);

			@Autowired
			private Ā«e.nameĀ»ActionContainer Ā«WordUtils.uncapitalize(e.name)Ā»ActionContainer;

			@Autowired
			private Ā«e.nameĀ»GuardContainer Ā«WordUtils.uncapitalize(e.name)Ā»GuardContainer;

			@Autowired
			private Ā«e.nameĀ»ControlObjectLocator controlObjectLocator;

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

			@Override
			public void configure(
				StateMachineStateConfigurer<Ā«e.nameĀ»_StateEnumerationImpl, Ā«e.nameĀ»_EventEnumerationImpl> states)
						throws Exception {
				states.withStates()
					.initial(Ā«e.nameĀ»_StateEnumerationImpl.Ā«e.initialState.nameĀ»,
							initialStateĀ«e.nameĀ»Action())
					.states(EnumSet.allOf(Ā«e.nameĀ»_StateEnumerationImpl.class));
		}

		@Override
		public void configure(
				StateMachineTransitionConfigurer<Ā«e.nameĀ»_StateEnumerationImpl, Ā«e.nameĀ»_EventEnumerationImpl> transitions)
						throws Exception {
			transitions
				Ā«FOR state : e.states SEPARATOR '.and()'Ā»
					//STATE - Ā«state.nameĀ»
					Ā«FOR transition : state.transitions SEPARATOR '.and()'Ā»
						//TRANSITION - Ā«transition.nameĀ»
						.withExternal().source(Ā«e.nameĀ»_StateEnumerationImpl.Ā«state.nameĀ»)
						.target(Ā«e.nameĀ»_StateEnumerationImpl.Ā«transition.target.nameĀ»)
						.event(Ā«e.nameĀ»_EventEnumerationImpl.Ā«transition.trigger.nameĀ»)
						Ā«IF transition.guard != nullĀ»
							.guard(Ā«WordUtils.uncapitalize(e.name)Ā»GuardContainer
							.getĀ«state.nameĀ»___Ā«transition.target.nameĀ»_Ā«transition.nameĀ»_Ā«transition.guard.nameĀ»_guard()
							.Ā«state.nameĀ»_Ā«transition.target.nameĀ»_Ā«transition.nameĀ»_Ā«transition.guard.nameĀ»_guard())
						Ā«ENDIFĀ»
						Ā«IF transition.action != nullĀ»
							.action(Ā«WordUtils.uncapitalize(e.name)Ā»ActionContainer
							.getĀ«state.nameĀ»___Ā«transition.target.nameĀ»_Ā«transition.nameĀ»_Ā«transition.action.nameĀ»_action()
							.Ā«state.nameĀ»_Ā«transition.target.nameĀ»_Ā«transition.nameĀ»_Ā«transition.action.nameĀ»_action())
						Ā«ENDIFĀ»
					Ā«ENDFORĀ»
				Ā«ENDFORĀ»;
		}


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

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

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

Snippet 22

Actually it is nothing more then getting StateMachine that we defined in our Domain Specific Language and reading some properties and creating Java Code from it. State Machine Enumerations, State Enumerations, Event Enumerations, iterating over Transitions, Guards, Actions.

Java code generated with Java will look like this.

package org.salgar.swf_statemachine.ssm.customersearchsm.configuration;

import org.salgar.statemachine.domain.ControlObject;
import org.salgar.swf_statemachine.ssm.customersearchsm.controlobject.AbstractCustomerSearchSMControlObject;
import org.salgar.swf_statemachine.ssm.customersearchsm.enumeration.event.CustomerSearchSM_EventEnumerationImpl;
import org.salgar.swf_statemachine.ssm.customersearchsm.enumeration.state.CustomerSearchSM_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;

@Configuration
@EnableStateMachineFactory(name = "CustomerSearchSM")
public class CustomerSearchSMConfiguration extends
	EnumStateMachineConfigurerAdapter<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> {
	private static final Logger LOG = Logger.getLogger(CustomerSearchSMConfiguration.class);

	@Autowired
	private CustomerSearchSMActionContainer customerSearchSMActionContainer;

	@Autowired
	private CustomerSearchSMGuardContainer customerSearchSMGuardContainer;

	@Autowired
	private CustomerSearchSMControlObjectLocator controlObjectLocator;

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

	@Override
	public void configure(
		StateMachineStateConfigurer<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> states)
				throws Exception {
		states.withStates()
			.initial(CustomerSearchSM_StateEnumerationImpl.WAITING_CUSTOMERSEARCH_START,
					initialStateCustomerSearchSMAction())
			.states(EnumSet.allOf(CustomerSearchSM_StateEnumerationImpl.class));
}

@Override
public void configure(
		StateMachineTransitionConfigurer<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> transitions)
				throws Exception {
	transitions
		//STATE - WAITING_CUSTOMERSEARCH_START
		//TRANSITION - SearchRunning
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.WAITING_CUSTOMERSEARCH_START)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
		.event(CustomerSearchSM_EventEnumerationImpl.onStartSearch)
		.action(customerSearchSMActionContainer
		.getWAITING_CUSTOMERSEARCH_START___CUSTOMERSEARCH_RUNNING_SearchRunning_ProcessSearchStart_action()
		.WAITING_CUSTOMERSEARCH_START_CUSTOMERSEARCH_RUNNING_SearchRunning_ProcessSearchStart_action()).and()
						//STATE - CUSTOMERSEARCH_RUNNING
		//TRANSITION - CustomerFound
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMERSEARCH_RUNNING)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_FOUND)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerFound)
		.action(customerSearchSMActionContainer
		.getCUSTOMERSEARCH_RUNNING___CUSTOMER_FOUND_CustomerFound_ProcessCustomerFound_action()
		.CUSTOMERSEARCH_RUNNING_CUSTOMER_FOUND_CustomerFound_ProcessCustomerFound_action()).and()
						//STATE - CUSTOMER_FOUND
		//TRANSITION - CustomerFound
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_FOUND)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerAuthenticatedClicked)
		.action(customerSearchSMActionContainer
		.getCUSTOMER_FOUND___CUSTOMER_AUTHENTICATED_CustomerFound_ProcessCustomerFound_action()
		.CUSTOMER_FOUND_CUSTOMER_AUTHENTICATED_CustomerFound_ProcessCustomerFound_action()).and()
						//STATE - CUSTOMER_AUTHENTICATED
		//TRANSITION - CustomerJoined
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_JOINED)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerJoinedClicked)
		.guard(customerSearchSMGuardContainer
		.getCUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard()
		.CUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_isOrdersFound_guard())
		.action(customerSearchSMActionContainer
		.getCUSTOMER_AUTHENTICATED___CUSTOMER_JOINED_CustomerJoined_ProcessOrdersFoundCustomerJoined_action()
		.CUSTOMER_AUTHENTICATED_CUSTOMER_JOINED_CustomerJoined_ProcessOrdersFoundCustomerJoined_action()).and()
		//TRANSITION - OrdersLoading
		.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()
		//TRANSITION - CustomerAuthenticationRemoved
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_FOUND)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerAuthenticatedClicked)
		.action(customerSearchSMActionContainer
		.getCUSTOMER_AUTHENTICATED___CUSTOMER_FOUND_CustomerAuthenticationRemoved_ProcessCustomerAuthenticationRemoved_action()
		.CUSTOMER_AUTHENTICATED_CUSTOMER_FOUND_CustomerAuthenticationRemoved_ProcessCustomerAuthenticationRemoved_action()).and()
						//STATE - CUSTOMER_JOINED
		//TRANSITION - CustomerAuthenticationRemovedFromJoined
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_JOINED)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_FOUND)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerAuthenticatedClicked)
		.action(customerSearchSMActionContainer
		.getCUSTOMER_JOINED___CUSTOMER_FOUND_CustomerAuthenticationRemovedFromJoined_ProcessCustomerAuthenticationRemoved_action()
		.CUSTOMER_JOINED_CUSTOMER_FOUND_CustomerAuthenticationRemovedFromJoined_ProcessCustomerAuthenticationRemoved_action()).and()
		//TRANSITION - CustomerJoinedClicked
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_JOINED)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerJoinedClicked)
		.action(customerSearchSMActionContainer
		.getCUSTOMER_JOINED___CUSTOMER_AUTHENTICATED_CustomerJoinedClicked_ProcessCustomerJoinRemoved_action()
		.CUSTOMER_JOINED_CUSTOMER_AUTHENTICATED_CustomerJoinedClicked_ProcessCustomerJoinRemoved_action()).and()
						//STATE - ORDERS_LOADING
		//TRANSITION - OrdersLoaded
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.ORDERS_LOADING)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_JOINED)
		.event(CustomerSearchSM_EventEnumerationImpl.onOrdersLoaded)
		.action(customerSearchSMActionContainer
		.getORDERS_LOADING___CUSTOMER_JOINED_OrdersLoaded_ProcessOrdersLoaded_action()
		.ORDERS_LOADING_CUSTOMER_JOINED_OrdersLoaded_ProcessOrdersLoaded_action()).and()
		//TRANSITION - CustomerAuthenticationFromOrdersLoadingRemoved
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.ORDERS_LOADING)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_FOUND)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerAuthenticatedClicked)
		.action(customerSearchSMActionContainer
		.getORDERS_LOADING___CUSTOMER_FOUND_CustomerAuthenticationFromOrdersLoadingRemoved_ProcessCustomerJoinRemoved_action()
		.ORDERS_LOADING_CUSTOMER_FOUND_CustomerAuthenticationFromOrdersLoadingRemoved_ProcessCustomerJoinRemoved_action()).and()
		//TRANSITION - CustomerJoinRemoved
		.withExternal().source(CustomerSearchSM_StateEnumerationImpl.ORDERS_LOADING)
		.target(CustomerSearchSM_StateEnumerationImpl.CUSTOMER_AUTHENTICATED)
		.event(CustomerSearchSM_EventEnumerationImpl.onCustomerJoinedClicked)
		.action(customerSearchSMActionContainer
		.getORDERS_LOADING___CUSTOMER_AUTHENTICATED_CustomerJoinRemoved_ProcessCustomerJoinRemoved_action()
		.ORDERS_LOADING_CUSTOMER_AUTHENTICATED_CustomerJoinRemoved_ProcessCustomerJoinRemoved_action())
				;
}


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

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

	@Bean
	public Action<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> initialStateCustomerSearchSMAction() {
		return new Action<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl>() {
			@Override
			public void execute(
					StateContext<CustomerSearchSM_StateEnumerationImpl, CustomerSearchSM_EventEnumerationImpl> context) {
				AbstractCustomerSearchSMControlObject controlObject = controlObjectLocator.getControlObject();
				((ControlObject) controlObject).resetStateMachine();
				context.getExtendedState().getVariables().put("CustomerSearchSMControlObject", controlObject);
			}
		};
	}
}

Project Structure:
If you look to my previous blogs some Maven artifacts are disappeared and some new ones are appeared.

xtext_project_structure
Picture 2

The projects ‘swf_statemachine_model’ (which was containing the UML Model for the state machine), ‘swf_statemachine_xpand’ (which was containing XPand template for Java Code generation from UML Models) were removed. New are, ‘swf_statemachine_domain_specific_language’ contains XText DSL definitions, ‘swf_statemachine_domain_specific_language_generator’ the functionality MWE2 and Xtend needs to create the Java Code, ‘swf_statemachine_domain_specific_language_model’ containing model for our DSL and ‘swf_statemachine_domain_specific_language_creator’ containing the MWE2 Workflow to create the Java Code.

All these projects contains nearly standard functionality so there isn’t too many thing to explain other then one functionality in ‘swf_statemachine_domain_specific_language_generator’ which contain a class called ‘SwfReader’. It seems standard model reader class from XText designed to work form Eclipse and it has problems to identify Maven classpath entries. For this reason I have to modify it little bit.

package org.salgar.swf_statemachine.xtext.reader.reader;

import org.apache.log4j.Logger;
import org.eclipse.xtext.mwe.Reader;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class SwfReader extends Reader {
    private static final Logger LOGGER = Logger.getLogger(SwfReader.class);

    @Override
    public void setUseJavaClassPath(boolean isUse) {
        if (isUse) {
            Set<String> classPathEntries = new HashSet();
            retrieveClassPathEntries(Thread.currentThread().getContextClassLoader(), classPathEntries);
            List<String> tmp = new ArrayList<>(classPathEntries);

            for (String entry : tmp) {
                addPath(entry);
            }

        }
    }

    private Set<String> retrieveClassPathEntries(ClassLoader classLoader, Set<String> classPathEntries) {
        List<String> givenLoaderClassPathEntries = new ArrayList<String>();
        if (classLoader instanceof URLClassLoader) {
            URLClassLoader tmp = (URLClassLoader) classLoader;
            for(URL classPathURL : tmp.getURLs()) {
                String classPath = classPathURL.getPath().trim().toLowerCase();

                if (classPath.endsWith("/") || classPath.endsWith(".jar")) {
                    givenLoaderClassPathEntries.add(classPathURL.getFile());
                }
            }
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Classpath entries from thread context loader: {" + givenLoaderClassPathEntries.toString() + "}");
            }
            classPathEntries.addAll(givenLoaderClassPathEntries);
        }
        if (givenLoaderClassPathEntries.isEmpty() && (classLoader instanceof URLClassLoader || classLoader == null)) {
            givenLoaderClassPathEntries.addAll(retrieveSystemClassPathEntries());
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("System classpath entries from thread context loader: {" + givenLoaderClassPathEntries.toString() + "}");
            }
            classPathEntries.addAll(givenLoaderClassPathEntries);
            return classPathEntries;
        }
        if( classLoader.getParent() != null) {
            retrieveClassPathEntries(classLoader.getParent(), classPathEntries);
        }
        return classPathEntries;
    }

    private static List<String> retrieveSystemClassPathEntries() {
        List<String> pathes = new ArrayList<>();
        String classPath = System.getProperty("java.class.path");
        String separator = System.getProperty("path.separator");
        String[] strings = classPath.split(separator);

        for(String path : strings) {
            pathes.add(path);
        }

        return pathes;
    }
}

Snippet 23

This version of the Reader search all the entries for the given Threads Classloader, also it parents and finally adds the entries also from the ‘java.class.path’ and ‘path.separator’ otherwise your MWE2 workflow because it can’t resolve the entries from Maven dependency elements.

When we will run the Maven Build, it will create the necessary Java artifacts into the ‘swf_statemachine_domain_specific_language_creator’ project, under the directory ‘src/generated’. Nearly all the implementations are same as the previous blog only some package structure had changed, so after adapting some unit tests (which also proving my Test Rig concept) unit test completed successfully and everything run correctly at the first start of the ‘swf_statemachine_techdemo’ web application which will look following when it runs.

When we run the Maven it will execute the ‘GenerateStateMachine.mwe2’ from the project ‘swf_statemachine_domain_specific_language_creator’…

module org.salgar.swf_statemachine.xtext.GenerateStateMachine

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*

var rootPath = ".."

Workflow {
		bean = org.salgar.swf_statemachine.xtext.StateMachineDslStandaloneSetup : languageStandaloneSetup {

		}

	    component =  org.salgar.swf_statemachine.xtext.reader.reader.SwfReader {
        		useJavaClassPath = true
        		uriFilter = org.eclipse.xtext.mwe.NameBasedFilter {
        			extension = 'ssm'
        		}

        		register = languageStandaloneSetup
        		loadResource = {
        			slot = "modelSlot"
        		}
        }

        component = org.eclipse.xtext.generator.GeneratorComponent {
            slot = "modelSlot"
            register = languageStandaloneSetup
            outlet = {
                path = "${rootPath}/src/generated/java"
            }
        }
}

Snippet 24

which will configure our DSL via ‘org.salgar.swf_statemachine.xtext.StateMachineDslStandaloneSetup’ bean to the workflow. Reader as we mentioned before loads our model file and GeneratorComponent which will create via Xtend our Spring State Machine artifacts to the configured ‘/src/generated/java’ directory.

Tools:

  • XText
  • You can find the XText reference documentation here.

    XText is available as plugin for IntelliJ IDEA and Eclipse IDE. You can read and download from this links.

    Plugins
    XText

    The advantage of using this plugins, you will get auto complete help from both IDE when you are developing your grammar. Further on if you follow the default wizard from both IDE to create XText projects, these wizards will also create IntelliJ and Eclipse plugins which help you with syntax highlighting when you are creating a model file with your grammar.

    I didn’t include those to my project structure because they were not relevant for my feasibility study but you can include those and consider as an exercise to the reader.

  • Xtend
  • You can find the Xtend reference documentation here.

    For All the other tooling like Maven, git, you can find all the necessary information from my previous blog, only thing different this version of the feasibility study will be on another git branch and can be downloaded via the following command.

  • Git
  • git clone -b XText git@github.com:mehmetsalgar/swf_statemachine.git

  • Maven
  • We can run the build with ‘mvn clean install’ command, there is one thing to play attention, some projects are still following the model driven approach and creating artifacts from UML Models, as you might know from my previous blogs, code generation from UML has some dependencies to some artifacts that can only be found in Eclipse p2 repositories and to access those maven build should be started only once with parameter ‘mvn clean install -Pfull-build’ after that these dependencies will be placed into the maven repository and this will not be needed anymore.

    A successful build will look like the following.

    [INFO] ------------------------------------------------------------------------
    [INFO] Reactor Summary:
    [INFO]
    [INFO] Spring WebFlow - State Machine .................... SUCCESS [0.217s]
    [INFO] Spring WebFlow - State Machine - Comet ............ SUCCESS [1.271s]
    [INFO] Spring WebFlow - State Machine - Fornax Extensions  SUCCESS [0.461s]
    [INFO] Spring WebFlow - State Machine - Domain Model ..... SUCCESS [8.056s]
    [INFO] Spring WebFlow - State Machine - Domain Specific Language  SUCCESS [13.781s]
    [INFO] Spring WebFlow - State Machine - Domain Specific Language Generator  SUCCESS [0.308s]
    [INFO] Spring WebFlow - State Machine - Domain Specific Language Model  SUCCESS [0.032s]
    [INFO] Spring WebFlow - State Machine - Techdemo Domain Model  SUCCESS [3.060s]
    [INFO] Spring WebFlow - State Machine - Domain Specific Language Creator  SUCCESS [2.529s]
    [INFO] Spring WebFlow - State Machine - Statemachine Model Implementation  SUCCESS [0.366s]
    [INFO] Spring WebFlow - State Machine - TechDemo ......... SUCCESS [2.575s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

    Snippet 25

    Maven configuration for these projects are pretty standard, there only one special thing to be mentioned, we are using two non standard maven directory structure ‘xtext-gen’ and ‘xtend-gen’ which are used for the generated code for XText and Xtend.

    MWE2 Workflow use deafult ‘xtext-gen’ directory but we have to configure the Xtend Maven plugin to use the ‘xtend-gen’.

    <plugin>
       <groupId>org.eclipse.xtend</groupId>
       <artifactId>xtend-maven-plugin</artifactId>
       <version>${xtext-version}</version>
       <executions>
         <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
             </goals>
         </execution>
       </executions>
       <configuration>
         <outputDirectory>${project.basedir}/src/main/xtend-gen</outputDirectory>
         <testOutputDirectory>${project.basedir}/src/test/xtend-gen</testOutputDirectory>
        </configuration>
    </plugin>
    

    Snippet 26

    also we have to add these directory to the classpath.

    <plugin>
    	<groupId>org.codehaus.mojo</groupId>
    	<artifactId>build-helper-maven-plugin</artifactId>
    	<version>${build_helper.plugin.version}</version>
    	<executions>
    		<execution>
    			<id>add-source-xtend</id>
    			<phase>initialize</phase>
    				<goals>
    					<goal>add-source</goal>
    				</goals>
    				<configuration>
    					<sources>
    						<source>src/main/xtend-gen</source>
    					</sources>
    				</configuration>
    		</execution>
    		<execution>
    			<id>add-source</id>
    			<phase>initialize</phase>
    			<goals>
    				<goal>add-source</goal>
    				<goal>add-resource</goal>
    			</goals>
    			<configuration>
    				<sources>
    					<source>src/main/xtext-gen</source>
    				</sources>
    				<resources>
    					<resource>
    						<directory>src/main/xtext-gen</directory>
    						<excludes>
    							<exclude>**/*.java</exclude>
    							<exclude>**/*.g</exclude>
    						</excludes>
    					</resource>
    				</resources>
    			</configuration>
    		</execution>
    	</executions>
    </plugin>
    

    Snippet 27

  • Tomcat
  • You can find the instructions how to configure the tomcat and starting here in the previous blog

    Conclusion:
    Well I still think using UML Model for starting point for Spring State Machine is a better idea for complex and big projects but it is nice to see with the help of the XText it is actually quite easy and feasible to create our Domain Specific Language for Spring State Machine and use it. Can we keep an eye of overall view when the project has more then 100 state machines when they are modeled with our DSL compared to UML Model, I don’t know, it is little bit of personal taste thing but at least you know from this blog that is possible.

    As always I hope this blog will be useful for somebody.

    Advertisements

    About Mehmet Salgar

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

    One Response to XText, Domain Specific Language and Spring State Machine

    1. Pingback: Extremely Ajaxified Web Application with Spring Webflow, Primefaces and State Machines | 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