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

VIATRA2/API Example

VIATRA2 Java API example

The VIATRA2 model transformation framework provides an extensive API for performing the most frequently used tasks using Java method invocations instead of through the user interface. The basic steps are opening a model space, importing model instances, parsing and executing transformations and saving the results.

We've compiled an introductory example where all these steps are covered, the example can be downloaded from the VIATRA extras SVN repository here.

1. Set up the example

Check-out the api_example plugin from the SVN repository, along with the bpmn importer plugin (and of course the rest of the VIATRA framework either from SVN or the update site).

Apart from selecting the required plugins for the runtime Eclipse application configuration, you should add the "-console" argument to the arguments (in order to be able to start the execution).

Finally, the example uses files from the runtime workspace instead of the plugin bundle, so import the project into the runtime workspace as well (it should have the same name and contain at least the vpml, vtcl and input folders).

2. Execute example

After starting the Eclipse application, type "ss" into the osgi console (in the original Eclipse), find the "id" of the plugin (org.eclipse.viatra2.api_example) and type "start ID" (replace ID with the actual number, ie. 545). As a result, the example should run, putting some VIATRA2 specific output on the console, with a final line "This is a test output" generated by the transformation execution.

3. API example detailed

The whole API example can be found in the "Viatra2ApiExample.java" in the default package of the plugin, specifically in the "executeExample" method.

Open model space

In our example, we first create a new framework using the singleton framework manager. Then we use the mergeFile method of the IFramework to open a given model space stored in a file. Note that you can use the alternative createFramework(String fileName) method to perform the two operetions in one step.

// create framework
framework = FrameworkManager.getInstance().createFramework();
// parse vpml
framework.mergeFile(root + "/vpml/example.vpml");

Import model

Model importers correspond to file extensions but they also have a unique identifier as well. In the first case the extension based solution is shown, where importers for the "bpmn" extension are queried from the framework and the first of those is used for importing. In the second case, the unique identifier (given in it's extension point properties) of the importer is used. Further details on creating importers can be found on the VIATRA2/UseCases/ModelExportImport page.

// import model, root is the project location
framework.nativeImport(root + "/input/example.bpmn", framework.getNativeImportersForExtension("bpmn").iterator().next());
// alternative solution
framework.nativeImport(root + "/input/example.bpmn","org.eclipse.viatra2.imports.bpmn2");			

Load machine

The simple way of loading a machine from a VTCL file is using the loadMachine function, where loaders are handled similarly to importers. The return value of the function is the Machine itself, although a class cast is required. Instead of casting, the machine can be queried from the framework using it's fully qualified name.

// parse vtcl
Machine machine = (Machine) framework.loadMachine(root + "/vtcl/example.vtcl", framework.getLoadersForExtension("vtcl").iterator().next());
//Machine machine = (Machine) framework.getMachineByFQN("example");

However, the simple version can not be used when we would like to get information about the parsing, for examples errors existing in the transformation code, or missing metamodels from the modelspace. In order to access such information, the VTCLParserManager of the framework is used. You can create a ParseController using a VTCL file path string and then use the parseAndBuild method to parse it. After that, the existence of errors can be checked and the error information accessed. The reference to the parsed machine itself is also accessible from the ParseController.

VTCLParserManager pm  = (VTCLParserManager)framework.getVTCLParserManager();
String vtclFileName = root + "/vtcl/example.vtcl";
VTCLParseController pc = pm.lookupAndCreateParseController(vtclFileName);
pc.parseAndBuild();

if(pc.hasErrors()){
  List<ErrorInformation> errors = pc.getErrors();
  for (ErrorInformation errorInformation : errors) {
    System.err.println(errorInformation.getMessageWithLocation());
  }
}
else{
  machine = pc.getMachine();
}	

Running transformation and transaction management

Running a transformation (or a given rule in it) is done by first creating a Map for parameters, where the keys are the name of the parameter and the value is the input. The runEntryPoint method of the framework will execute the given machine using the parameters and indicate the progress of the execution on customizable progress monitor.

//parameters
Map<String, Object> params = new HashMap<String, Object>();
params.put("TestParam", "example.model.testModel");
// run transformation
framework.runEntrypoint(machine, params, new IProgressReport() {
  @Override
  public void progress(int indicator) {}
});

Transaction manager

Transformation executions can be groped into transactions, which are abortable and undoable. Before running the transformation, the transaction manager's beginTransaction method is used to create a checkpoint with a given ID. Aborting the current transaction will cause the framework to try a compensation to return to the checkpoint, while commiting is used to close a transaction after a successful execution. However, if a rollback is required later on, undoing changes is possible by using the ID of the checkpoint to where the rollback should happen.

// start transaction
String transID = framework.getTopmodel().getTransactionManager().beginTransaction();
// abort transaction (during execution, not recommended)
// closes transaction and tries compensation
framework.getTopmodel().getTransactionManager().abortTransaction();
// commit transaction if OK
framework.getTopmodel().getTransactionManager().commitTransaction();
// undo otherwise (used later)
// reverts all the modifications _after_ the beginning of the transaction
framework.getTopmodel().getTransactionManager().undoTransaction(transID);				

Accessing the transformation output

The output of the transformation (print and println invocations) is accessible either after the transformation if buffers are used (see VIATRA2/UseCases/CodeGeneration) or during runtime for the non-buffered output.

When buffers are used, a given buffer is queried from the singleton BufferStore class, and the contents are accessed as a simple String.

// buffer access after transformation execution
// use core:// mainly
StringWriter bufsw = (StringWriter) BufferStore.getBuffer(framework.getTopmodel(), "core://example");
// access actual content of the buffer
System.out.println(bufsw.getBuffer().toString());

If the output of the transformation is required during the execution, a codeoutput plugin class can be defined to deal with certain events. When the codeoutput is initialized, it receives a reference to the framework itself which can be stored in order to access the model space for example. The beginWork method is called by the framework when the transformation execution is started, the codeOut method is called every time a print or println rule is invoked from the transformation (without buffers). Finally, the endWork method is called when the transformation finishes. You can add this codeoutput to the framework before executing the transformation.

// event-driven approach (even during transformation execution)
// prepare output reading
CodeOutputPlugin cop = new CodeOutputPlugin() {
  @Override
  public void init(IFramework fw) {
    // recives FW reference for optional usage
  }
  @Override
  public void beginWork() {
    // call-back called at the beginning of the transformation execution
  }
  @Override
    public void codeOut(String s) throws VPMRuntimeException {
      // called for every print() and println() rule invocation from VTCL
      // does not support buffers
  }
  @Override
  public void endWork() {
    // call-back called at the end of the transformation execution
  }
};
framework.addCodeOutListener(cop);

Saving the modelspace and cleanup

Finally, if you would like to keep the model space after the transformation execution(s), the saveFile method is used for persisting the state of the model space into an arbitrary vpml file. In order to keep things nice and tidy after you finish your work with the framework, use the disposeFramework method of the manager to signal that it is no longer in use. If you had a codeoutput attached to the framework, don't forget to remove that as well.

// save workspace
framework.saveFile(root + "/vpml/example_save.vpml");
// cleanup
framework.removeCodeOutListener(cop);
FrameworkManager.getInstance().disposeFramework(framework.getId());

Back to the top