Jump to: navigation, search

VIATRA/Integration/MWE2 Integration

Transformation Chains Using VIATRA MWE 2 Integration

Motivation

Defining heterogeneous model transformation chains using a specialized description language.

  • Sequence of model transformation steps can be easily specified and maintained.
  • Nontrivial side-effect relations between transformation steps can be handled.
  • The execution of event-driven transformations can be explicitly controlled.
  • Because of the specialized language, the description code base is short and easily expandable.

MWE 2 Basics

Generic modeling workflows can be easily described via using the Xtext Modeling Workflow Engine. It enables the creation of components with various attributes, and it executes them in order. More, exact information about the Xtext MWE 2 can be found at https://www.eclipse.org/Xtext/documentation/306_mwe2.html

VIATRA MWE2 Integration Library

Overview

The VIATRA MWE2 Integration library provides support for defining complex transformation chains using Xtext MWE 2 workflows. As it can be seen on the figure below, the transformation chain itself is an MWE2 component, meaning that it can be easily integrated into higher level modeling workflows.

VIATRAMWE2.png

The transformation chain consists of different typed transformation steps. These are the following:

  • Composite transformation steps: The composite steps are responsible for creating the control flow of the model transformation chain. These steps (Sequence, Loop, Conditional, Parallel) can contain other transformation steps. Once a given composite transformation step is executed, it will call its sub-steps in a specific manner (For example in case of Sequences, execute the contained steps in their order of appearance).
  • Model Transformation Steps: These steps cannot contain any child steps, meaning that they are used for implementing actual model transformations. They have access to the workflow context, to and from which they can push and pull data. They can also communicate directly with each other using parametric messages.

Messaging

  • The MWE2 integration library implements basic publish/subscribe based messaging. Transformation steps can publish messages to certain topics. If an other transformation step is subscribed to that given topic, it will receive the message as the execution token reaches the target step. Once the target step processes the message, the topic is informed about the act, and the message is deleted from the topic.
  • Topic management: A message broker is responsible for the management of the individual topics. It enables the transformation steps to subscribe to any existing or new topic. It is also responsible for handing the incoming messages to the appropriate topic. Transformation steps can also query the message broker for the now messages on their subscribed topics.
  • Message creation and processing: Message creation is done by message factories. These user specified objects can create messages with certain parameters. Message processors on the other hand are used by the transformation steps to process certain typed incoming messages.
  • SubscribeTo and PublishTo objects: These objects represent publications and subscriptions on the transformation step's side. They store which message factory or processor should be used with the specified topic.

Control flow

As mentioned before, the execution sequence of transformation steps can be defined by a standard control statement hierarchy, similar to imperative languages. The following control statements are supported:

  • Transformation Chain: Transformation chains area the root elements of the control hierarchy. They can contain other transformation steps (they cannot contain transformation chains), which will be executed in order of their addition to the chain.
  • Sequence: Sequences are basic control elements. They contain other transformation steps, and execute them in the order of their addition.
  • Conditional: Conditional statements decide which one of their child transformation steps will be executed (these can be sequences, loops etc.), based on the validity of a condition specified by the user.
  • For loop: Simple 'for' loop, it will iterate through its child elements, N times, where N is directly specified by the user using a numeric literal, or calculated dynamically.
  • Foreach loop: Loop that iterates through an IIterable element provided by the user.
  • While loop: A simple 'while' loop that will iterate through its child elements as long as the condition provided by the user appears to be valid.
  • Do..While loop: Similar to the 'while' loop, however instead of checking the condition before each cycle, this one checks it after it.
  • Parallel: Control statement that enables the parallel execution of transformation steps. Steps contained by this element will be assigned to independent threads. Note, that trasnformation steps that take part in a two way communication with one another should not be added to parallel sections, as in case of parallel execution timing errors can occur(lost messages, double messages etc.).

Prerequisites and Installation

Update site installation (before VIATRA 1.2.0)

Update site installation

  • Install Xtext SDK 2.9.0 update site
  • Install VIATRA SDK 1.2.0 and the MWE2 Integration feature from this update site (TODO change this to release site when 1.2.0 is released)


Library Classes and Interfaces

The library defines a set of interfaces and abstract base classes which represent the main elements the previously described transformation workflow semantics. User defined behavior can be added via the extension of these elements

Interfaces

  • ICompositeStep: Defines the interface of composite transformation steps that can contain other steps.
  • ITransformationStep: Interface that defines the main entry points of transformation steps. The user defined functionality can be implemented in the execute, initialize and dispose methods.
  • ITopic: Defines the interface of topics. It includes methods for publishing and querying messages, as well as managing subscriptions.
  • IMessage: Interface for messages, defines methods for getting and setting the message parameter.
  • IMessageFactory: Provides an interface for creating certain typed messages. As messages contain parameters, message- and parameter types are specified as generic type parameters.
  • IMessageProcessor: Provides an interface for creating certain typed messages. As messages can contain parameters, message- and parameter types are specified as generic type parameters.
  • ISubscribeTo: Defines methods that enable the simple usage of topics and message factories.
  • IPublishTo: Defines methods that enable the simple usage of topics and message processors.
  • IMessageBroker: Defines methods for the management of Topics and subscriptions.
Providers
  • IProvider: Basic provider interface that defined methods for adding the actual workflow context to a given provider.
  • IConditionProvider: Extends the IProvider, enables the user to provide dynamically calculated conditions for loops and conditional statements.
  • IIterableProvider: Extends the IProvider, enables the user to provide IIterable objects for 'foreach' loops.
  • IIterationNumber: Extends the IProvider, enables the user to dynamically calculate the number of iterations a 'for' loop does.

Classes

  • TransformationChain: Implements the ICompositeStep and the MWE2 IWorkFlowComponent interface. As it is a composite step, it can contain additional transformation steps. In its execute method, the registered transformation steps are initialized and executed.
  • TransformationStep:Abstract class that implements the ITransformationStep interface, that acts as a base class for user defined transformation steps. It incorporates the following features:
    • Support for containing ISubscribeTo and IPublishTo objects
    • Initialize the context attribute in the initialize() method
    • Process all incoming messages
    • Support the addition of user defined initialization, execute functionality and message publication (doInitialize(), doExecute(), publishMessages())
  • MessageBroker: The MessageBroker is a singleton class that manages topics and subscriptions. It enables transformation steps to subscribe to topics, send and query messages, as well as acknowledge message processing (In case the message is successfully processed, the message itself will be removed from the topic's appropriate queue).
  • Topic: Topics contain subscribing transformation steps and messages sent to these subscribers. Messages are contained in a multi map whose key objects are transformation steps, while the value objects are lists of messages.
  • SubscribeTo: SubscribeTo objects contain an IMessageProcessor implementation and specify the Topic to which the transformation step has subscribed. The event processor is used for processing the messages that are provided by the topic. This ISubscribeTo implementation also features the priority property, which help in defining an order in which conflicting channels can be processed.
  • PublishTo: As specified in the IPublishTo interface, instances of this class contain an IMessageFactory object and the qualified name of a topic. The message factory is responsible for adding events to the aforementioned topic. The PublishTo class provides an easy to use interface for adding publishing messages.
Control Structures
  • Sequence:Composite transformation step that implements a basic sequence control flow construction. It initializes and executes its contained steps in the same sequence they have been defined.
  • Conditional: Composite transformation step that implements an 'IF' style conditional construction. The condition is specified by an IConditionProvider which enables the specification of dynamically evaluated conditions. If the condition evaluation returns true, the ifTrue step is executed, if otherwise, the ifFalse step is executed. As the condition is evaluated in run time, both of the steps is initialized.
  • ForLoop: Composite transformation step that implements a 'for' style loop. The number of iterations can either be explicitly specified at compile time, using the iterations attribute, or dynamically calculated by an IIterationNumberProvider.
  • ForeachLoop: Composite transformation step that implements a 'foreach' style loop. This kind of loop requires an IIterable object to iterate through. It is provided in run time by an IIterableProvider.
  • WhileLoop: Composite transformation step that implements a 'while' style loop. similar to the conditional step, the dynamically evaluated loop condition is provided by an IConditionProvider. The condition is evaluated before the current iteration.
  • DoWhileLoop: Composite transformation step that implements a 'do..while' style loop. similar to the while loop, the dynamically evaluated loop condition is provided by an IConditionProvider. In this case however, the condition is evaluated after the current iteration instead.
  • Parallel: Composite transformation step that enables the parallel execution of transformation steps. Each transformation step will be assigned to a new thread.
    • Be advised: The parallel regions should be independent from each other, as there is no order of execution defined. This means, that typically parallel regions should not send each other parametric messages.
Provider Implementations
  • BaseProvider: Abstract base provider implementation that has an IWorkFlowContext attribute. This way inheriting classes can access the workflow context.
  • BaseIterationNumberProvider: Basic iteration number provider that extend the BaseProvider abstract class and returns the number handed to it in its constructor.
Example Messages, Factories and Processors
  • StringMessage: Basic message implementation that has a String typed parameter. It serves as a basic example for implementing custom messages.
  • StringMessageFactory: Message Factory that is responsible for the creation of StringMessage objects. Similar to the StringMessage class, this class serves as an example.
  • StringMessageProcessor: Message Processor that is responsible for processing StringMessage objects. It serves as an example for user defined message processors.
Wrapping fine grained event-driven transformations
  • IController: An interface that defines methods for explicitly controlling the execution of a fine-grained event-driven model transformation step.
  • ISchedulerController: Interface that defines functions which are used when an IController object also wraps an VIATRA EVM Scheduler.
  • MWE2BaseControllableScheduler: An EVM scheduler that enables the workflow to explicitly control the execution of a fine-grained event-driven transformation. As it implements the IController interface, its usage is similar to the MWE2ControllableExecutor. Internally, however, this solution uses a custom Scheduler object instead of an Executor. Using a custom Scheduler means, that the original VIATRA EVM Scheduler will be overridden.

Examples

All of the below stated examples can be found in the mwe2integration.examples project in the following repository:

Basics

Typically the following steps are needed to create an MWE Transformation Chain:

  • Create a new Eclipse plug-in project via File Menu --> New --> Project
  • Create a new MWE2 file (in a not default package) using the similar method. If prompted, add the Xtext nature to the project.
  • Add the following plug-in dependencies:
    • org.eclipse.viatra.emf.mwe2integration
    • org.eclipse.emf.mwe2.language
  • Define custom transformation steps, messages and other classes supporting them.
  • Define workflow structure in the MWE2 file
  • Run the MWE2 workflow or integrate it in the transformation architecture.

The example workflows are mostly implemented in Xtend, of course plain java implementations are possible as well.

Simple Example

The usage of these classes can be most effectively described via a simple 'Hello World' example that defines two very simple transformation steps. These transformation steps do not communicate with each other using messages, and no complex control structures are used.

Define Transformation Steps

In the following step define two very simple transformation steps. Add these classes to the package created before.

ExampleATransformationStep.xtend

/** 
 * An example transformation step. User defined transformation steps should extend the abstract class 
 * TransformationStep. It already implements the core features of transformation steps, namely the management of 
 * publishings, subscriptions, as well as message processing and -creation.
 * 
 */
class ExampleATransformationStep extends TransformationStep {
	override doInitialize(IWorkflowContext ctx) {
		//this method is called after the workflow context is assigned to the step
		//The transformation should be  initialized here
		System.out.println("Init A transformation")
		//The context can be used to access global variables
		//via ctx.get("String")
	}
 
	override void doExecute() {
		//This method is executed during the execute method of a given transformation step,
		//after the received messages are processed and before outgoing messages are sent-
		//Run the transformation (or any other user defined function) here 
		System.out.println("A transformation executed")
	}
 
	override void dispose() {
		// Dispose functions
		System.out.println("Dispose A transformation")
	}
 
	//In this method the sending of messages can be defined
	//Typically the following implementation, which sends a given message type to all publishing topics, 
	//is sufficient
	override void publishMessages() {
		publishings.forEach[
			publishMessage("MESSAGE A")
		]
	}
}

ExampleBTransformationStep.java

/** 
 * An example transformation step. User defined transformation steps should extend the abstract class 
 * TransformationStep. It already implements the core features of transformation steps, namely the management of 
 * publishings, subscriptions, as well as message processing and -creation.
 * 
 * This class is the same as the A variant, it is needed as most of the examples call for two different 
 * transformation steps
 */
class ExampleBTransformationStep extends TransformationStep {
	override doInitialize(IWorkflowContext ctx) {
		//this method is called after the workflow context is assigned to the step
		//The transformation should be  initialized here
		System.out.println("Init B transformation")
		//The context can be used to access global variables
		//via ctx.get("String")
	}
 
	override void doExecute() {
		//This method is executed during the execute method of a given transformation step,
		//after the received messages are processed and before outgoing messages are sent-
		//Run the transformation (or any other user defined function) here 
		System.out.println("B transformation executed")
	}
 
	override void dispose() {
		// Dispose functions
		System.out.println("Dispose B transformation")
	}
 
	//In this method the sending of messages can be defined
	//Typically the following implementation, which send a given message type to all publishing topics, 
	//is sufficient
	override void publishMessages() {
		publishings.forEach[
			publishMessage("MESSAGE B")
		]
	}
}

Define Transformation Structure

Define the transformation chain structure using the MWE2 language. Make sure to include all the referenced packages (if the classes are in the same package as the MWE2 file, include that package as well), and that the module name should match the containing package as well.

SimpleChainExample.mwe2

module org.eclipse.viatra.emf.mwe2integration.example.SimpleChainExample
 
//Import the necessary libraries
//Note, that the package containing the workflow needs to be imported as well (if it contains used resources)
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.messages.*
import org.eclipse.viatra.emf.mwe2integration.examples.resources.*
 
//The workflow and components are defined in the usual MWE2 fashion
Workflow {
	//Add a TransformationChain component
	//This component represents an MWE2 component that can contain numerous transformations
	component = TransformationChain {
		//Add transformation steps
		//These classes all extend the TransformationStep abstract class, which implements their core features.
		//User defined functionality can be added via overriding the doExecute() method 
		step = ExampleATransformationStep{}
		//Multiple transformation steps will be executed in order of their definition
		//Note that in the other examples, more elaborate control constructions will be presented.
		step = ExampleBTransformationStep{}
	}
	//You can add additional regular MWE2 components here
}

After this, run the MWE2 file as an MWE 2 workflow.

Simple Example with Conditional

In the following example, the basic workflow will be expanded with a conditional transformation step. Conditional transformation steps are composite steps that determine which one of their child elements should be executed, based on a dynamically computed condition. In order for the conditional statement to function, a user defined class that contains the aforementioned condition and implements the IConditionProvider interface.

ExampleIfCondition.xtend

/**
 * This IConditionProvider provides a basic example dynamically computed condition for IF Statements.
 * 
 * Note that this provider is used with the examples only, and it implements no logical function.
 */
public class ExampleIfCondition extends BaseProvider implements IConditionProvider{
	private Boolean ret = true;
 
	/**
	 * Once the condition of the conditional step is evaluated, this method is called
	 * In this case, it will return true and false in an alternating fashion.
	 */
	override apply() {
		var toReturn = ret;
		ret = !ret;
		return toReturn
	}
}


Define Transformation Structure

The workflow model is similar to the previous example, however it incorporates a conditional transformation step with the above specified condition provider. If the provider return true, the ExampleATransformationStep is executed, if not the ExampleBTransformationStep will run instead.

SimpleConditionalExample.mwe2

module org.eclipse.viatra.emf.mwe2integration.example.SimpleConditionalExample
 
//Import the necessary libraries
//Note, that the package containing the workflow needs to be imported as well (if it contains used resources)
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.messages.*
import org.eclipse.viatra.emf.mwe2integration.examples.resources.*
 
//The workflow and components are defined in the usual MWE2 fashion
Workflow {
	//Add a TransformationChain component
	//This component represents an MWE2 component that can contain numerous transformations
	component = TransformationChain {
		//A conditional transformation step can serve as a basic IF statement in any regular programming language
		step = Conditional{
			//If conditions can be specified in any class that implements the IConditionProvider interface
			condition = ExampleIfCondition{}
			//If the condition is true, this step will be executed
			ifTrue =  ExampleATransformationStep{}
			//If the condition is false, this step will be executed
			ifFalse = ExampleBTransformationStep{}
		}
	}
	//You can add additional regular MWE2 components here
}

After this, run the MWE2 file as an MWE 2 workflow.

Simple Example with Loops

It is also possible to define loops in the transformation chain. In the following example, supported loop statements will be implemented.

While and Do...While loops

While loops have a condition property, based on which it will be determined if the next iteration should take place or not. In MWE2 based transformation chains, these conditions can be implemented in classes that implement the IConditionProvider interface. In the example the following class will be used:

ExampleWhileCondition.xtend

/**
 * This IConditionProvider provides a basic example dynamically computed condition for WHILE Loops.
 * 
 * Note that this provider is used with the examples only, and it implements no logical function.
 */
public class ExampleWhileCondition extends BaseProvider implements IConditionProvider{
	private int reference = 3;
	private int actual = 0;
 
	//This condition provider will return true on the first three calls, and false on the fourth
	//Resulting in a 3 length loop
	override apply() {
		if(reference > actual){
			actual++
			return true
		}
		false
	}
}

For Each Loop

Similar to Java, for each loops need an iterable object they can iterate through. These objects can be provided by an IIterableProvider. In this example the following iterable provider will be used.

ExampleLoopIterable.xtend

/**
 * This IIterableProvider creates a list on which a FOREACH loop can iterate through.
 * 
 * Note that this provider is used with the examples only, and it implements no logical function.
 */
public class ExampleLoopIterable extends BaseProvider implements IIterableProvider{
	override getIterable() {
		var list = Lists.newArrayList
		list.add("1")
		list.add("2")
		return list
	}
}

For Each Loop

In MWE2 based transformation chains, the number of iterations done by 'for' loops can either be explicitly specified by setting the 'iterations' attribute, or can be dynamically calculated by an IIterationNumberProvider. In this example both loop definitions will be included. For the latter the following provider will be used:

ExampleLoopNumber.xtend

/**
 * This IIterationNumberProvider dynamically evaluates how many iterations a FOR cycle should iterate through.
 * 
 * Note that this provider is used with the examples only, and it implements no logical function.
 */
public class ExampleLoopNumber extends BaseProvider implements IIterationNumberProvider{
	override getIterationNumber() {
		return 2
	}
}


Define Transformation Structure

After all the providers have been implemented, the workflow itself should be implemented. This example workflow contains one of all the supported loop types:

SimpleLoopExample.mwe2

module org.eclipse.viatra.emf.mwe2integration.example.SimpleLoopExample
 
//Import the necessary libraries
//Note, that the package containing the workflow needs to be imported as well (if it contains used resources)
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.messages.*
import org.eclipse.viatra.emf.mwe2integration.examples.resources.*
 
//The workflow and components are defined in the usual MWE2 fashion
Workflow {
	//Add a TransformationChain component
	//This component represents an MWE2 component that can contain numerous transformations
	component = TransformationChain {
		//**** FOR ****
		//All the basic loop types are supported here as well.
		//The number of iterations a for loop does can be explicitly specified in the MWE2 file
		step = ForLoop{
			iterations = "4"
			step = ExampleATransformationStep{}
		}
		//Or it can be dynamically calculated by a class that implements IIterationNumberProvider
		step = ForLoop{
			iterationProvider = ExampleLoopNumber{}
			step = ExampleATransformationStep{}
		}
 
		//**** FOREACH ****
		//In case of a ForEach loop, an iterable needs to be provided on which the loop can iterate through
		//This is done by an IIterableProvider
		step = ForEachLoop{
			iterable = ExampleLoopIterable{}
			step = ExampleATransformationStep{}
		}
 
		//**** While ****
		//The condition a while loop uses can be dynamically calculated by an IConditionProvider
		step = WhileLoop{
			condition = ExampleWhileCondition{}
			step = ExampleATransformationStep{}
		}
		//It is similar in case of a DoWhile loop as well
		step = DoWhileLoop{
			condition = ExampleWhileCondition{}
			step = ExampleATransformationStep{}
		}
 
	}
	//You can add additional regular MWE2 components here
}

After this, run the MWE2 file as an MWE 2 workflow.

Complex Example with Messaging

So far, examples have not utilized direct, message based communication yet. In the following example, one of the transformation steps sends a simple, example message to the other one.

In order to create the aforementioned functionality, a user defined parametric message should be implemented.

ExampleMessage.xtend

/**
 * Example message, that contains a String parameter. Generally, messages should implement the IMessage<?> interface. 
 */
class ExampleMessage implements IMessage<String>{
	String parameter
 
	new(String parameter){
		this.parameter = parameter
	}
	override getParameter() {
		return parameter
	}
	override setParameter(String parameter) {
		this.parameter = parameter
	}
}

In order to create ExampleMessage objects, an ExampleMessageFactory should be defined. This class is responsible for the creation of the previously defined example messages.

ExampleMessageFactory.xtend

/**
 * Example message factory that is responsible for the creation of ExampleMessage objects.
 */
class ExampleMessageFactory implements IMessageFactory<String,ExampleMessage>{
	/**
	 * If the defined parameter matches ExampleMessage, a new instance is created.
	 */
	override createMessage(Object parameter) throws InvalidParameterTypeException{
		if(parameter.isValidParameter){
			return new ExampleMessage(parameter as String)
		}
		throw new InvalidParameterTypeException
	}
 
	/**
	 * Checks if the defined parameter matches the parameter type of the ExampleMessage class
	 */
	override isValidParameter(Object parameter) {
		if(parameter instanceof String){
			return true
		}
		return false;
	}
}

On the receiving side, a message processor will process the previously defined example messages. Therefore, a user defined message processor needs to be specified in order to the workflow to function correctly.

ExampleMessageProcessor.xtend

/** 
 * Example message processor that is used for processing ExampleMessages. 
 * Other custom processors should look similar to this one.
 */
class ExampleMessageProcessor implements IMessageProcessor<String, ExampleMessage> {
	//A ITransformationStep object that contains this processor
	protected ITransformationStep parent;
 
	override getParent() {
		return parent
	}
 
	override setParent(ITransformationStep parent) {
		this.parent = parent
	}
 
	/**
	 * Processes the given message. If it contains parameter of the wrong type,  
	 * an InvalidParameterTypeException is thrown.
	 */
	override processMessage(IMessage<?> message) throws InvalidParameterTypeException{
		if(message instanceof ExampleMessage){
			System.out.println(message.parameter+" processed")
		}else{
			throw new InvalidParameterException
		}
	}
}

Define Transformation Structure

After the message and the related supporting classes have been created, the workflow using direct messaging should be implemented:

MessageExample.mwe2

module org.eclipse.viatra.emf.mwe2integration.example.MessageExample
 
//Import the necessary libraries
//Note, that the package containing the workflow needs to be imported as well (if it contains used resources)
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.messages.*
import org.eclipse.viatra.emf.mwe2integration.examples.resources.*
 
//The workflow and components are defined in the usual MWE2 fashion
Workflow {
	//Add a TransformationChain component
	//This component represents an MWE2 component that can contain numerous transformations
	component = TransformationChain {
		// Transformation steps can either publish to or subscribe to various message topics
		// Topic registration and subscription management is supervised by the message broker	
		step = ExampleBTransformationStep{
			//PublishTo objects wrap a topic and a message factory
			//The specified message factory is used to put a message to the specified topic
			publishing = PublishTo{ topicName = "ExampleTopicA" factory = ExampleMessageFactory{}}
		}
		step = ExampleATransformationStep{
			publishing = PublishTo{ topicName = "ExampleTopicA" factory = ExampleMessageFactory{}}
		}
 
		step = ForLoop{
			iterations = "3"
			//Subscriptions work in a similar way
			step = ExampleATransformationStep{
				// SubscribeTo objects wrap a topic and a message processor 
				// that will be used to process messages sent to the topic
				subscription = SubscribeTo{ topicName = "ExampleTopicA" processor = ExampleMessageProcessor{}}
			}
		}
		//Typically when a transformation step is executed:
		//		1. Each subscription's messages are processed using the proper processor (all of the messages)
		//		2. User defined action is performed (doExecute())
		//		3. The user can define which kind of messages are sent to which topic. (publishMessages(Operand))
 
		//In this case, as all of the published messages are processed during the first operation, 
		//the following execution sequence is expected:
		// 		B transformation executed
		// 		A transformation executed
		// 		MESSAGE B processed
		// 		MESSAGE A processed
		// 		A transformation executed
		// 		A transformation executed
		// 		A transformation executed
	}
	//You can add additional regular MWE2 components here
}

After this, run the MWE2 file as an MWE 2 workflow.

Complex Example with Parallel Execution

It is also possible to define parallel sections in the transformation chain. This can be achieved via using the 'parallel' transformation step. It works similar to the fork-join semantics of the UML activity diagrams. In this example, no further element definitions are needed, the workflow reuses the previously defined steps and providers.

ParallelExample.mwe2

module org.eclipse.viatra.emf.mwe2integration.example.ParallelExample
 
//Import the necessary libraries
//Note, that the package containing the workflow needs to be imported as well (if it contains used resources)
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.messages.*
import org.eclipse.viatra.emf.mwe2integration.examples.resources.*
 
//The workflow and components are defined in the usual MWE2 fashion
Workflow {
	//Add a TransformationChain component
	//This component represents an MWE2 component that can contain numerous transformations
	component = TransformationChain {
		//It is possible to define sections that are executed parallel
		//In this case, each contained transformation step is executed in separate threads.
		step = Parallel{
			//In this parallel section the for loop containing 3 A transformation steps and
			//A B transformation step is executed parallel
			step = ForLoop{
				iterations = "3"
				step = ExampleATransformationStep{}
			}
 
			step = ExampleBTransformationStep{}
		}
 
		//This Additional step will be executed AFTER all of the parallel sections are executed
		//This is similar to the FORK-JOIN semantics of UML activity diagrams
		step = ExampleBTransformationStep{}
 
		//In this case, As far as the execution sequence is concerned, no exact sequence can be defined 
		//due to the parallel nature of this workflow
		//The only fact that can be specified is that the second B transformation step will be executed
		//AFTER the parallel section
	}
	//You can add additional regular MWE2 components here
}

Complex Example (CPS)

A more complex example can be found in the VIATRA CPS example project. The project itself aims at providing an example, how EMF VIATRA related technologies can be used in a model driven development workflow.

Naturally, this example contains numerous different transformation methods and other auxiliary steps. The VIATRA MWE2 integration library can be used to define the structure of these transformation steps. The example project here contains both batch- and fine-grained, event-driven M2M transformations. In this section, a VIATRA based event driven M2M transformation will be used.

MWE Workflow Components

  • Initializer component: This standard MWE 2 workflow component is responsible for the initialization of the transformation environment. It creates the model resources, generates the source model, instantiates the main VIATRA query engine used by the transformation steps and creates the output project structure. It also adds the created resources to the workflow context.

Transformation Steps

  • M2M Transformation Step: In this case, the model-to-model transformation is a VIATRA based incremental event-driven transformation, implemented in the following CPS project.

The transformation step contains the following attributes:

    protected MWEBaseControllableSchedulerFactory factory; //Scheduler factory that creates a scheduler responsible for controlling the ED transformation.
    protected AdvancedViatraQueryEngine engine; //Main VIATRA query engine used by the transformation steps.
    protected CPS2DeploymentTransformationViatra transformation; //The previously mentioned VIATRA based event-driven transformation.

It overrides the following methods:

    @Override
    public void doInitialize(IWorkflowContext ctx) {
        CPSToDeployment cps2dep = (CPSToDeployment) ctx.get("model");
        engine = (AdvancedViatraQueryEngine) ctx.get("engine");
        factory = new MWEBaseControllableSchedulerFactory();
        transformation = new CPS2DeploymentTransformationViatra();
        transformation.setScheduler(factory);
        transformation.initialize(cps2dep, engine);
        System.out.println("Initialized model-to-model transformation");
    }
 
    @Override
    public void dispose() {
        //dispose tranformation
        transformation.dispose();
        System.out.println("Disposed model-to-model transformation");
    }
 
    public void doExecute() {
        factory.run();
        while (!factory.isFinished()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Model-to-model transformation executed");
 
    }
  • Change Monitor Transformation Step: This step detects the changes in the intermediary deployment model, and sends the changes themselves to the M2T transformation step.

The transformation step contains the following attributes:

    protected AdvancedViatraQueryEngine engine;    //Main VIATRA Query engine used by the transformation steps.
    protected DeploymentChangeMonitor monitor;  //A Change monitor class that collects all the changes in a specified deployment model, for a period of time.
    protected DeploymentChangeDelta delta;      //Delta object containing changes in the model

It overrides the following methods:

    @Override
    public void doInitialize(IWorkflowContext ctx) {
        // create transformation
        System.out.println("Initialized change monitor");
        //get the main VIATRA Query engine from the Workflow context (it was added by the initializer)
        engine = (AdvancedViatraQueryEngine) ctx.get("engine");
        //Get the deployment model root from the workflow context
        Deployment deployment = ((CPSToDeployment) ctx.get("model")).getDeployment();
        //Initialize monitor and start monitoring.
        monitor = new DeploymentChangeMonitor(deployment, engine);
        try {
            monitor.startMonitoring();
        } catch (ViatraQueryException e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public void doExecute() {
         //Create a new checkpoint that contains all the model changes since the previous checkpoint up to the current time.
        delta = monitor.createCheckpoint();
        System.out.println("Checkpoint created");
    }
 
    @Override
    public void publishMessages() {
        //Send the model changes to all target channels
        for (IPublishTo iPublishTo : publishTo) {
            iPublishTo.publishMessage(delta);
        }
    }
 
    @Override
    public void dispose() {
        System.out.println("Disposed change monitor");
    }
  • M2T Transformation Step: The M2T transformation step uses a distributed code generator component that returns the output files as character sequences.

The transformation step contains the following attributes:

    protected AdvancedViatraQueryEngine engine; 
    public ICPSGenerator generator;    //Code Generator component
    public String projectName;         //Name of the output project
    public String sourceFolder;        //Location of the output project
    public List<M2TOutputRecord> output;  //Output of the transformation
    public DeploymentChangeDelta delta;   //Incoming delta

It overrides the following methods:

    @Override
    public void doInitialize(IWorkflowContext ctx) {
        System.out.println("Initialized model-to-text transformation");
        //get the main VIATRA Query engine from the Workflow context (it was added by the initializer)
        engine = (AdvancedViatraQueryEngine) ctx.get("engine");
        //get the output project name from the Workflow context (it was added by the initializer)        
        projectName = (String) ctx.get("projectname");
        //get the output project folder from the Workflow context (it was added by the initializer)
        sourceFolder = (String) ctx.get("folder");
        //Create new code generator
        generator = new CodeGenerator(projectName,engine,true);
 
    }
 
    @Override
    public void doExecute() {
        //Create the snippets generated from the intermediary model
        ChangeM2TOutputProvider provider = new ChangeM2TOutputProvider(delta, generator, sourceFolder);
        output = provider.generateChanges();
        System.out.println("Model-to-text transformation executed");
    }
 
    @Override
    public void publishMessages() {
        //Send output to serializer
        for (IPublishTo iPublishTo : publishTo) {
            iPublishTo.publishMessage(output);
        }
    }
 
    @Override
    public void dispose() {
        System.out.println("Disposed model-to-text transformation");
 
    }
  • Serializer Transformation Step: Creates new source files based on the output provided by the M2T transformation.

The transformation step contains the following attributes:

    public DefaultSerializer serializer; //CPS serializer
    public String sourceFolder;          //Location of the source folder
    public List<M2TOutputRecord> m2tOutput; //Output created by the M2T transformation

It overrides the following methods:

    @Override
    public void doInitialize(IWorkflowContext ctx) {
        System.out.println("Initialized serializer");
        //Init serializer
        serializer = new DefaultSerializer();
        sourceFolder = (String) ctx.get("folder");
    }
 
    @Override
    public void doExecute() {
        ListBasedOutputProvider provider = new ListBasedOutputProvider(m2tOutput);
        //Serialize output
        serializer.serialize(sourceFolder, provider, new JavaIOBasedFileAccessor());
 
        System.out.println("Serialization completed");
    }
 
    @Override
    public void dispose() {
        System.out.println("Disposed serializer");
    }
 
    //Wraps the snippet list provided by the M2T transformation step
    public class ListBasedOutputProvider implements IM2TOutputProvider{
        private List<M2TOutputRecord> records = new ArrayList<M2TOutputRecord>();
 
        public ListBasedOutputProvider(List<M2TOutputRecord> records) {
            super();
            this.records = records;
        }
 
        @Override
        public List<M2TOutputRecord> getOutput() {
            return records;
        }
 
        public void setRecords(List<M2TOutputRecord> records) {
            this.records = records;
        }
    }


  • Model Modifier Transformation Step: Modifies the source CPS model and sends events to the M2M transformation, meaning that the workflow has restarted.

The transformation step contains the following attributes:

    protected CPSToDeployment model; //Model root
    protected CPSModelBuilderUtil modelBuilder = new CPSModelBuilderUtil(); //CPS model modification utility class

It overrides the following methods:

    @Override
    public void doInitialize(IWorkflowContext ctx) {
        model = (CPSToDeployment) ctx.get("model");  
    }
 
    @Override
    public void doExecute() {
        modifyModel();
        System.out.println("Model modification executed");
 
    }
 
    @Override
    public void dispose() {
        System.out.println("Disposed model modifier");
    } 
 
    private void modifyModel(){
        ApplicationType appType = null;
        EList<ApplicationType> appTypes = model.getCps().getAppTypes();
        for (ApplicationType applicationType : appTypes) {
            if(applicationType.getId().contains("AC_withStateMachine")){
                appType = applicationType;
            }
        }
 
        HostInstance instance = null;
        EList<HostType> hostTypes = model.getCps().getHostTypes();
        for (HostType type : hostTypes) {
            if(type.getId().contains("HC_appContainer")){
                instance = type.getInstances().get(0);
            }
        }       
        if(appType != null && instance !=null){
            modelBuilder.prepareApplicationInstanceWithId(appType,"new.app.instance", instance);
        }
    }

Events, Event Factories, Processors

  • ChangeDeltaMessage: Message type that contains the model modifications detected by a DeploymentChangeMonitor.
  • M2TOutputMessage: Message type that contains character sequences created by the M2T transformation.
  • ChangeDeltaMessageFactory: Message factory that creates a ChangeDeltaEvent.
  • M2TOutputMessageFactory: Message factory that creates a M2TOutputEvent.
  • ChangeDeltaMessageProcessor: Message processor that adds the ChangeDelta contained by a ChangeDeltaEvent to its parent Transformation Step.
  • M2TOutputMessageProcessor: Message processor that adds the M2T output character sequences contained by a M2TOutputEvent to its parent Transformation Step.


Workflow Structure

The structure of the workflow is specified in the ControllableEventDrivenTransformation.mwe2 file. After the previously defined transformation steps and workflow components have been imported to the module, they can be easily instantiated using the MWE2 description language. Note, that the package containing the .mwe2 file should be imported as well.

module org.eclipse.viatra.emf.mwe2integration.transdemo
 
import org.eclipse.viatra.emf.mwe2integration.*
import org.eclipse.viatra.emf.mwe2integration.mwe2impl.*
import org.eclipse.viatra.examples.cps.integration.*
import org.eclipse.viatra.examples.cps.integration.messages.*
import org.eclipse.viatra.examples.cps.integration.eventdriven.controllable.*
 
 
Workflow {
        //Add initializer workflow component
	component = InitializerComponent{
		modelSize = "4"
		outputProjectLocation = "D:\\MWE_TEST"
		outputProjectName = "VIATRA_ED_Controllable"
	}
	//Use the MWE2TransformationChain class for defining transformation chains
        component = TransformationChain {
		step = ForLoop{
			//set the number of iterations
                        iterations = "2"
			//Add the M2M transformation step
			step = M2MScheduledEventDrivenViatraTransformationStep {}
			//Add he change monitor
                        //it publishes messages to the topic named "M2TTopic" and uses the ChangeDeltaMessageFactory to instantiate them.
			step = ChangeMonitorTransformationStep{
				publishing = PublishTo{topicName = "M2TTopic" factory = ChangeDeltaMessageFactory{}}
			}
			//The M2T transformation step subscribes to to the M2TTopic and sends messages to SerializeTopic
			step = M2TDistributedTransformationStep{
				subscription = SubscribeTo{topicName = "M2TTopic" processor = ChangeDeltaMessageProcessor{}}
				publishing = PublishTo{topicName = "SerializeTopic" factory = M2TOutputMessageFactory{}}
			}
			//the serializer step subscribes to SerializeTopic
			step = SerializerTransformationStep{
				subscription = SubscribeTo{topicName = "SerializeTopic" processor = M2TOutputMessageProcessor{}}
			}
			//modify the model
			step = ModelModifierStep {}
		}
	}
}

To run the specified workflow, simply run the mwe2 file as an 'MWE2 Workflow'.

Executing Workflows

When it comes to executiong the specified workflows there are three main options to choose from:

  • As mentioned before, run the .MWE2 file as an MWE2 workflow
  • Use the MWE2IntegrationInitializer to initialize a plain java environment and run the workflow
public void runSimple() {
        //Define the location of the mwe2 file
        location = "src/org/eclipse/viatra/emf/mwe2integration/examples/simple/SimpleChainExample.mwe2";
        //Use the MWE2IntegrationInitializer to initialize the environment
        MWE2IntegrationInitializer initializer = new MWE2IntegrationInitializer();
        Mwe2Runner  mweRunner = initializer.initializePlainJava();
        //Use the MWE runner to run the workflow
        mweRunner.run(URI.createURI(location), new HashMap<String,String>(), new WorkflowContextImpl());
    }
  • Use the MWE2IntegrationInitializer to initialize a headless Eclipse environment and run the workflow
public void runHeadless() {		
        //Define the location of the mwe2 file
        location = "src/org/eclipse/viatra/emf/mwe2integration/examples/simple/SimpleChainExample.mwe2";
        //Use the MWE2IntegrationInitializer to initialize the headless eclipse environment
        MWE2IntegrationInitializer initializer = new MWE2IntegrationInitializer();
        //Use the current plug-in's class loader so the user defined transformation steps and other elements will be added to the classpath.
        Mwe2Runner  mweRunner = initializeHeadlessEclipse(class.classLoader)
        //Use the MWE runner to run the workflow
        //Note that here you can add elements to the workflow context
        IWorkflowContext context = new WorkflowContextImpl()
	context.put("Example", "Example")	
	mweRunner.run(URI.createURI(location),new HashMap<String, String>(), context)
    }

Project Locations

  • The library itself can be found here: source, JavaDoc
  • Example project location: here