Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

MWE The Big Picture

Revision as of 06:12, 19 June 2008 by Sven.efftinge.typefox.io (Talk | contribs) (Configuration language (codename DI-Language -> DI=Dependency Injection))

The purpose of MWE is to help orchestrate and configure code generation 'workflows'. Just like with compilers a code generator can and should be splitted into several single parts, each part doing a specific job. Such parts could be:

  • parsing / loading model
  • validating model
  • linking model
  • transforming model
  • modifying model
  • generating code out of a model

Typically there are a couple of component fulfilling such jobs included in a generator (compiler-chain), where each needs additional component-specific configuration. Also there are different solutions for each job. For instance modeltransformation can be done using QVT, ATL or Xtend, each need different configuration.

Configuration language (codename DI-Language -> DI=Dependency Injection)

The language described here is already implemented and checked into EMF/MWE CVS. Note that it is based on [Xtext] 2, which is currently (July 08) under development.


The description of a generator workflow is made of a sequence of declared concrete components (e.g. EMFResourceLoaderComponent, EValidationComponent, JetComponent, XpandComponent, etc.) where each is configured.

For instance an EMFResourceLoaderComponent would need the URIs to the resources to be loaded and optionally respective ResourceFactory configuration, because workflows are run in standalone mode most of the time. XpandComponent needs to know what kind of TypeAdapter to use, where to generate the different files, how to postprocess them, etc.

Essentially such a description is sequence of components where each component, can be configured with arbitrary complex components itself. Example:

EmfResourceLoaderComponent {
  platformRoot = ".."
  uri = "platform:/resource/foo/bar/model.dsl"
  extensionToFactoryMap += { 
     ext="dsl" 
     factory= MySpecialDSLResourceFactory {
        someAdditionalConfig = "foobar"
     }
  } 
}

Previously this has been configured via XML :

<component class="EmfResourceLoaderComponent"
   platformRoot = ".."
   uri = "platform:/resource/foo/bar/model.dsl">
  <extensionToFactoryMap ext="dsl">
      <factory class="MySpecialDSLResourceFactory" 
               someAdditionalConfig="foo"/>
  </extensionToFactoryMap>
</component>

No matter what syntax being used, the core concepts remain. This part (the language) don't need to know anything about what to instantiate. The only interface to the configuration is Java reflection.

Features of the DI language

Type adapter

It is not only possible to instantiate Java classes via default constructor, but provide arbritrary implementations of TypeSystem. The di-language is shipped with implemenations for EMF (using EMF's refection layer) and for Java (using default constructor and adder and setter methods).

This hook will also be used to improve convenience for repeated tasks. For instance, it is possible to use it like a factory and have preconfigured complex object trees being injected. Another useful case is to provide a built-in TypeSystem for a specific runtime model (see next section) in order to let users write more intentional looking configurations like e.g.:

workflow {
   parallel {
       generator {
          invoke = "foo::bar()"
       }
       
       generator {
          invoke = "foo::bar2()"
       }
   }
} 

Where workflow, parallel and generator are built-in names known by a configured type adapter.

Local Variables

Local Variable can be accessed in two ways

  • via name on the right hand side of an assignment
 foo = localVarName
  • via &{name} within a string literal.
 foo = "foo${bar}"

Another example :

 my.Object {
    name= "${theName}"
    otherProp = aLocalVar
 }
Variables can be passed in as parameters

When instantiating a workflow description, a map of parameters can be passed in. The values can be any kind of Java object.

Variables can be declared

Variable declarations (and usage) look like:

 var x = "foo"
 var y = my.Object { name = "${x}bar" }
Complex Values can be stored as variable

When declaring a complex value one can assign it to a localVar by adding :varname in front of the opening curly bracket.

The following code would inject a reference to the created object in it's property 'self'.

 my.Object :myVar {
   self = myVar
 }

Invocation of other workflows

The root element of another workflow can be injected via:

 my.Object {
    coolStuff = file "scheme:/foo/workflow.mwe" {}
 }

Parameters can be passed like this:

 my.Object {
    coolStuff = file "scheme:/foo/workflow.mwe" {
        generateTo = "${project}/src-gen"
        packageRegistry = myPackageRegistry
    }
 }


Type inference

If the dependency's type is a concrete type and you want to inject an instance of that type, you don't have to eplicitely state the type's name, because it can be inferred from the dependency. Example:

If we have the following two classes

class Foo {
 setBar(Bar) ...
}
class Bar {
 setName(String) ...
}

The configuration

Foo {
 bar = Bar {name = "foo"}
}

can be abbreviated like so:

Foo {
 bar = {name = "foo"}
}

Runtime Workflow Model

As you can see one can instantiate any kind of Object graph using the di-langauge. For a wokflow in order to being executable, we need some kind of runtime model invoking the different components and manging the overall execution. Therefore each component needs to conform to an interface so the runtime workflow model can work on. Such an interface usually defines some kind of 'execute' method and optionally additional life cycle callback methods (like preconfigure, postconfigure, preExecute,etc.) In the past we used an interface called WorkflowComponent, having two methods : checkConfiguration(), invoke(). Also there is a workflowcontext (essentially a hashmap) being passed into the invoke method, so the different components can communicate some how. One component puts the model into a slot another reads it from there, transforms it and writes the outcome to another slot. Pretty simple but worked ... We also introduced a so called CompositeComponent, which is just a composite node being able to hold a sequence of WorkflowComponent. In addition there is a basic If-then-else construct, used to conditionally execute components.

In Bug 225892 Brian Hunt posted an EMF based runtime workflow model. He also listed some interesting requirements:

  • Lightweight
  • Easily persisted
  • Transportable to a distributed computing environment for execution
  • Capable of execution of where it left off in the event of a component failure
    • Successful components must not be re-executed
  • Capable of extending the functionality of parameters
    • Simple key = value pairs are not sufficient
  • Capable of executing components in parallel
  • Capable of executing components conditionally
  • Capable of extending the possible execution result states
  • Capable of providing a rich editor for both the workflow model and parameter

values

    • I think GMF might fit nicely for a workflow editor
  • Capable of specifying parameter values separate from the workflow model
  • Capable of providing different algorithms for determining the final execution

state of a composite

Copyright © Eclipse Foundation, Inc. All Rights Reserved.