Adding a check-in participant to Rational Team Concert source control


Rational Team Concert 6.0.4 tries to make the check-in operation available to advisors and follow up actions.

Michael Valenta recently published an article on Adding a check-in participant to Rational Team Concert source control. on Jazz.net which provides details about this.

If you are interested in advisors and follow up actions, especially for SCM, this is an important article. Have a look and give him a thumbs up.

Please make sure to check with the links in the next section if you are just starting with the RTC Java API.

Just starting with extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

 

Advertisements

DIY stream naming convention advisors


Organizations might be interested in enforcing naming conventions for streams or make sure that stream names must be unique. This post shows some simple example advisor that check for a naming convention and make sure the stream name is unique.

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

The example in this blog post shows RTC Server and Common API.

Compatibility

This code has been used with RTC 6.0 and is prepared to be used with RTC 6.0.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC. It should run with any version that provides the operation ID.

The code in this post uses common and server libraries/services that are available in the RTC Server SDK.

Download

The code can be downloaded from here. Please note, there might be restrictions to access Dropbox and therefore the code in your company or download location.

Solution

Since some versions of RTC the operation ID com.ibm.team.scm.server.modifyStream has been made available. The operation allows to write RTC advisors (pre-conditions) and participants (follow up actions) that trigger on saving of a stream. The data that is made available in these operations allows to detect a variety of change types including a rename  of a stream.

RTC (version 6.x) ships the following out of the box preconditions for this operation ID:

  1. Ensure that snapshot names are unique for streams in the process area.
  2. Prevent Adding Component to Stream When Component and Stream Owners are Different.
  3. Prevent Adding User Owned Component
  4. Restrict Stream Visibility to be set to Public

The implementing classes are shipped with the SDK

  1. com.ibm.team.scm.service.internal.process.advisors.UniqueBaselineSetNameAdvisor
  2. com.ibm.team.scm.service.internal.process.advisors.StreamAddComponentAdvisor
  3. com.ibm.team.scm.service.internal.process.advisors.StreamAddUserOwnedComponentAdvisor
  4. com.ibm.team.scm.service.internal.process.advisors.StreamVisibilityAdvisor

Looking at the source code can be a great inspiration.

This post shows two advisors

  1. StreamNamingPatternAdvisor
  2. UniqueStreamNameAdvisor

StreamNamingPatternAdvisor checks for a simple naming convention and prevents creation or renaming of streams that violate the naming convention.

UniqueStreamNameAdvisor checks if the name of the stream is already used by another stream and prevents saving the stream if that is the case. Please note, the operation com.ibm.team.scm.server.modifyStream does not trigger if someone creates or saves a user repository workspace. This means it is not possible to provide this capability for  them.

General Approach

The operation ID provides a special interface IModifyStreamOperationData which makes the type of change and the related data available.

imodifystreamoperationdata

The code for the advisors first checks if the operation data provided is of the type IModifyStreamOperationData.If not, the operation terminates. If so, it casts to be able to use the interface to access the change details. If the data indicates anything other than a IModifyStreamOperationData.CHANGES, the advisor terminates.

IModifyStreamOperationData opData = (IModifyStreamOperationData) operationData;
if (!opData.isOperationType(IModifyStreamOperationData.CHANGES)) {
	// Nothing to do
	return;
}

If the operation is for such a change, the operation data can contain several different changes. So the code gets all the change ID’s for the changes in a set. The code then iterates the change ID’s and gets the change usinf the ID as IStreamChange. This interface allows to get more information, for example the identifier for the operation. In our case, if it is a name change IModifyStreamOperationData.NAME, the advisors are responsible to deal with it.

	// Get the set of change IDs and look through the changes
	Set changeIDs = opData.getChangeIds();
	for (String changeID : changeIDs) {
		IStreamChange change = opData.getChange(changeID);
		// Is this a stream name change?
		if (change.getIdentifier().equals(IModifyStreamOperationData.NAME)){
		........

The interface IStreamChange does not provide a whole lot of data to find out what actually happened. But the identifier gives a good idea. The IStreamChange is implemented by several interfaces that provide more information.

istreamchange

  • com.ibm.team.scm.common.process.IStreamComponentChanges is an interface to describe component changes on the stream
  • com.ibm.team.scm.common.process.IStreamFlowChange is an interface to describe flow target related changes on the stream
  • com.ibm.team.scm.common.process.IStreamPropertyChange is an interface to describe property changes such as a name change on the stream

The code can now cast to the specific interface IStreamPropertyChange and access the current and the new value of the property change. These values are the current name and the new name of the stream.

With the new name the code can run a basic validation. If the validation succeeds, the advisor terminates, otherwise it complains and fails the advisor.

The code below shows the whole advisor implementation. In this example the code looks for a very simple prefix ‘TEST_’ on the stream name. If the prefix is available it succeeds, it fails otherwise. This validation can be replaced by a more complex and potentially configurable method.

/*******************************************************************************

 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2017. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.scm.naming.advisor.service;

import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.scm.common.process.IModifyStreamOperationData;
import com.ibm.team.scm.common.process.IStreamChange;
import com.ibm.team.scm.common.process.IStreamPropertyChange;
import com.ibm.team.scm.service.internal.AbstractScmService;

@SuppressWarnings("restriction")
/**
 * This advisor tests the name of a stream against a naming convention pattern.
 * If the stream name does not match the pattern, the stream can not be saved.
 * 
 * Note that this extension point does not get any change events for repository workspaces. It only works for streams.
 * 
 * 
 * There are several examples for extensions that are shipped with the product that can be looked into in the SDK.
 * @see com.ibm.team.scm.service.internal.process.advisors.UniqueBaselineSetNameAdvisor
 * @see com.ibm.team.scm.service.internal.process.advisors.StreamVisibilityAdvisor
 * @see com.ibm.team.scm.service.internal.process.advisors.StreamAddComponentAdvisor
 * @see com.ibm.team.scm.service.internal.process.advisors.StreamAddUserOwnedComponentAdvisor
 *
 */
public class StreamNamingPatternAdvisor extends AbstractScmService implements
		IOperationAdvisor {

	public static final String REQUIRED_PREFIX = "TEST_";
	public static final String STREAM_NAMING_ADVISOR = "com.ibm.js.scm.naming.advisor.service.streamNaming";

	@Override
	public void run(AdvisableOperation operation,
			IProcessConfigurationElement advisorConfiguration,
			IAdvisorInfoCollector collector, IProgressMonitor monitor)
			throws TeamRepositoryException {
		Object operationData = operation.getOperationData();
		if (!(operationData instanceof IModifyStreamOperationData)) {
			// There are some stream modify operations that are not process
			// enabled so they do not provide any data.
			return;
		}
		// com.ibm.team.scm.server.modifyStream is only called for streams.
		// Saving a repository workspace (which is really like a stream) does
		// not trigger the extension point.
		// This especially implies that it is not possible to test for unique
		// names across streams and workspaces. It is possible to
		IModifyStreamOperationData opData = (IModifyStreamOperationData) operationData;
		if (!opData.isOperationType(IModifyStreamOperationData.CHANGES)) {
			// Nothing to do
			return;
		}

		// Get the set of change IDs and look through the changes
		Set changeIDs = opData.getChangeIds();
		for (String changeID : changeIDs) {
			IStreamChange change = opData.getChange(changeID);
			// Is this a stream name change?
			if (change.getIdentifier().equals(IModifyStreamOperationData.NAME)) {
				/***
				 * Get the change details based on the change we identified
				 * 
				 * The supported types of changes for this extension point are
				 * 
				 * @see com.ibm.team.scm.common.process.IStreamPropertyChange
				 * @see com.ibm.team.scm.common.process.IStreamComponentChanges
				 * @see com.ibm.team.scm.common.process.IStreamFlowChange
				 * 
				 *      A name change is stored as IStreamPropertyChange
				 */
				if (change instanceof IStreamPropertyChange) {
					IStreamPropertyChange streamPropertyChange = (IStreamPropertyChange) change;
					String newName = streamPropertyChange.getNewValue()
							.toString();
					if (validateName(newName)) {
						// We are fine
						return;
					}
					String description = "The stream name violates the naming conventions stream name must have prefix '"
							+ REQUIRED_PREFIX + "'!";
					String summary = description;
					IAdvisorInfo info = collector.createProblemInfo(summary,
							description, STREAM_NAMING_ADVISOR);//$NON-NLS-1$
					collector.addInfo(info);
				}
			}
		}
	}

	/**
	 * Validate the name against the convention
	 * 
	 * @param name
	 * @return
	 */
	private boolean validateName(String name) {
		// Implement your own naming convention here
		if (name.startsWith(REQUIRED_PREFIX)) {
			return true;
		}
		return false;
	}
}

The UniqueStreamNameAdvisor only replaces the validation with a more complex one querying the SCM system for streams with a given name.

The code creates workspace search criteria to search for a stream, that happens to have the same name as the new name for this stream will be. If so, it fails. The search is limited to streams, because it is not possible to do this check for repository workspaces and a conflict would be hard to handle.

/**
 * Validate the name against the convention a stream must have a unique name
 * 
 * @param name
 * @return
 * @throws TeamRepositoryException
 */
private boolean validateName(String name) throws TeamRepositoryException {
	// Get the query service and set criteria
	IScmQueryService queryService = getService(IScmQueryService.class);
	final IWorkspaceSearchCriteria criteria = IWorkspaceSearchCriteria.FACTORY
			.newInstance();
	criteria.setExactName(name); // Look for the same name
	// Only for streams, we can not prevent creation of workspaces with the
	// same name either and a user could create a workspace with the same
	// name and prevent us from saving the stream later.
	criteria.setKind(IWorkspaceSearchCriteria.STREAMS);
	// We only have to find one other
	ItemQueryResult result = queryService.findWorkspaces(criteria, 1, null);
	if (!result.getItemHandles().isEmpty()) {
		return false;
	}
	return true;
}

Consolidation

It is relatively obvious and straight forward to consider refactoring the solution shown here. Create an abstract class that contains all the shared code of both classes, with an abstract method validateName(). Create implementations providing the implementation for the method validateName() and provide these implementation classes in the plugin.xml. That way it is simple to provide new versions for various naming conventions.

Code Structure

The code structure follows the general approach that has been used in this blog for quite some time now. The code comes in the following Eclipse projects used for the purpose explained below.

com.ibm.js.scm.naming.advisor.common is the project that defines the jazz component to be used by this extension. It is in a separate project to allow to use it in case of implementing an aspect editor.

com.ibm.js.scm.naming.advisor.launches is a project that contains the launches that can be used to debug the extension with Jetty.

com.ibm.js.scm.naming.advisor.service is the project that contains the server side extension code.

com.ibm.js.scm.naming.advisor.service.feature defines is the feature for the server side deployment of the extension.

com.ibm.js.scm.naming.advisor.service.updatesite contains the update site to build the server side extension.

com.ibm.js.scm.naming.advisor.service.serverdeploy contains the provision profile as well as the folder structure needed for the final deployment. To prepare deployment Build the update site and copy the plugins and feature folder into the  js_scm_naming_advisor folder. To deploy copy the provision_profiles and the sites folder with all their contents into the server/conf/ccm folder of the RTC server.

codestructure

Configuring the precondition

When running the custom advisor in Jetty or finally deployed the advisor can be configured as usual.

configuring

If configured, the advisors prevent saving streams that do not conform to the specific naming condition. The advisor displays the problem like below.

fail_unique_name

Summary

As always I hope this helps someone out there with running RTC. Please keep in mind that this is as usual a very basic example with no or very limited testing and error handling. Please see the links in the getting started section for more examples, especially if you are just getting started.

Setting Access Control Permissions for SCM Versionables


This is the third post in the series around very fine grained access control permissions for work items and SCM versionables. It explains how to set the access control permissions for RTC SCM versionables.

See the problem description in the first post of the series

Related posts

The posts in this series are:

  1. Manage Access Control Permissions for Work Items and Versionables
  2. Setting Access Control Permissions for Work Items
  3. Setting Access Control Permissions for SCM Versionables – this post
  4. Setting Attributes for SCM Versionables

Also see

Controlling access to source control artifacts in Rational Team Concert

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

Compatibility

This code has been used with RTC 5.0.2 and is prepared to be used with RTC 6.0.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC.

The code in this post uses common libraries/services that are available in the Plain Java Client, Eclipse client and Jazz Eclipse server API. If client or server API is used, this is stated.

SCM Versionable Access Control

Lets have a look at access control for RTC SCM versionables such as files and folders. The rules have been explained in this post.

Keep in mind, that with RTC version 5.0.2 it is not possible to use access groups to limit access to versionables. The code is prepared for it, but will fail if such an access context is used. So make sure to only use default (which is controlled by the component), project areas, team areas (called process areas if the distinction is unimportant) or a single user as access control context for versionables prior to RTC 6.0.1. The code below reflects the capabilities for 6.0.1 and higher as well.

This image shows the context menu available for selecting the access control context manually. The menu is available from SCM enabled Eclipse views such as package explorer or repository files for files under Jazz SCM version control.

FileAccessControlContextMenu

The next screen shot shows the selections available in RTC 6.0.1 to specify the access control context.

FileAccessControlChoices

  1. Component: This is the default access context, where the component specifies read access
  2. Contributor: SCM elements can be restricted to be accessed by a single user
  3. It is possible to select a project area or a team area as the read access context, only members of that process area (and sub process areas) will have access to the element
  4. Access Group: Select an access group to limit read access to members of that access group

It is important to note, that the access permission does not work for one version if the versionable, but for all elements.

The next section explains how this can be done using Java API code.

SCM Versionable Access Control Code

How to find the objects we are interested in, is explained in this post. The code to manage access contexts for versionables is different from the code used for work items. It does not use an UUID, but a special interface IPermissionContextProvider .

The interface com.ibm.team.scm.common.dto.IPermissionContextProvider is passed to set the context. The interface provides a factory and methods to create the permission context provider.

Default Access Context

The method

IPermissionContextProvider.FACTORY.createClear()

is available to create a default access context (access permission based on access to the component).

Access Contexts for Other Elements

The method

IPermissionContextProvider.FACTORY.create()

is available for item handles of the types IProcessAreaHandle, IAccessGroupHandle and IContributorHandle to create the access context. This example code shows how to get the permission context provider for various items.

IProcessAreaHandle processAreaHandle = ..... get the handle
IAccessGroupHandle accessGroupHandle = ..... get the handle
IContributorHandle contributorHandle = ..... get the handle

IPermissionContextProvider processAreaContext = IPermissionContext
IPermissionContextProvider accessGroupContext = IPermissionContextProvProvider.FACTORY.create(processAreaHandle);ider.FACTORY.create(accessGroupHandle);
IPermissionContextProvider contributorContext = IPermissionContextProvider.FACTORY.create(contributorHandle);
IPermissionContextProvider defaultContext = IPermissionContextProvider.FACTORY.createClear();

 

Getting the IScmService

To be able to set the permissions for scm versionables, requires the IScmService class com.ibm.team.scm.common.IScmService. It is easy to get this service in the Server API, by basically using

IScmService fScmService = getService(IScmService.class);

In the Client API however, this service is not accessible using the usual call using getClientLibrary() like

IScmService fScmService = (IScmService) teamRepository.getClientLibrary(IScmService.class);

This does not work.

I searched the client API and found about 6 different ways how this interface was requested by the product and test code in the RTC client SDK. For various reasons I picked the following approach and wrapped it into a utility class.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2015. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.access.control.client;

import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.client.internal.TeamRepository;
import com.ibm.team.scm.common.IScmService;

public class ScmServiceClient {
	/**
	 * Get the SCM Service in a client application
	 * 
	 * @param teamRepository
	 * @return
	 */
	public static IScmService getSCMService(ITeamRepository teamRepository) {

		IScmService scmService = (IScmService) ((TeamRepository) teamRepository)
				.getServiceInterface(IScmService.class);
		return scmService;
	}
}

Note that the method uses the internal interface com.ibm.team.repository.client.internal.TeamRepository and not the interface ITeamRepository that is usually used to get a client library. The method getServiceInterface() is not exposed on ITeamRepository.

Set Access Control for SCM Versionables

The code to set access control

/**
 * Sets the Access control context for an array of versionable handles
 * 
 * @param vhandles
 * @param component
 * @param contextProvider
 * @param scmService
 * @throws TeamRepositoryException
 */
public static void setControl(IVersionableHandle[] vhandles,
		IComponentHandle component,
		IPermissionContextProvider contextProvider, IScmService scmService)
		throws TeamRepositoryException {
	scmService.setPermissions(vhandles, component, contextProvider, null,
			null);
}

Having a versionable handle, the IScmService, the handle for the component that contains the versionable and the permission context provider, the code to set the permission context looks as follows:

IVersionableHandle versionableHandle = ..... get the handle
IComponentHandle component = ..... get the handle
IAccessGroupHandle accessGroupHandle = ..... get the handle

IPermissionContextProvider accessGroupContext = IPermissionContextProvider.FACTORY.create(accessGroupHandle);

IScmService scmService = (IScmService) ((TeamRepository) teamRepository)
				.getServiceInterface(IScmService.class);

IVersionableHandle[] vhandles = new IVersionableHandle[] { versionableHandle };

scmService.setPermissions(vhandles, component, contextProvider, null,
				null);

If the available data is a change set, the code would look like this

/**
 * Sets the Access control context for the versionable handles on a change
 * set
 * 
 * @param changeSet
 * @param contextProvider
 * @param scmService
 * @throws TeamRepositoryException
 */
@SuppressWarnings("unchecked")
public static void setControl(IChangeSet changeSet,
		IPermissionContextProvider contextProvider, IScmService scmService)
		throws TeamRepositoryException {
	if (changeSet == null) {
		// no change set or no read access to it
		return;
	}
	for (IChange change : (List) changeSet.changes()) {
		if (change.kind() == IChange.DELETE) {
			return;
		}
		IVersionableHandle versionableHandle = change.afterState();
		if (versionableHandle == null) {
			// change was a delete
			return;
		}

		IVersionableHandle[] vhandles = new IVersionableHandle[] { versionableHandle };
		scmService.setPermissions(vhandles, changeSet.getComponent(), contextProvider, null, null);
	}
}

Get Access Control for SCM Versionables

The code below shows how to read the access control context for the versionables. Please note, it is necessary to have read access to the item, to be able to access it at all.

/**
 * @param versionableHandle
 * @param component
 * @param scmService
 * @param message
 * @throws TeamRepositoryException
 */
public static void printAccessControl(IVersionableHandle versionableHandle,
		IComponentHandle component, IScmService scmService, String message)
		throws TeamRepositoryException {

	IVersionableHandle[] vhandles = new IVersionableHandle[] { versionableHandle };
	IVersionablePermissionsReport[] perms = scmService.getPermissions(
			vhandles, component, null);
	System.out.println(message);
	if (perms.length == 0) {
		// no readContext - item is public
		System.out.println("Item is public.");
	} else {
		// readContext already exists, print it
		for (IVersionablePermissionsReport prep : perms) {
			IPermissionContextProvider provider = prep.getContext();
			IAuditableHandle handle = provider.getReadContext();
			System.out.println("Permission context: " + handle.getItemId().getUuidValue());
		}
	}
}

Summary

This post explains setting read access control for SCM versionables. The next post will talk about setting attributes for SCM objects.

RTC 6.0 – Does the API Change and Break my Code?


I was curious if there will be a lot of rework needed to bring extensions and automation over to RTC 6.0. Here is what I have seen so far.

I found the time to setup my RTC 6.0 environment for Extensions development. This went very smoothly and did not differ to what I describe in Running the RTC Extensions Workshop With RTC 6.0. I then brought all the extensions, API and automation examples from my RTC API Development environment over into the new workspace in the RTC 6.0 environment.

I did not see any errors except in the code related to Contributing Attribute Presentations and the error can likely be fixed by using a different method. So although my API code touches a broad range of the API in RTC, there are only minor changes. The majority of the API seems to be very stable, since this was also the case when I brought over my code into RTC 4.0 some years ago.

There are likely additions to the API, but the API that is there seems to be very stable otherwise. This includes even internal API that I have used on some occasions. Of course, you still want to test that everything work after you brought it over.

I have to confess that I am so far doing the majority of the work in an environment based on RTC 4.0.1. The reasons are basically that RTC 5.x had an annoying issue as explained in Running The RTC 4.x Extensions Workshop With RTC 5.0.x. Since I did not want to put up with it all the time I kept working with 4.0.1 and only tested the result in different versions.

Summary

The RTC client and server API used in extensions and for automation seems to be very stable and there should be not a lot of effort required to bring over your own automation and extensions over to RTC 6.0

As always I hope this saves users out there some time and worries.

Reading and Writing Files Directly from and to an RTC SCM Stream


Can you read files directly from an RTC SCM stream or write them directly into it, avoiding having to use a repository workspace? Kevin, a colleague, was recently facing this challenge writing automation for a customer. We discussed this challenge when we met on a trip.

I was pretty sure it should be possible, but I had no clue how. I have worked with parts of the RTC SCM API and published the results in this blog. However, I have always used a repository workspace and the usual workflow and I had no answer. So how can you do this, provided the API always wants a workspace connection?

Kevin got this puzzle solved with the help of one of our developers and has published the resulting code and reasoning in this blog post. If you are interested, check his code out and give him a thumbs up!

Other posts in this blog about using the SCM API

 

 

Restrict Delivery of Changesets Associated to Wrong Work Item Types Advisor


How can I access work items associated to a change set in an Advisor or precondition on the server? Here is an Advisor example that prevents delivery of change sets if they are associated to the wrong work item type.

I tried to answer some questions in the forum. They were related to how to get the work item from a change set in an Advisor. I thought it would be simple because I had done a lot with the work item link API on client and server already. This is called hubris, I guess, and I found out the hard way, that the API from the Jazz SCM side is very different from what I was used to so far. Since it was so tough, I thought I should publish what I found, so that others can use it to jump start solving their own requirements.

License and how to get started with the RTC API’S

As always, our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

The example in this blog shows RTC Server and Common API.

Download

The code for the example can be found here. The code uses the RTC 4.0 SDK and should work with lter versions of the SDK.

Advisors and Participants

I have already blogged about the RTC Update Parent Duration Estimation and Effort Participant. This blog is about an Advisor, so what is the difference?

A Participant is basically operational behavior that happens after a certain operation. It can fail and roll back the save, but does not have to. Participants are also called follow-up actions. An Advisor or precondition would be used If it is necessary to check certain conditions before an operation is performed. A good example for this is the Descriptive Change Sets Advisor that requires a comment or a work item associated to a change set before it can be delivered.

In general Advisors and Participants are very similar. Even most of the required API code can be used in both extension types. However there are some differences.

Participants

  • Extend the extension point com.ibm.team.process.service.operationParticipants
  • Implement the interface com.ibm.team.process.common.advice.runtime.IOperationParticipant
  • Are allowed to modify the changes they are triggered for

Advisors

They get different information when called but other than that most of the code is similar. It is also important for both implementations to extend com.ibm.team.repository.service.AbstractService in order to be able to get services and other vital information from the API.

Participants as well as Advisors need to provide an operation ID for the operation they want to be triggered and they need to specify a component and required services.

You can find a list of extension points and operation ID’s here in the Jazz Wiki. The Rational Team Concert 4.0 Extensions Workshop shows all the steps required to create a complete participant. The techniques used there have been used to create the Advisor in this post. Please note that all code below is for a Server extension.

How does that API Work?

The implementation started like any other Advisor or Participant. I looked at this question that originally started my effort and tried to figure out how to use the link API.

And I completely failed. I was able to locate similar code in the SDK but I could not figure out how to get the ProviderFactory needed to get the links. Now I did some really serious “search for references” in the SDK. I searched for plugins that extend the same extension point and use the same classes I was using, especially the link API.

As a side note, that is the only technique, besides using a search engine and scanning the SDK documentation for the RTC versions 3.x, 2.x and 1.x in the Jazz Team Wiki looking for examples, that I use to find around in the API.

The Eclipse Plugin Development Environment allows to search for references, declarations of classes, methods and extension points. It allows to use the Java search for classes and methods. It can also restrict to what kind of results one is looking for. This is really powerful and helped solving almost all my questions so far.

Robins blog about using the Plugin Spy to find Client API is also a great resource if looking for API. In most of the cases, the Client PAI works similar in the Server, only the Client Libraries need to be replaced by a Service.

Finally I found two classes that helped me solving the issue. Only, at first, I did not notice it was two classes. The Class com.ibm.team.filesystem.service.internal.ServerRequireWorkItemAdvisor uses the link code. It is the Descriptive Change Sets Advisor that requires a comment or a work item associated to a change set before it can be delivered. However, it gets the required factory in its constructor which does not match the way my implementation gets created by the extension point. I figured after a lot of confusion that this class is actually created by com.ibm.team.filesystem.service.internal.ServerRequireWorkItemAdvisorRunner which is the class that is called in the Advisor implementation. And it is able to get the factory I was looking for.

The Advisor Implementation Explained

So I tried the code and .. it did not compile. Another close look revealed that ServerRequireWorkItemAdvisorRunner extends AbstractScmService and not only AbstractService which allows it to get the data needed to get the factory. This finally led to my code below.

/*******************************************************************************
 * Licensed Materials - Property of IBM (c) Copyright IBM Corporation 2005-20012.
 * All Rights Reserved.
 * 
 * Note to U.S. Government Users Restricted Rights: Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 ******************************************************************************/
package com.ibm.js.team.scm.extension.restrictdelivery.advisor;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.filesystem.common.workitems.ILinkConstants;
import com.ibm.team.links.common.ILink;
import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.service.IRepositoryItemService;
import com.ibm.team.scm.common.IChangeSetHandle;
import com.ibm.team.scm.common.links.ChangeSetLinks;
import com.ibm.team.scm.common.process.DeliverOperationData;
import com.ibm.team.scm.common.providers.ProviderFactory;
import com.ibm.team.scm.service.internal.AbstractScmService;
import com.ibm.team.scm.service.internal.utils.ServerProviderFactory;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.IWorkItemHandle;
import com.ibm.team.workitem.service.IWorkItemServer;

/**
 * Finds the work items a changeset is related to and checks if they are od a
 * specific type. Prevents from delivery if the type iswrong.
 * 
 */
@SuppressWarnings("restriction")
public class RestrictDeliveryToWorkitemType extends AbstractScmService
		implements IOperationAdvisor {

	public static final String COM_IBM_JS_TEAM_SCM_EXTENSION_RESTRICTDELIVERY_ADVISOR_RESTRICTWORKITEMTYPE = "com.ibm.js.team.scm.extension.restrictdelivery.advisor.restrictworkitemtype";
	// Services we need
	private IWorkItemServer workItemServer;
	IRepositoryItemService itemService;

	@Override
	public void run(AdvisableOperation operation,
			IProcessConfigurationElement advisorConfiguration,
			IAdvisorInfoCollector collector, IProgressMonitor monitor)
			throws TeamRepositoryException {
		Object operationData = operation.getOperationData();
		if (!(operationData instanceof DeliverOperationData)) {
			return;
		}
		DeliverOperationData data = (DeliverOperationData) operationData;
		List changeSetHandles = data.getChangeSetHandles();

		// Get the required service interfaces
		workItemServer = getService(IWorkItemServer.class);
		// @see com.ibm.team.filesystem.service.internal.ServerRequireWorkItemAdvisorRunner
		// @see com.ibm.team.filesystem.service.internal.ServerRequireWorkItemAdvisor
		itemService = getService(IRepositoryItemService.class);

		// Get the ProviderFactory to be able to find the links
		ProviderFactory providerFactory = new ServerProviderFactory(this, itemService);

		// Check all relationships
		for (IChangeSetHandle iChangeSetHandle : changeSetHandles) {
			List links = ChangeSetLinks.findLinks(providerFactory,iChangeSetHandle,
					new String[] { ILinkConstants.CHANGESET_WORKITEM_LINKTYPE_ID },
					monitor);
			for (ILink link : links) {
				Object resolved = link.getTargetRef().resolve();
				if (resolved instanceof IWorkItemHandle) {
					checkWorkITemType(resolved, collector, monitor);
				}
			}
		}
	}
}

This part of the code basically gets the data, gets the Services and the ProviderFactory. Then it uses the ProviderFactory and iterates all links to find if it is associated to a work item. If it is it checks for a valid work item type.

To allow better display in this blog, I pulled checking the work item type into a method.

public void checkWorkITemType(Object resolved,
		IAdvisorInfoCollector collector, IProgressMonitor monitor)
		throws TeamRepositoryException {
	// We found a work item.
	IWorkItemHandle wiHandle = (IWorkItemHandle) resolved;
	IWorkItem workItem = (IWorkItem) workItemServer
		.getAuditableCommon().resolveAuditable(wiHandle,
		IWorkItem.FULL_PROFILE, monitor);
	String workitemType = workItem.getWorkItemType();

	if (!(workitemType.equalsIgnoreCase("defect") || workitemType.equalsIgnoreCase("task"))) {
		IAdvisorInfo info = collector.createProblemInfo(
		"Change Set associated to wrong work item type. Delivery denied ",
		"The change set is assotiated to work item " + workItem.getId()
		+ " of type " + workitemType
		+ ". Changesets are allowed only for work items of type Defect or Task. Please remove this assotiation",
		COM_IBM_JS_TEAM_SCM_EXTENSION_RESTRICTDELIVERY_ADVISOR_RESTRICTWORKITEMTYPE);
		collector.addInfo(info);
	}
}

If the code detects a wrong work item type, it creates a problem info and that provides the failure information.

If you are looking for the Client API, here are some code hints in this forum question. More information about the client SCM API can be found in this and  the related posts.

As usual the exception handling is very basic and you might want to improve that if using this code. In addition you probably want to add a schema to make the code configurable in the process configuration. However, I hope this is helpful and saves some cycles searching through the RTC SDK.