Wednesday, November 16, 2011

Model to model mapping transformations with RSA


During the last year, I've been working on a project developing a transformation between UML models and custom XML. The platform of choice was IBM Rational Software Architect (RSA) which provides very good support for creating model to model transformations. Although there is a pretty a good tutorial in the RSA Help, there are quite a few things that are tricky. Thus I thought that sharing some of the experience I made might help others facing similar challenges. So let's get down to business, here's what I'll cover in the blog:
  1. Getting setup and getting help
  2. Tips for structuring model-to-model mapping projects
  3. How to write custom mapping rules
  4. Pattern for managing the transformation execution lifecycle
  5. Aborting an executing transformation
  6. Pattern for reporting transformation problems
  7. Model markers and decorators
1. Getting setup and getting help

Before you get started on extending RSA, you should make sure to install the extensibility components. Tricky enough, this does not get installed by default, so fire up IBM Installation Manager, hit Modify and make sure to check the "Extensibility" feature. When this is done, you'll have access to "everything" you need to extend this already incredibly capable tool. One funny(?) quirk is that most of the RSA help is available online from IBM's InfoCenters for the various flavors of RSA. However, the help for the extensibility - including APIs, programmers reference etc is only available from the local installed help. I would suspect that this is due to the on-line help being generated from the features installed by default...

No matter what, it is convenient to use the on-line help for everything else. So here's a couple of links:

Rational Software Architect v8 on-line help
Rational Software Architect RealTime Edition v8 on-line help

Now that you have the help figured out, start out by working through the tutorial on model to model mapping transformations. This will save you a lot of time. Also, when working with the mapping model editor, make sure that you have the Outline view open - otherwise you'll have a tough time navigating through your mapping model...

2. Tips for structuring model-to-model mapping projects


The source the for the model to model mapping project is the mapping model. Typically, this is kept in a "model" folder in your model-to-model mapping project. From it, you then generate the transformation source code. In addition to this, you will typically need to write custom code. Here's where the structuring comes in handy - using a simple but clever Java package scheme, you can easily separate custom (handwritten) code from the generated code. This will allow you to simply delete all generated code to ensure that it is completely regenerated. Typical cases when this is useful, is when you remove map levels in the mapping model. Here's an example of a package structure:


mymappingdemo            - Core classes for the transformation plug-in 
mymappingdemo.custom     - Here's your handwritten custom code
mymappingdemo.conditions - You may want to keep your conditions separate
mymappingdemo.l10n       - Generated message strings
mymappingdemo.transforms - Generated transformation source code


In this example, you can safely delete all classes in the mymappingdemo.transforms package, except... Yes there is an exception. If you have modified the generated code, you will need to be careful and make sure to preserve any modified classes. In this case, you must avoid deleting any classes containing "@generated NOT" tags.


3. How to write custom mapping rules
There's two ways of writing custom rules for mapping a model element from the input model to the output model:
  1. Using the inline editor
  2. Creating a custom rule in an external Java class
Use the second alternative. The inline editor is just no good. It is really worse than a regular text editor. Instead create an external Java class and you'll have access to everything you expect when coding and debugging Java with Eclipse.

4. Pattern for managing the transformation execution lifecycle
In many cases it is useful to be able to perform operations before and after the actual transformation. In order to do this I came up with the following approach:

In the generated transformation code, there's a class MainTransform in which you can modify the constructor:


public MainTransform() { 
    super(TRANSFORM, Umlrt2ifmMessages.mainTransform);
    LifecycleManager.register();
    ...
}

Here I've added a call to register my "LifeCycleManager", a class which allows operations for controlling the lifecycle of "things". "Things" can be various caches etc which needs to be cleared before invoking the transformation.

The key reason for introducing this type of "life cycle management" was when I realized that the transformation is only instantiated once - it is a plugin! This if you depend on things being performed at invokation or at the end of a transformation, you need a way to trigger them.

Here's how my LifeCycleManager looks like:

package mymappingdemo.custom;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;

import com.ibm.xtools.transform.core.IRunTransformationListener;
import com.ibm.xtools.transform.core.ITransformContext;
import com.ibm.xtools.transform.core.RunTransformationEventManager;
import com.ibm.xtools.transform.core.extension.AbstractTransformExtensionHelper;
import com.ibm.xtools.transform.core.internal.engine.TransformRunHelper; 
/**
 * Implements lifecycle management for the transformation<br>
 * The transformation is instantiated once per RSA session, thus classes that 
 * persist objects which should not be cached/reused between invocations of 
 * the transformation must be reset (cleared). This can be done by invoking 
 * reset code in the {@link #onTransformationStart(ITransformContext)} method.
 * 
 */
public class LifecycleManager extends AbstractTransformExtensionHelper implements IRunTransformationListener {

     // singleton
     private static LifecycleManager instance;
 
     private ITransformContext transformContext;

    private LifecycleManager() {
    }

     public static LifecycleManager getDefault() {
           if (instance == null) {
                instance = new LifecycleManager();
                RunTransformationEventManager.getInstance().addListener(instance); 
           }
           return instance;
     }


     @Override
     public void onTransformationStart(ITransformContext itransformcontext) {
         this.transformContext = itransformcontext;
         //TODO: add things to perform at the start of a transformation here:
     } 

     @Override
     public void onTransformationEnd(ITransformContext itransformcontext, IStatus istatus) {
            this.transformContext = null; // nothing to cancel, transf. done
            //TODO: add things to perform at the end of a transformation here:
     }


     /**
      * Invoke this method to register the manager as a listener 
      * with the transformation framework
      * 
      */
     public static void register() {
         getDefault();
     }

}



5. Aborting an executing transformation
If we for some reason wants to abort the execution of a transformation, we can add a method  cancelTransform() to the class LifecycleManager, which has access to the transformation context and can cancel it. This can be very useful if you detect problems during the execution of a transformation. To implement this we need to modify the class LifecycleManager. First we need to add a method to support cancellation:

      /**
        * Cancels the transformation execution
        * 
        */
      public void cancelTransform() {
           if (this.transformContext == null) {
                return; // nothing to cancel
           }
           IProgressMonitor monitor = 
              TransformRunHelper.getProgressMonitor(this.transformContext);
           monitor.setCanceled(true);
      }

Unfortunately this will warn you about access restrictions, so you may want to suppress them:


@SuppressWarnings(&quot;restriction&quot;) // Necessary because of: {@link LifecycleManager#cancelTransform()}
public class LifecycleManager extends AbstractTransformExtensionHelper implements IRunTransformationListener {
    ...


Now you are ready to cancel the execution when you've detected a problem with:

LifecycleManager.getDefault().cancelTransform();

6. Pattern for reporting transformation problems


Typically, you're transformation will detect various problems when processing the input and generating the output. This can be of the typical severities info, warning and error and thus it fits right into creating problem markers. More on that in the next sections.

However, we need to deal with the fact that transformation may often be executed in headless mode and thus we do not have access to the marker framework. For this, we can use a trick described by Chris Aniszczyk (ref #1):


 
 /**
  * Detects whether running headless or not
  * @return
  */
 private static boolean isHeadless() {
  // source: http://aniszczyk.org/2007/07/24/am-i-headless/
  boolean headless = false;
  Bundle b = Platform.getBundle("org.eclipse.ui");
  if (b==null || b.getState() != Bundle.ACTIVE) {
     headless = true;
  } 
  return headless;
 }



Next we wrap this up so that we can report problems without having to think about whether we're headless or not ;-)
Here's a sniplet of code that just uses regular "syserr" to print out a message in headless mode, while it calls the problem marker utility when the UI is available:



public class ProblemReporter {
 private static boolean isHeadless() {} 
    ...
    return headless;
 }

 /**
  * Reports an error message for the given model element<br>
  * Prints to stderr in headless mode, else logs to the platform log
  * 
  * @param modelElement
  * @param message
  */
 public static void reportError(NamedElement modelElement, String message) {
   String location = modelElement.getQualifiedName();
   if (isHeadless()) {
       System.err.println("ERROR: location=" + location + ", message=" + message);
   } else {
       TransformationMarkerHelper.getDefault()
                .createErrorMarker(modelElement, message);
   }
 }




7. Model markers and decorators
In order to give the user feedback on problems detected during the transformation, we can use the IMarker framework. There's a good starting point in the article on "Mark My Words" (ref #2). However, a short entry here will not give this justice, so I'll followup with a separate post on the topic.

Happy transforming!




References:

Thursday, April 14, 2011

Hidden features in IBM Rational Software Architect (RSA)


Being a very large tool, there's also an enormous amount of "hidden features" in RSA. Here I'm going to list a few of my favorites. Starting out with:


Controlling the order of model elements in Project Explorer (PE)

By default, model elements are listed alphabetically in PE. Often good, but sometimes you just want to be able to control the order they are listed in - but how?

Well, as with some many other things, bring up the Preferences dialog (Window > Preferences) and browse to Modeling > Views > Project Explorer.
There you'll find a section on "Project Explorer settings" in which you can modify the "Sort by" settings. Setting it to "Storage Order" gives you full control over the order of elements in PE.

Next, you very likely will want to to reorder the elements. With "Storage Order" you'll notice that they appear in the order that they were added to the model.

Reording is done by one of the more hidden features. Here's some examples:

Reordering contents of a UML Package:
  1. Right click on the package in PE and select "Properties"
  2. Switch from "General" to "Advanced"
  3. Click in the value part of the entry for "PackagedElement" and a button marked "..." appears
  4. Click this button
  5. A new Property dialog is opened - use this to move the elements up/down to suit your needs
  6. Close all dialogs and enjoy
If you need to rearrange attributes in a class, perform the same steps, but instead of locating "PackagedElement" locate "OwnedAttribute" and perform your reordining there.

As this is dependent on having the "Sort by" preference set, other users will need to set the same "Sort by" as you to enjoy your model order. You may want to consider sharing of preferences to address this issue.

Monday, March 28, 2011

Controlling Eclipse Preferences


Ever struggled with preference settings in Eclipse?
A typical case is where you have to over and over again reapply the same preferences in to new workspaces and projects. Quite tiresome.

This problem was addressed during this years EclipseCon in the talk Getting Eclipse Preferences Under Control in Teams. Here we learned of three parallel projects addressing in different ways the same issue:

Full presentation is available here.

Shortly after the session, I also stumbled across a post on the e4-dev list announcing yet another initiative e4preferences.

Given that there is a fair number of attempts to address various aspects of the "preference problem", I now hope that we can work together to, if not once and for all solve, but at least improve the preference sharing support for end users.

Except for Bug 334016, there's wikis describing what they do, so I'll have a go at explaining it.

The name of this feature is "Common Preferences" and was developed for Ericsson in order to address the problem of sharing "common preferences" between users and teams in the larger enterprise.

It has been deployed and successfully used for about 4 years now.
For the end user, the typical case could be that he/she never notices that Common Preferences is there to help out. This is accomplished by installing the feature along with everything else in the Eclipse based product and having it preconfigured to set key preferences (such as network proxies etc) upon creating a workspace.

In addition to this, the users can then add their own preferences to the list of what is to be managed. There's a neat export wizard allowing the user to identify what preferences are modified (i.e. changed from their default values) and thus helping the user to compile a Eclipse preference file which later can be imported by others in the team (including the user him/herself). It also allows the comparison of applied preferences with those defined by the Common Preferences.

As of writing, I'm waiting for this bug to progress further through the submission process. Ideally, it could result in an improved preference sharing support in the platform, possibly by merging the key features from the various initiatives addressing this gaping hole.

Why don't you go ahead and vote on bug 334016 ...