Friday, April 16, 2010

Equinox Aspects to the Rescue - Patching non-intrusive

For my current project I implemented synchronization between Rational Team Concert (RTC) and Rational Clear Quest (CQ) using the Clear Quest Connector.

The development team uses RTC as its primary tool to work with defects and work item whereas the test team and the customer is using the CQ web client.

Due to a limitation in the CQ Connector all comments in a defect made in RTC are synched to CQ with the user ID of the CQ Connector. As a consequence, the users of the CQ web client are unable to determine who has made a comment.

I created an enhancement request which unfortunately won't be implemented.

So I thought I should roll my own solution to this problem.

Being a fan of AOP and AspectJ, I thought I could use Equinox Aspects to intercept the call where the comment is saved and simply prepend the username. In fact it was just as easy. I only needed to figure out how to use AspectJ with Equinox Aspects in my eclipse installation. The following two guides helped me:

The first thing to do was find a place in the code where I could easily apply my change. I downloaded the RTC SDK and loaded the bundles into a fresh workspace using the target definition supplied in the SDK so that I could browse and search the codebase using eclipse's features like determining call hierarchies etc.

I was looking for a (central) place that would be sufficient to intercept for all comments added. I quickly found such a place in the code in the model class com.ibm.team.workitem.common.internal.model.Comments.

The method I picked looks like this:

public IComment createComment(IContributorHandle creator, XMLString content) {
  Comment comment= ModelFactory.eINSTANCE.createComment();
  Utils.initNew(comment);
  comment.setCreator(creator);
  comment.setContent(content.getXMLText());
  return comment;
}

Looking at the call hierarchy for this method reveals that it is called from various places in the Eclipse UI and also on the server side (the REST services).



Everything I need for the modification is right there - the content of the comment and the creator. I created the following pointcut to match the code:

public pointcut createCommentMethodToIntercept(IContributorHandle creator, XMLString content, IComments comments) :
 
    execution(public IComment Comments.createComment(IContributorHandle, XMLString))
    && args(creator, content)
    && target(comments);

I am extracting the creator, the content and the comments object here.

The actual modification is done using an around advice:

@SuppressWarnings("restriction")
IComment around(IContributorHandle creator, XMLString content, IComments comments) : createCommentMethodToIntercept(creator, content, comments) {
  try {
    Comments concreteComments = (Comments) comments;
    if(shouldApply(getWorkItemType(concreteComments))) {
      ContributorImpl contributorImpl = (ContributorImpl) creator.getFullState();
      String userId = contributorImpl.getName();
      String xmlText = content.getXMLText();
      System.out.println("Original content: " + xmlText);
      content = XMLString.createFromXMLText(userId + ": " + xmlText);
      System.out.println("Replaced content: " + content.getXMLText());
    }
  } catch(ClassCastException e) {
    e.printStackTrace();
  }
  return proceed(creator, content, comments);
}

Since the modification should only affect defects that are synched to ClearQuest I am calling the getWorkItemType method which makes use of the comments object:

private String getWorkItemType(Comments comments) {
  WorkItem workItem = comments.fWorkItem;
  String workItemType = workItem.getWorkItemType();
  System.out.println("WorkItem type: " + workItemType);
  return workItemType;
}

The shoulApply method just checks a system property for a list of work item types that should be affected.

The aspect is then applied to the bundle containing the Comments class which is com.ibm.team.workitem.common by specifying the following line in the manifest file of the plug-in containing the aspect:

Eclipse-SupplementBundle: com.ibm.team.workitem.common

Basically, this is all that needs to be done.

Now, when I add a comment to a defect that gets synched to ClearQuest my name is prepended to the comment:




Of course, Equinox Aspects must be installed correctly and running the weaving hook for the aspect to be applied at runtime.

On the server side RTC also employs OSGI and bundles through a servlet bridge. Unfortunately, I wasn't able to get the aspect applied there.