Managing Contributor Licenses using the Java API


The question how to manage licenses using the plain java client libraries came up recently. There used to be a blog post in the internet that explained it, but that has been taken down. Questions on Jazz.net are not very clear for this, so I dug into the API myself. Here is the result.

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.

Solution

The service to access and assign licenses is the class com.ibm.team.repository.common.ILicenseAdminService.

Note that this class is not available as a client library such as other common and client services. The way to get this class in a client extension or a plain java API base application looks as follows:

ILicenseAdminService licenseAdminService = (ILicenseAdminService) ((IClientLibraryContext) teamRepository).getServiceInterface(ILicenseAdminService.class);

To access the class in a server extension like a pre-condition/advisor or a follow up action/participant is however using the typical com.ibm.team.repository.service.AbstractService.getService(Class) that is usually used e.g.:

ILicenseAdminService licenseService = getService(ILicenseAdminService.class);

The licensing code should work against each of the CLM applications JTS, CCM, QM, RM and should work for any available license.

This code can be used to print the license information for the available license types:

	IContributorLicenseType[] Licensetypes = licenseAdminService.getLicenseTypes();

	for (IContributorLicenseType iContributorLicenseType : Licensetypes) {
		System.out.println("License Type: ");
		System.out.println("License Type ID: " + iContributorLicenseType.getId());
		System.out.println("License Type Name: " + iContributorLicenseType.getName());
		System.out.println("License Type Description: " + iContributorLicenseType.getDescription());
		System.out.println("License Type ProductName: "	+ iContributorLicenseType.getProductName());
	}

The resulting list of licenses would look like below.

License Type: 
License Type ID: com.ibm.team.clm.stakeholder
License Type Name: Stakeholder
License Type Description: Diese Stakeholder-Lizenz ist zur Unterstützung von peripheren Benutzern gedacht, z. B. von externen Kunden, von Mitarbeitern der Benutzerunterstützung oder von Benutzern, die Arbeitselemente modifizieren und den Projektfortschritt beobachten müssen. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Stakeholder hat Lese- und Schreibzugriff auf Change Management sowie Lesezugriff auf Berichte und Planungsfunktionen, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten. Floating-Lizenzen werden dynamisch von einem Lizenzserver zugewiesen. Ein Benutzer mit zugewiesener Floating-Lizenz kann in den Pool der Benutzer aufgenommen werden, die die verfügbaren, auf dem Lizenzserver installierten Floating-Lizenzen gemeinsam nutzen. Führt ein solcher Benutzer eine Operation aus, für die diese Lizenz erforderlich ist, wird ihm vom Lizenzserver dynamisch eine Floating-Clientzugriffslizenz zugewiesen, wenn eine solche verfügbar ist. 
License Type ProductName: Rational solution for Collaborative Lifecycle Management
License Type: 
License Type ID: com.ibm.team.clm.practitioner
License Type Name: Practitioner
License Type Description: Diese Lizenz 'CLM Practitioner' ist für Anwender bestimmt, die aktiv an Projekten der Rational-Lösung für Collaborative Lifecycle Management mitwirken. Ein Benutzer mit zugewiesener Clientzugriffslizenz 'CLM Practitioner' hat vollen Lese- und Schreibzugriff auf das Änderungsmanagement, auf die Anpassung von Berichten, auf die Planung, auf das Softwarekonfigurationsmanagement, auf die Automation (Buildsystem), auf bestimmte Erweiterungen für IBM Unternehmensplattformen, auf Funktionen für Anforderungsdefinition und -management sowie Qualitätsmanagement und Lesezugriff auf Funktionen des Designmanagements, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational solution for Collaborative Lifecycle Management
License Type: 
License Type ID: com.ibm.team.rrc.reviewer
License Type Name: Contributor
License Type Description: Diese Mitarbeiterlizenz ist für professionelle Teammitglieder bestimmt, die keine Entwickler sind, aber aktiv am Projekt beteiligt sind. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Mitarbeiter hat vollen Lese- und Schreibzugriff auf das Änderungsmanagement, auf die Anpassung von Berichten und auf die Planung. Diese Lizenz ermöglicht außerdem den Lesezugriff auf Funktionen des Anforderungsmanagements, des Softwarekonfigurationsmanagements, der Automation (Buildsystem), des Testmanagements und des Designmanagements, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational DOORS Next Generation
License Type ID: com.ibm.team.rtc.contributor
License Type Name: Contributor
License Type Description: Diese Mitarbeiterlizenz ist für professionelle Teammitglieder bestimmt, die keine Entwickler sind, aber aktiv am Projekt beteiligt sind. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Mitarbeiter hat vollen Lese- und Schreibzugriff auf das Änderungsmanagement, auf die Anpassung von Berichten und auf die Planung. Diese Lizenz ermöglicht außerdem den Lesezugriff auf das Softwarekonfigurationsmanagement, auf die Automation (Build-System), auf das Anforderungsmanagement, das Testmanagement und das Designmanagement, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational Team Concert
License Type: 
License Type ID: com.ibm.team.rtc.stakeholder
License Type Name: Stakeholder
License Type Description: Diese Stakeholder-Lizenz ist zur Unterstützung von peripheren Benutzern gedacht, z. B. von externen Kunden, von Mitarbeitern der Benutzerunterstützung oder von Benutzern, die Arbeitselemente modifizieren und den Projektfortschritt beobachten müssen. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Stakeholder hat Lese- und Schreibzugriff auf Change Management sowie Lesezugriff auf Berichte und Planungsfunktionen, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational Team Concert
License Type: 
License Type ID: com.ibm.team.rtc.buildsystem
License Type Name: Build System
License Type Description: Die Clientzugriffslizenz 'Build System' kann nur einer Benutzer-ID zugewiesen werden, die von einem automatisierten Build-System verwendet wird. Damit haben Einheiten des Build-Systems Lesezugriff auf das gesamte Leistungsspektrum sowie Schreibzugriff auf alle Leistungsmerkmale, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational Team Concert
License Type: 
License Type ID: com.ibm.team.rtc.developer
License Type Name: Developer
License Type Description: Diese Lizenz ist für professionelle Entwickler bestimmt, die aktiv am Projekt beteiligt sind. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Entwickler hat vollen Lese- und Schreibzugriff auf das Änderungsmanagement, auf die Anpassung von Berichten, auf die Planung, auf das Softwarekonfigurationsmanagement und auf die Automation (Buildsystem). Diese Lizenz ermöglicht außerdem den Lesezugriff auf das Anforderungsmanagement, das Testmanagement und das Designmanagement, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten.
License Type ProductName: Rational Team Concert
License Type: 
License Type ID: com.ibm.team.clm.contributor
License Type Name: Contributor
License Type Description: Diese Mitarbeiterlizenz ist für professionelle Teammitglieder bestimmt, die keine Entwickler sind, aber aktiv am Projekt beteiligt sind. Ein Benutzer mit zugewiesener Clientzugriffslizenz für Mitarbeiter hat vollen Lese- und Schreibzugriff auf das Änderungsmanagement, auf die Anpassung von Berichten und auf die Planung. Diese Lizenz ermöglicht außerdem den Lesezugriff auf das Softwarekonfigurationsmanagement, auf die Automation (Buildsystem), auf das Anforderungsmanagement, das Testmanagement und das Designmanagement, sofern rollenbasierte Prozessberechtigungen keine diesbezüglichen Einschränkungen beinhalten. Floating-Lizenzen werden dynamisch von einem Lizenzserver zugewiesen. Ein Benutzer mit zugewiesener Floating-Lizenz kann in den Pool der Benutzer aufgenommen werden, die die verfügbaren, auf dem Lizenzserver installierten Floating-Lizenzen gemeinsam nutzen. Führt ein solcher Benutzer eine Operation aus, für die diese Lizenz erforderlich ist, wird ihm vom Lizenzserver dynamisch eine Floating-Clientzugriffslizenz zugewiesen, wenn eine solche verfügbar ist. 
License Type ProductName: Rational solution for Collaborative Lifecycle Management

The license type ID can be used to assign a license to the user.

	IContributor licenseContributor = teamRepository.contributorManager().fetchContributorByUserId(licenseUser, monitor);
	licenseAdminService.assignLicenseWithResult(licenseContributor,	licenseID);

For Example for user “bob” and the license type ID “com.ibm.team.rtc.developer”:

	IContributor licenseContributor = teamRepository.contributorManager().fetchContributorByUserId("bob", monitor);
	licenseAdminService.assignLicenseWithResult(licenseContributor,	"com.ibm.team.rtc.developer");

To remove a license use the code below and provide the license type ID

	IContributor licenseContributor = teamRepository.contributorManager().fetchContributorByUserId(licenseUser, monitor);
	licenseAdminService.assignLicenseWithResult(licenseContributor,	licenseID);

	licenseAdminService.unassignLicense(licenseContributor, licenseID);

Assigning licenses in a batch file

Using a plain java API application is not the only way to assign licenses. It is also possible to use the repo tools to do that. I use the following batch file to assign licenses to the users created for the JKE Banking sample life cycle project that I use to explore CLM.

echo on
set SERVERFOLDER="C:\CLM2016\6.0.3\JazzTeamServer\server"
set REPOSITORY="https://clm.example.com:9443/jts"
set USERID="myadmin"
set PASSWORD="myadmin"


rem primary users
call %SERVERFOLDER%\repotools-jts -createUser userId=bob licenseId=com.ibm.team.rrc.author repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=marco licenseId=com.ibm.rqm.tester repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=marco licenseId=com.ibm.team.rtc.developer repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=deb licenseId=com.ibm.team.rtc.developer repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=tanuj licenseId=com.ibm.rqm.tester repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=rebecca licenseId=com.ibm.team.rtc.developer repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%

rem Build user
call %SERVERFOLDER%\repotools-jts -createUser userId=build licenseId=com.ibm.team.rtc.buildsystem repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%

rem secondary users
call %SERVERFOLDER%\repotools-jts -createUser userId=ursula licenseId=com.ibm.team.rrc.author repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=curtis licenseId=com.ibm.rqm.viewer repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=tammy licenseId=com.ibm.rqm.tester repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
call %SERVERFOLDER%\repotools-jts -createUser userId=sally licenseId=com.ibm.team.rrc.author repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
rem Design Manager License 
rem %SERVERFOLDER%\repotools-jts -createUser userId=al licenseId= repositoryURL=%REPOSITORY% adminUserId=%USERID% adminPassword=%PASSWORD%
pause

This works very well, except that it takes a long time to run since each repotools command has to individually log in.

Download and 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 however run with any version of RTC that has the specific API already implemented. The code shown below should work with almost all versions of RTC.

The post shows client, common API that are available in the RTC Server SDK and the RTC Plain Java Client Libraries.

You can download the Eclipse project with the application to print and assign a license to a user here.

You can download the batch file to assign the licenses for the JKE Banking Example in a zip archive here.

Related posts

Summary

The code above can be used to assign and remove licenses from users in RTC and CLM. An alternative method is using the repotools. as always I hope that this helps users out there with their tasks.

Advertisements

Setting Custom Attributes for SCM Versionables


This is the forth post in the series around very fine grained access control permissions for work items and SCM versionables. It explains the last requirement, which was to be able to set the custom attributes on elements in the SCM system using the RTC API.

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
  4. Setting Custom Attributes for SCM Versionables – this post

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.

Note, some capabilities where only finalized in RTC 6.0.1 and are not available in versions before. Especially custom attributes for components and potentially for items other than SCM versionables.

Custom Attributes for SCM Versionables

Rational Team Concert 5.0.2 introduces support for creating custom attributes on source file versions.

The attributes can be set for every version of the SCM versionable. Dependent on the definition the attribute can be missing or different on different versions. This is different to the behavior we have seen in the post Setting Access Control Permissions for SCM Versionables, where the read access permission applies to all versions of the item.

The image below shows for which SCM elements custom attributes are available and how they can be set up to behave.

SCM Attributes

The behavior is important, because there are several very distinct requirements. For example it is desirable to be able to set some attribute only on one specific version. As an example to classify safety critical source code that needs special testing if changed, to allow for auditing. For others it is better to keep a value, once it is set.

Working with Custom Attributes for SCM Versionables

The following code shows how to work with the custom attributes for versionable handles. Please note, that there is no way to access these attributes in the RTC UI at the moment. They are accessible in the API otherwise they can only be set and read in the RTC SCM Commandline.

Getting the IScmService

To set custom attributes for versionables, requires the IScmService class com.ibm.team.scm.common.IScmService. As described in the post Setting Access Control Permissions for SCM Versionables, It is easy to get this service in the Server API, by basically using

IScmService fScmService = getService(IScmService.class);

However, in the Client API, this is not accessible as client library using the usual getClientLibrary() call.

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, so it is necessary to cast the ITeamRepository object to the actual class that implements the interface.

Listing Custom Attributes

See this post for a hint.

Setting Custom Attributes

The following code sets the custom attributes for versionable handles.

It basically gets the attributes available on the element. The data is returned as a hashmap with the attribute name being the key. This can be used for printing the data as well as shown below.

The code puts the attribute and its value in the hashmap and then this nap is passed to be saved.

/**
 * Set a specific attribute of versionable handles
 * 
 * @param vhandles
 * @param attributeName
 * @param attributeValue
 * @param scmService
 * @throws TeamRepositoryException
 */
public static void setAttribute(IVersionableHandle[] vhandles,
		String attributeName, String attributeValue, IScmService scmService )
		throws TeamRepositoryException {
	ICustomAttributeList[] versionableAttributesList = scmService
			.fetchCustomAttributesForVersionable(vhandles, null);
	
	for (int i = 0; i < versionableAttributesList.length; i++) {
		ICustomAttributeList attributeList = versionableAttributesList[i];
		Map<String, Object> attributeMap = attributeList
				.getCustomAttributes();
		attributeMap.put(attributeName, attributeValue);
		attributeList.setCustomAttributes(attributeMap);
		scmService.saveCustomAttributesForVersionable(vhandles[i],
				attributeList, null);
	}
}

As shown in the post Setting Access Control Permissions for SCM Versionables it is easy to create the required array if only one versionable handle is available using this code.

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

Similar, it is easy to use the code here if the available data is a change set, by using the same technique as shown in the post Setting Access Control Permissions for SCM Versionables.

In both case, it is possible to create the array with all elements that need the change and then run the operation for all. This is also the most efficient way.

Removing Custom Attributes

Custom attributes can be removed by removing an attribute entry from the map or creating a map that does not contain this attribute. It is possible to erase all custom attributes by setting an empty map like below:

ICustomAttributeList[] versionableAttributesList = scmService
		.fetchCustomAttributesForVersionable(vhandles, null);

for (int i = 0; i < versionableAttributesList.length; i++) {
	ICustomAttributeList attributeList = versionableAttributesList[i];
	attributeList.setCustomAttributes(new HashMap<String, Object>());
	scmService.saveCustomAttributesForVersionable(vhandles[i],
			attributeList, null);
}

I have not tested this, but I assume that attributes that are defined to have a default value and are automatically applied, will be recreated with the default value when deleting them from the map.

Getting Custom Attributes

The next code snippet shows how to get or accesses the custom attributes and how to print them. It basically gets the map and then iterates the contained keys to access the attributes:

/**
 * Print the attributes for an array of versionable handles
 * 
 * @param vhandles
 * @param scmService
 * @throws TeamRepositoryException
 */
public static void printAttributes(IVersionableHandle[] vhandles, IScmService scmService,String message)
		throws TeamRepositoryException {
	System.out.println(message);
	ICustomAttributeList[] versionableAttributesList = scmService
			.fetchCustomAttributesForVersionable(vhandles, null);

	for (int i = 0; i < versionableAttributesList.length; i++) {
		ICustomAttributeList attributeList = versionableAttributesList[i];
		Map<String, Object> attributeMap = attributeList
				.getCustomAttributes();
		printAttributes("Attributes: ",attributeMap);
	}
}


/**
 * @param message
 * @param attributeMap
 */
private static void printAttributes(String message, Map<String, Object> attributeMap) {
	System.out.print(message + " - ");
	Set keys = attributeMap.keySet();
	if(keys.isEmpty()){
		System.out.print("No");
	}
	System.out.println(" attributes found:");
	for (String key : keys) {
		Object value = attributeMap.get(key);
		printAttributeValue(message, key, value);			
	}	
}

/**
 * @param message
 * @param key
 * @param value
 */
private static void printAttributeValue(String message, String key, Object value) {
	if (value != null) {
		System.out.println(message + ": " + key
				+ " value: " + ((String) value));
	} else {
		System.out.println("Version Attribute: " + key
				+ " no value");
	}
}

Getting Available Custom Attributes

Please see How to retrieve the set of allowed custom SCM attributes via Plain Java Client API?

Custom Attributes for Other SCM Elements

The API to get or set custom attributes for other objects, is similar to the API for versionables. Since they are unique objects and don’t have different versions, there is only one set of custom attributes available for them.

Please note, the full functionality for these objects only became available in 6.0.1

Custom Attributes for Streams

To set custom attributes for streams, since 6.0.1 the common API provides the interface

com.ibm.team.scm.service.internal.ScmService.setWorkspaceCustomAttributes(IWorkspaceHandle, IGenericAttributes, String[], ISynchronizationTimes[], IRepositoryProgressMonitorHandle)

to set custom attributes.

The interface com.ibm.team.scm.common.IWorkspace provides the method

com.ibm.team.scm.common.IWorkspace.getCustomAttributes()

to get the custom attributes of the stream.

Please note, that the data is cached, if you want accurate information refresh the object e.g. using com.ibm.team.scm.client.IWorkspaceConnection.refresh(IProgressMonitor).
The code to set and get the custom attributes would look like below:

IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
IWorkspaceSearchCriteria criteria = IWorkspaceSearchCriteria.FACTORY.newInstance().setKind(IWorkspaceSearchCriteria.STREAMS);
criteria.setExactName("JKE Banking Integration Stream");
List connection = wm.findWorkspaces(criteria, Integer.MAX_VALUE, monitor);
IWorkspaceHandle streamHandle = connection.get(0);
IWorkspaceConnection wcStream = wm.getWorkspaceConnection(streamHandle, monitor);
IWorkspace streamConnection = wcStream.getResolvedWorkspace();
IScmService scmService = ScmServiceClient.getSCMService(teamRepository);
		
Map<String,Object> customAttributes=new HashMap<String,Object>();
customAttributes.put(CUSTOM_ATTRIBUTE, "Test");
String[] attributesToClear = new String[] { CUSTOM_ATTRIBUTE };

wcStream.refresh(monitor);
printAttributes("Stream AttributesInitial", streamConnection.getCustomAttributes());
		
// Set the custom attributes
IGenericAttributes streamAttributes = IGenericAttributes.FACTORY.newInstance(customAttributes);
scmService.setWorkspaceCustomAttributes(streamHandle, streamAttributes, null, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));

wcStream.refresh(monitor);
printAttributes("Stream Attributes", streamConnection.getCustomAttributes());

// Clear the custom attributes
scmService.setWorkspaceCustomAttributes(streamHandle, null, attributesToClear, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));

wcStream.refresh(monitor);
printAttributes("Stream AttributesAfterClear", streamConnection.getCustomAttributes());

Custom Attributes for Snapshots

To set custom attributes for snapshots, since 6.0.1 the common API provides the interface

com.ibm.team.scm.service.internal.ScmService.setBaselineSetCustomAttributes(IBaselineSetHandle, IGenericAttributes, String[], ISynchronizationTimes[], IRepositoryProgressMonitorHandle)

to set custom attributes.

In addition, in 6.0.1, the interface com.ibm.team.scm.common.IBaselineSet provides the method

com.ibm.team.scm.common.IBaselineSet.getCustomAttributes()

to get the custom attributes of the snapshot.

Please note, that the data is cached, if you want accurate information refresh the object e.g. using the IItemManager and the IItemManager.REFRESH flag to get the latest information.
The code to set and get the custom attributes would look like below:

IBaselineSetSearchCriteria bScriteria = IBaselineSetSearchCriteria.FACTORY.newInstance();
bScriteria.setExactName("TestSnapShot");
List baselineSetHandles = SCMPlatform.getWorkspaceManager(teamRepository).findBaselineSets(bScriteria, 100, monitor);
BaselineSetHandle snapShotHandle = (BaselineSetHandle) baselineSetHandles.get(0);
IBaselineSet snapShot = (IBaselineSet) teamRepository.itemManager().fetchCompleteItem(snapShotHandle,IItemManager.REFRESH,monitor);
	
printAttributes("Snapshots AttributesInitial", snapShot.getCustomAttributes());

scmService.setBaselineSetCustomAttributes(snapShotHandle, custAttributes, null, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
snapShot = (IBaselineSet) teamRepository.itemManager().fetchCompleteItem(snapShotHandle,IItemManager.REFRESH,monitor);
printAttributes("Snapshots Attributes", snapShot.getCustomAttributes());

scmService.setBaselineSetCustomAttributes(snapShotHandle, null, attributesToClear, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
snapShot = (IBaselineSet) teamRepository.itemManager().fetchCompleteItem(snapShotHandle,IItemManager.REFRESH,monitor);
printAttributes("Snapshots AttributesAfterClear", snapShot.getCustomAttributes());

Custom Attributes for Baselines

To set custom attributes for streams, since 6.0.1 the common API provides the interface

com.ibm.team.scm.service.internal.ScmService.setBaselineCustomAttributes(IBaselineHandle, IGenericAttributes, String[], ISynchronizationTimes[], IRepositoryProgressMonitorHandle)

to set custom attributes for snapshots.
In addition, in 6.0.1, the interface com.ibm.team.scm.common.IBaseline provides the method

com.ibm.team.scm.common.IBaseline.getCustomAttributes()

to get the custom attributes of the baseline.

Please note, that the data is cached, if you want accurate information refresh the object e.g. using the IItemManager and the IItemManager.REFRESH flag to get the latest information.
The code to set and get the custom attributes would look like below:

IBaselineHandle baseLineHandle = buildComponent.getInitialBaseline();
IBaseline baseLine = (IBaseline) teamRepository.itemManager().fetchCompleteItem(baseLineHandle,IItemManager.REFRESH,monitor);
		
printAttributes("Baseline AttributesInitial", baseLine.getCustomAttributes());

scmService.setBaselineCustomAttributes(baseLineHandle, custAttributes, null, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
baseLine = (IBaseline) teamRepository.itemManager().fetchCompleteItem(baseLineHandle,IItemManager.REFRESH,monitor);
printAttributes("Baseline Attributes", baseLine.getCustomAttributes());

scmService.setBaselineCustomAttributes(baseLineHandle, null, attributesToClear, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
baseLine = (IBaseline) teamRepository.itemManager().fetchCompleteItem(baseLineHandle,IItemManager.REFRESH,monitor);
printAttributes("Baseline AttributesAfterClear", baseLine.getCustomAttributes());

Custom Attributes for Components

To set custom attributes for components, since 6.0.1 the common API provides the interface

com.ibm.team.scm.service.internal.ScmService.setComponentCustomAttributes(IComponentHandle, IGenericAttributes, String[], ISynchronizationTimes[], IRepositoryProgressMonitorHandle)

In addition, in 6.0.1, the interface com.ibm.team.scm.common.IComponent provides the method

com.ibm.team.scm.common.IComponent.getCustomAttributes()

to get the custom attributes of the component.

Please note, that the data is cached, if you want accurate information refresh the object e.g. using the IItemManager and the IItemManager.REFRESH flag to get the latest information.
The code to set and get the custom attributes would look like below:

printAttributes("Component AttributesInitial", buildComponent.getCustomAttributes());

scmService.setComponentCustomAttributes(buildComponent, custAttributes, null, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
buildComponent = (IComponent) teamRepository.itemManager().fetchCompleteItem(buildComponent,IItemManager.REFRESH,monitor);
printAttributes("Component Attributes", buildComponent.getCustomAttributes());

scmService.setComponentCustomAttributes(buildComponent, null, attributesToClear, null, IRepositoryProgressMonitor.ITEM_FACTORY.createItem(monitor));
buildComponent = (IComponent) teamRepository.itemManager().fetchCompleteItem(buildComponent,IItemManager.REFRESH,monitor);
printAttributes("Component AttributesAfterClear", buildComponent.getCustomAttributes());

Summary

This post explains how to set custom attributes for SCM elements. As always, I hope this is useful to someone out there.

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.

Setting Access Control Permissions for Work Items


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

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 – this post
  3. Setting Access Control Permissions for SCM Versionables
  4. Setting Attributes for SCM Versionables

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.

Work Item Access Control Code

Now that we have all the rules and pieces together as explained in this post, lets have a look at the work item access control. Again, note, that work items can only have access groups and project areas set for access control in manual mode as shown in the image below.

WorkItemAccessContextSelection

To set the access context for work items, the interface IWorkItem provides the method

IWorkItem.setContextId(UUID contextId);

It should also be possible to set the internal attribute with the ID “contextId” also available as constant

IWorkItem.CONTEXT_ID_PROPERTY

in the IWorkItem interface.

Working With the UUID Representing the Access Context

How to find the objects we are interested in, is explained in this post.

To be able to set the access control context for work items, it is necessary to understand how to access the value that represents the access context of a process area (project or team area) or access group.

The common API provides this method to access the access context of an item:

com.ibm.team.repository.common.IAuditableHandle.getItemId()

This interface implements (through a number of intermediate interfaces) the interface IItemHandle, which defines the method getItemId() for all items. So strictly speaking the method is available as

com.ibm.team.repository.common.IItemHandle.getItemId()

This means, the access context can be accessed already from the item handle e.g. in case only a handle such as IProcessAreaHandle, IProjectAreaHandle, ITeamAreaHandle, IAccessGroupHandle or IContributorHanlde is available.

It is also available at all resolved IItems such as IProcessArea, IProjectArea, ITeamArea, IAccessGroup or IContributor as all these interfaces implement IItemHandle. In fact it is available on all IItems. Example code:

IProcessAreaHandle paHandle = ..... get the handle
IProcessArea pa = ..... resolve the handle

// These values are the same
UUID paHandleContext1 = paHandle.getItemID(); 
UUID paHandleContext2 = pa.getItemID();

Note, that all items in RTC have an itemID like the one above and can be uniquely be identified by it. Other itemID’s are just not used as access context.

The return value is of the type

com.ibm.team.repository.common.UUID

The UUID is an object that uniquely identifies an item in the repository and can be used to recreate the item as well. The string representation shows up in several places, for example in OSLC links. To get the string representation use

com.ibm.team.repository.common.UUID.getUuidValue()

If it is necessary to recreate an UUID from the string representation as an example named contextIDString, use the method

UUID.valueOf(contextIDString)

This recreates the UUID of the object if the string is a valid string representation of a UUID.

This way, you have the choice to use the UUID or its string representation to set the access context of RTC work items.

Setting the WorkItem Access Context

Given the UUID provided by a project area or access group, it is possible to set the access context of a work item.

Although all the code to retrieve the access context is common code, the code to set the work items context differs on client and server.

In the Client API, use a WorkItemOperation like the code below.

/*******************************************************************************
 * 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 org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.common.UUID;
import com.ibm.team.workitem.client.WorkItemOperation;
import com.ibm.team.workitem.client.WorkItemWorkingCopy;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.ItemProfile;

/**
 * WorkItem Operation to set the access context for the work item. This is
 * client code only.
 * 
 */
public class SetAccessControlOperation extends WorkItemOperation {

	private UUID fAccessContext;

	public SetAccessControlOperation(UUID accessContext,
			ItemProfile loadProfile) {
		super("Set Access Context", loadProfile);
		fAccessContext = accessContext;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException {
		workingCopy.getWorkItem().setContextId(fAccessContext);
	}
}

 

The operation can be called like this:

	SetAccessControlOperation operation = new SetAccessControlOperation(
		UUID.valueOf(context), IWorkItem.FULL_PROFILE);
	operation.run(workItem, monitor);

Because the WorkItemOperation is not available in the server API, a save has to be performed manually like below.

	// Set the work item visibility
	// Get the full state of the parent work item so we can edit it
	// and avoid stale data exceptions
	// Get the required service interfaces
	IWorkItemServer wiServer = getService(IWorkItemServer.class);
	IWorkItem fullItem = (IWorkItem) wiServer
			.getAuditableCommon()
			.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
			.getWorkingCopy();

	// Set the context of the work item
	fullItem.setContextId(UUID.valueOf(context));

	// Save the work item
	HashSet additionalParams = new HashSet();
	additionalParams.add("WORK_ITEM_CONTEXT_CHANGE");

	IStatus status = wiServer.saveWorkItem3(fullItem, null, null,
			additionalParams);
	if (!status.isOK()) {
		String parameter[] = new String[1];
			parameter[0] = Integer.toString(fullItem.getId());

		String description = NLS.bind(
				"Failed to save work item ''{0}''.", parameter);
		IReportInfo info = collector.createInfo(
				"WORK_ITEM_CONTEXT_CHANGE"
				+ ": Error running the participant.",
				description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
		return;
	}

Please note, if this is performed in a participant/follow up action, it is important to send an additional save parameter like “WORK_ITEM_CONTEXT_CHANGE”. This protects the participants from causing a recursion ultimately crashing the server. The participant can protect itself to run again for the save it just performed by testing for the additional save parameter. If the parameter is detected, the participant knows it caused the save and should exit to prevent the recursion.

Please also note, that in some cases you might have a WorkItemWorkingCopy, instead of an IWorkItem. In these cases use a cast or the method getWorkItem() on the working copy to get the IWorkItem item to be able to set the context.

The examples above uses the string version of the UUID, however, it would have been possible to get the UUID directly from the IItem.

Getting the WorkItem Access Context

It is possible to get the access context of a work item, if the work item is accessible for read, by using IWorkItem.getContextId(). The resulting UUID can be used to get the related item.

Summary

This post explains how to set read access control for work items. The next post will explain the details around setting access control for SCM versionables.

Manage Access Control Permissions for Work Items and Versionables


A customer requires very fine grained access control to work items and versionable objects such as files in the SCM system. In addition the customer had the requirement to be able to set attributes on elements in the SCM system.

I knew that it can be set for work items and kind of how to because I had briefly looked at it in the A RTC WorkItem Command Line Version 3.0. But I was not completely sure about the rules and how to access the SCM part and the attributes in SCM.

Since the customer needed this urgently I looked at what the rules are and what can be done.

The content is way too big for just one post, so this is going to be a series. The first post explains the rules around access control permissions and some basic API’s around finding the objects that can be used to provide the access control context.

Related posts

The posts in this series are:

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

Also see

Controlling access to source control artifacts in Rational Team Concert

License and Download

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.

Problem Description

The first question was, if it was possible to set the permission to access

  1. Work Items
  2. Items in the SCM system such as versionables

The second question was, if it was possible to set the attributes introduced in RTC 5.0 that can be specified and set using the API. The documentation only mentioned the SCM command line.

The third question was, how team areas and access groups can be created automatically.

The forth question was, what exactly the rules are:

  1. Can this be done in the server?
  2. Can this be done in the client?
  3. Can this be done in a command line tool or from another application?
  4. What permissions are required and what are the rules in the different contexts?

Solution Approach

The approach that we chose was to try to implement a minimal demonstrator to demonstrate the capabilities and prove them as a proof of concept. this demonstrator was also intended to serve as a platform to find the rules.

Learning

Here is what we learned creating the demonstrator.

The Rules – Work Items

Work Items have an attribute called “Restricted Access” that can be used to control access to them. There are basically the following modes available for work items that can be used.

  1. Access control using access control of the project area
  2. Restricted access by category
  3. Restricted access by setting the restricted access attribute

Option 1: Only the users that have access to the context set for the project area can access these items. This can be set to everyone basically exposing the work items to everyone. Other options a re project area membership and access control list etc. The rules here are very clear.

Option 2: It is possible to set RTC in a project area to automatically determine access permissions based on the category (filed against) of the work item. This sets the restricted access to the project area or team area associated to the category. Only members of the team area (and members of sub team areas of the team area associated to the category) have access to the work items which are restricted by category. It is worth noting, that a user being associated to the project area can not look into the work items filed against teams, if these limit access, it is quite the opposite.

Option 3: Use a special editor presentation to set the restricted access attribute manually to a project area or an access group. this could potentially be supported by automation.

The most important learning for option 3 is, that it is only possible to set the restricted access attribute to a project area or an access group this way. It is not possible to set the access context to a team area. If the context is set to a team area, it automatically picks up the containing project area. This also holds for automation.

See this help topic on how to set up the work items to allow setting the restricted access.

The Rules – Versionables

Prior to RTC 6.0.1, which has just been released, it is only possible to set read access control to the default (which is controlled by the component) project areas, team areas (called process areas if the distinction is unimportant) and a single user.

If access control is set to a team area, any member of that team area and its sub team areas have access. Note that this is different from option 2 for work items. If access control is set to a project area, any member who has access to the project area has access to the item.

RTC 6.0.1 introduces the capability to set access control to an access group. Only members of this access group have access to the item. This seems to be the best option by far, especially since access groups can contain project areas and team areas which adds their members to the access group.

Please note, that access control applies to the whole item and its history.

JazzAdmin Repository Role

Users with the JazzAdmin repository role have access to any work item or SCM controlled item, regardless of the access control setting. Users with this role (and sufficient licenses) can access all data.

Project Area Administrators

Administrators of a team or project area and don’t have the JazzAdmin repository role can not access items that have access control set to a context they don’t have access to.

General Rules

Users can only set the access context of work items or items in the SCM system to an access context that preserves their access permission. It is not possible to find or select an access group, process area the user has no access to/is member of or a different user and set the access context to it. This would remove the read permissions and is prevented.

Users with the JazzAdmin role and sufficient licenses however can do this, because they don’t loose read access.

General Rules For Automation

All automation can only perform the operations that are permitted to the user that is used to run the automation. This especially means that automation running in the context of a user with JazzAdmin repository role and the required licenses can perform all operations. The user context available in an automation depends on the automation.

  1. Follow up actions (participants) run in the context of the user that is performing the operation on the client or the server. They are especially not elevated run in a JazzAdmin repository role, if the user that performs the operation does not have this role. It is also not possible to use a different users or services to elevate the permissions.
  2. Preconditions (advisors) must not change the triggering elements. Otherwise the same rules apply as described in follow up actions.
  3. Asynchronous tasks or services in RTC are run with JazzAdmin permissions and can use the IImpersonationService to change the operation to a different user context.
  4. Plain Java Client Library or other client based automation run in the context of the user that was used to log into the system.

In the context of this post, 1 means, that it is not possible to set access control in such an operation that would remove read access from the user that performs the operation. If you require rules where this could be necessary, it would be necessary to run in a administrative context such as an Asynchronous task or in a client application with JazzAdmin repository role.

Finding ProcessAreas, Access Groups and Contributors

How to find the access context information needed using the Java API?

Finding ProcessAreas

There are many ways to find ProcessAreas (project areas and team areas) using the API, the simplest one assumes an java.net.URI constructed from a string based name path of the process area separated by ‘/’.

Lets assume a project area named TestProject1 and a team area within the project are named TestTeam1 and a sub team area underneath TestTeam1 named TestSubTeam1.
The URI for the project area could be constructed from the string “TestProject1”. The URI for TestTeam1 could be constructed from the string “TestProject1/TestTeam1”. Similar the URI for TestSubTeam1 could be constructed from the string “TestProject1/TestTeam1/TestSubTeam1”.

Process area names could contain spaces. Spaces are not allowed in URI’s, therefore they need to be replaced by the encoding character string “%20”.

To construct an java.net.URI from a string value use the following code.

String namePath = "TestProject1/TestTeam1/TestSubTeam1";
URI anURI = URI.create(URI.create(namePath.replaceAll(" ", "%20")))

The service to find the process area by its URI is provided by the interface IAuditableCommon. This interface class, as its name implies, is a common interface that is available in the RTC Plain Java API, as well as the RTC Eclipse client and Eclipse server SDK. This allows to use this API call in any possible context, either by

IAuditableCommon auditableCommon = (IAuditableCommon) teamRepository
	.getClientLibrary(IAuditableCommon.class);

in Plain Java and client SDK based code, or using

IAuditableCommon auditableCommon = getService(IAuditableCommon.class);

in the server extensions that all extend AbstractService (or in case of SCM server extensions AbstractScmService, which extends AbstractService).

For code that already retrieved the common Service IWorkItemCommon, for example to find work items, IAuditableCommon is available using the call

IAuditableCommon auditableCommon = workItemCommon.getAuditableCommon();

So the following code would retrieve the access context for the team area “/TestTeam1/TestSubTeam1” nested in the team area “TestTeam1” in the project area “TestProject1”

String namePath = "TestProject1/TestTeam1/TestSubTeam1";
IProcessArea area = auditableCommon.findProcessAreaByURI(URI.create(namePath.replaceAll(" ", "%20")), null, monitor);
UUID context = area.getItemId();

Note, for work items only a project area is a valid context. Setting the context above will result in the project area containing the process area to be set. Alternatively use

UUID context = area.getProjectArea().getItemId();

for work items and document the fact to save the time to figure it out the hard way during testing (like I did).

Finding the Public Access Context

Work items can also be set to public read access. The constant com.ibm.team.repository.common.IContext.PUBLIC is available for that.

UUID publicContext = IContext.PUBLIC.getUuidValue();

provides with the public access context.

Finding Access Groups

Access Groups are managed in the administration pages of the RTC application. It is possible to create, modify and delete access groups. The image below shows this section.

Access Group ManagementAccess Groups are also accessible using the IAuditableCommon common interface that was already used in the section above.

To get all access groups use this code:

IAccessGroup[] groups;
groups = auditableCommon.getAccessGroups(null, Integer.MAX_VALUE,
	monitor);

This returns all access groups up to a maximal number that is passed to the method.
It is possible to pass a filter to select only access groups with specific name pattern like below.

IAccessGroup[] groups;
groups = auditableCommon.getAccessGroups("My*", Integer.MAX_VALUE,
	monitor);

The access groups can be iterated like below to compare the name or do something similar.

for (IAccessGroup group : groups) {
// Compare the value to the access group name.
	if (group.getName().equalsIgnoreCase(value)) {
		return group;
	}
}

Similar to the process areas above, there is also a public access group which can be obtained like this:

return auditableCommon.getAccessGroupForGroupContextId(
	IContext.PUBLIC, monitor);

Finding Contributors

Finding contributors is different in the client and the server. There is no common API available.
In the client API the IContributorManager can be used for example using the following methods.

IContributorManager contributorManager= teamRepository.contributorManager();
// Find a user by the users ID returns a handle
IContributorHandle contributorHandle = contributorManager
	.fetchContributorByUserId(attributeValue, monitor);

// Get all users, returns a list of full IContributor items
List contributors = contManager.fetchAllContributors(monitor);

In the server API the IContributorService is available and provides the following method.

IContributorService contributorService = getService(IContributorService.class);
contributorService.fetchContributorByUserId(userId);

I was not able to locate a way to search for all users like in the client API. The server API usually gets a user passed and there is no need to find all users.

Summary

This post explains the rules around read access control for work items and RTC SCM versionables and some basic API’s around finding the objects that can be used to provide the access control context. The next post will explain the details around setting access control for work items.

 

Getting the RTC User Work Environment Information


Recently I was asked how to access the users work environment information. This is basically the information displayed in the Work Environment Tab of the user editor.

User Work EnvironmentI was able to get at the Information. The code below shows how that works and what the information means.

Related code on this blog can be found in
Manage Scheduled Absences Using The PlainJava Client Libraries.

Please note, I looked briefly into ways to update that information, but I was not able to find any public interface to do so. I found internal methods that apparently do it, but not in a way that has an easy API provided to the user.

License and Download

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!

You can download the latest version here: as Eclipse project.

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

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 code linked from this post contains Client API.

Solution

The few methods below show how to access the data.

As usual, I use a very simple base class that connects to the repository and only performs the operations needed to get at the data. Please look at the downloadable code, if you are not sure how all that works together.

The specific API for the data in question is in the packages.

 com.ibm.team.apt.common,
 com.ibm.team.apt.client

The run() method below is called with all the parameters needed and basically connects to the team repository. It then finds the user it should look at based on the ID provided.

Next it gets the IResourcePlanningClient client library, and from that the ResourcePlanningManager which provides access to the data we are interested in.

The data we want is stored in the IContributorInfo that is returned by the call to the method getContributorInfo() providing the contributor object for the user we are interested in.

The data of interest is in the IWorkLocationDefinition available from the IContributorInfo. The code gets the information and then evaluates the details to print them.

private static boolean run(String[] args) throws TeamRepositoryException {

	if (args.length != 4) {
		System.out
				.println("Usage: GetWorkLocationAndWorkDayDetail    ");
		return false;
	}

	String repositoryURI = args[0];
	final String userId = args[1];
	final String password = args[2];
	String userToLookup = args[3];
	IProgressMonitor monitor = new NullProgressMonitor();

	ITeamRepository teamRepository = TeamPlatform
			.getTeamRepositoryService().getTeamRepository(repositoryURI);
	teamRepository
			.registerLoginHandler(new LoginHandler(userId, password));
	teamRepository.login(monitor);
		
	// Get the user to look at
	System.out.println("Data for user: " + userToLookup + ".");
	IContributor inspectUser = teamRepository.contributorManager()
			.fetchContributorByUserId(userToLookup, monitor);
		
	// Get the resource planning client 
	final IResourcePlanningClient resourcePlanning = (IResourcePlanningClient) teamRepository
			.getClientLibrary(IResourcePlanningClient.class);

	// Get the contributor related planning information
	IContributorInfo info = resourcePlanning.getResourcePlanningManager()
			.getContributorInfo(inspectUser, true, monitor);
		
	// Get the worklocation information that contains the working days
	IWorkLocationDefinition location = info.getWorkLocation(inspectUser);
	printWorkLocation(location);
	printWorkDays(location);

	teamRepository.logout();

	return true;
}

First it prints the work location information, basically the user environment information with the timezone and the regional settings.

There is some data “Variant” I am not sure it is used.

/**
 * Print the work location data
 * This includes the available location with timezone
 * 
 * @param location
 */
private static void printWorkLocation(IWorkLocationDefinition location) {
	System.out.println("Location");
	System.out.println("Language: " + location.getLanguage());
	System.out.println("Timezone: " + location.getTimeZone());
	System.out.println("ZoneOffset: " + location.getZoneOffset());
	System.out.println("Country: " + location.getCountry() + " not used");

	// Not used
	System.out.println("Variant: " + location.getVariant() + " not used");
}

Then the code prints the work day information.

/**
 * Print all work days for a users location
 * 
 * @param location
 */
private static void printWorkDays(IWorkLocationDefinition location) {
	System.out.println("\nWorkdays");
	Collection workDays = location.getWorkDays();
	for (IWorkDayDefinition workDay : workDays) {
		printWorkDay(workDay);
	}
}

private static int HOUR_IN_MILLISECONDS=60*60*1000;

/**
 * Print the work day data
 * 
 * @param workDay
 */
private static void printWorkDay(IWorkDayDefinition workDay) {
	int hour = HOUR_IN_MILLISECONDS;
	System.out.println("WorkDay: " + workDay.getDay().getName()
			+ " Literal " + workDay.getDay() + ".");
	System.out
			.println("\tWorking time: " + workDay.getWorkingTime()
					+ " milliseconds equals to " + workDay.getWorkingTime() / hour
					+ " hours");
	System.out.println("\tEndTime " + workDay.getEndTime()
			+ " milliseconds equals to " + workDay.getEndTime() / hour + " hour.");
}

The interesting information here is in the interface IWorkDayDefinition.

  • The day returned by getDay() as IWeekDay which provides the day information using a label and a name; label and name are a string
  • The working time (how many hours a day) for each day using getWorkingTime()
  • The time when the user stops working using getEndTime()

Both time values are provided in milliseconds. The working time is the time in hours set in milliseconds. The end time is the end time in milliseconds from midnight.
Running the code with parameters

"https://clm.example.com:9443/ccm/" "admin" "*****" "curtis"

results in the following output (for the same user shown in the image at the beginning):

Data for user: curtis.
Location
Language: en
Timezone: US/Eastern
ZoneOffset: -18000000
Country: US
Variant: null not used

Workdays
WorkDay: MONDAY Literal MONDAY.
	Working time: 28800000 milliseconds equals to 8 hours
	EndTime 64800000 milliseconds equals to 18 hour.
WorkDay: TUESDAY Literal TUESDAY.
	Working time: 28800000 milliseconds equals to 8 hours
	EndTime 61200000 milliseconds equals to 17 hour.
WorkDay: WEDNESDAY Literal WEDNESDAY.
	Working time: 28800000 milliseconds equals to 8 hours
	EndTime 61200000 milliseconds equals to 17 hour.
WorkDay: THURSDAY Literal THURSDAY.
	Working time: 28800000 milliseconds equals to 8 hours
	EndTime 61200000 milliseconds equals to 17 hour.
WorkDay: FRIDAY Literal FRIDAY.
	Working time: 28800000 milliseconds equals to 8 hours
	EndTime 61200000 milliseconds equals to 17 hour.
WorkDay: SATURDAY Literal SATURDAY.
	Working time: 0 milliseconds equals to 0 hours
	EndTime 0 milliseconds equals to 0 hour.
WorkDay: SUNDAY Literal SUNDAY.
	Working time: 0 milliseconds equals to 0 hours
	EndTime 0 milliseconds equals to 0 hour.

Summary

This post provides you with all the code and information needed to read and interpret the WorkLocation information stored in RTC.

As always, I hope this post saves users out there some time – or is at least fun to read.

Build Artifacts Publishing and Automated Build Output Management Using the Plain Java Client Libraries


The Jazz Build Engine is unfortunately very limited with respect to publish build results. Can this be done better? Is it possible to automatically move the data of special builds into artifact repositories? I have published some articles in the Jazz.net library  long ago. Since the situation hasn’t really changed, I wanted to publish them here for completeness and to make them easier to find.

The Jazz development uses a similar approach (albeit not that basic) to manage their builds.

See a comparison of binary repository managers.

Publishing Build Output Files

It is possible to upload logs and result files to the build result of a Jazz Build Engine (JBE) build run.

However, uploading big files just makes the repository bigger. Even worse, clicking on any uploaded logs and other uploaded files on a build result leads to downloading the file to the local disk, where the file can hopefully be opened with a local editor.

This does not scale well and ideally it would be nice if all that is in the build result is a link to the build and the supporting result files which one could see in a browser. It would also be interesting to be able to see a web page for each build that shows statistics and result details.

How can this be done easily?

The article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert shows a simple scenario how to do this and have a direct browser-based access to all of the build result files.

As a summary the basic approach used in the article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert is to have the build files, including the built files and the build result information stored somewhere (on a disk) and only publish the URL to that location back to the build result for navigation. The URL is just an index file in the simplest example, it could be far more complex and, for example, contain statistics, test results and test coverage information.

Management of Build Output and Backup or Using Artifact Repositories

Publishing basically means keeping the loaded build repository workspace and the generated files somewhere, and publish them with a HTTP server. No matter how cheap disk space might be, at some point in time you want to get rid of outdated build data that is not referenced any more. You also want to make sure to back up and keep the relevant build data needed to be preserved such as released versions.

The article Automated Build Output Management Using the Plain Java Client Libraries explains how to do this by creating a small RTC Plain Java Client Library application that purges build data and results, allows to back up the relevant builds and to store them in an artifact repository. The example code has a placeholder for this step using IBM Rational Asset Manager. By copying files or using an API this could target any system. Provided the system publishes the data back into the web, the example shows code to add the link to the location in the artifact repository to the build result for later reference.

License and Download

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!

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

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 Build Output Manager

I will not show the whole code here, only some special parts that are interesting. How the code works and more reasoning can be found in the article Automated Build Output Management Using the Plain Java Client Libraries. To run this tool assumes a setup as described in the article Build Artifacts Publishing Using HTTP Servers in Rational Team Concert and the one above.

The code provided is for a more complete version of the Build Output Manager published before, it can actually delete folders and it can be run in a simulation mode. It also creates console output with more information.

The project looks as follows:

Java Project

The folder src contains the single class that makes up this tool.

The folder build contains a jardesc file that can be used to create the Jar file that allows to run it.

The folder BuildPublishing contains an example ant  build script for result publishing and an example for an Apache configuration.

The root folder contains a file RunManager.bat and RunManager.sh that runs the jar file and the class within assuming the jar file is in the same folder. The files define the location for the JAVA_HOME and the location of the Plain Java Client Libraries files. The structure is set up to allow to copy the Jar file and the shell scripts into a sub folder of the installs folder of a Extensions workshop environment and run it. As an example name the folder BuildResultManager. Change the paths if needed.

Folder with JAR file and Scripts

The root folder also contains the file ReadMe – HowToRelease.txt which explains how to build and release the tool. follow the description to release your version.

What the Build Output Manager Does

The general idea is described in the article Automated Build Output Management Using the Plain Java Client Libraries. Here a short summary.

Given a build result root folder that contains sub folders for each build, the Build Output Manager literates the sub folders. For each sub folder it checks if there is a file build.properties available that contains the information needed to identify the build result and the RTC repository.

If there is a build.properties file, the Build Output Manager uses the information inside and contacts the RTC server to check if the build result is still available. If it can find the build result, it checks its details.

It checks if the build result has been marked as a deliverable (also called release) since it last checked. If it is marked as a deliverable, the build is important and needs to be preserved, in order to satisfy the links back from the build result. At this point it would also be interesting to create a backup of the build data and/or move the build data into an artifact repository.

The code here has a stub method addToBackUp() that pretends to store the data in the IBM Rational Asset Manager. This could be enhanced to work for any other artifact repository or backup system. The existing code also shows how to publish the link information to the artifact in the repository back to the build result. For builds identified as deliverable, the code creates a new file in the build result folder that marks this folder as deliverable. This file is used in subsequent runs to skip any checks and thus preserves the folder from deletion.

If the build result is no longer available the Build Output Manager assumes that the build result was purged and the build result folder can be deleted.

If there is no build.properties file, the Build Output Manager can not really determine anything in the repository. However, it can act on aging and other information and has some code built-in the method handleNotQualifyingFolders() to help dealing with this situation and some flags to control its behavior.

By default all non qualifying folders (lacking the build.properties file) are checked for aging. All over a certain age are checked if they should be deleted based on special settings in the Build Output Manager. If the Build Output Manager is set to delete these folders, the deletion is performed.

All non qualifying folders that are past the aging threshold and are not deleted are collected in a list as deletion candidates. After processing all folders, the Build Output Manager publishes a list of notifications for all the remaining candidates for deletion by aging that are left over. This notification provides information in a human readable format that could be used by an administrator to check what is going on.

The information below is the result of a run with the default parameters.

Example output:

Process Build Result ID: _q47SAH2OEeWt7sIN-jhQsg	 in folder: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20151028-1712
	Processing Build Result[UUID _q47SAH2OEeWt7sIN-jhQsg]	 May be deleted	 Personal build	Status:COMPLETED
Process Build Result ID: _wz8NAX2QEeWt7sIN-jhQsg	 in folder: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20151028-1727
	Build result no longer in repository: _wz8NAX2QEeWt7sIN-jhQsg
	DELETING: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20151028-1727
Process Build Result ID: _ZsVi0X2REeWt7sIN-jhQsg	 in folder: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20151028-1731
	Processing Build Result[UUID _ZsVi0X2REeWt7sIN-jhQsg]	 May be deleted	 Personal build	Status:COMPLETED
Process Build Result ID: _QEDkgX2SEeWt7sIN-jhQsg	 in folder: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20151028-1738
	Processing Build Result[UUID _QEDkgX2SEeWt7sIN-jhQsg]	 May be deleted	 Personal build	Status:COMPLETED



Deletion Candidates:
Folder should be deleted: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\C20150429-1043	 age: 183	days. Jazz SCM metadata detected!
Folder should be deleted: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20150407-1128	 age: 205	days. Jazz SCM metadata detected!
Folder should be deleted: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild\I20150407-1238	 age: 204	days. Jazz SCM metadata detected!

For the folders that contain a build.properties file the Build Output Manager checked if the build result in RTC has been deleted (i.e. while purging results).  For the build folder label I20151028-1727 build result ID _wz8NAX2QEeWt7sIN-jhQsg was identified but the build result could no longer be found in the repository and the folder was deleted (simulation only with default parameters).

The Build Output Manager detected the folder C20150429-1043 that is 183 days old but has no build.properties file. It also detected that the folder contains jazz SCM metadata. This is an indicator that it was created during a repository workspace load operation and is likely to be a build output folder (as opposed to a folder containing cute cat videos).

These folders could have been created by builds that run on a build definition or with build scripts, that don’t create the build.properties file themselves. It might be a good idea to somehow fix this situation. There is a special flags available to deal with situations like this and automatically delete these folders based on age.

If there is no indication of Jazz SCM metadata, this would also be announced. These folders are likely to be folders that have been manually created for other purposes. There is a special flags available to deal with situations like this and automatically delete these folders based on age. However this is an extreme measure and I would suggest to check first if there is not something else going on and make sure the folders in the build output root folder are only created during builds.

RTC Build Result Pruning

RTC has a basic capability to prune build results. The pruner removes unwanted build results from the repository, which ideally drives which build output folders should be deleted. See the article Automated Build Output Management Using the Plain Java Client Libraries for sone more information on that. This capability is very basic. If it seems not be satisfying see Robins blog post about creating a Custom build result pruner that implements a more anhanced strategy. Since this is also based on the Plain Java Client libraies, it would be possible to integrate it with the Build Output Manager discussed in this post.

How to Run the Build Output Manager Tool

The Build Output Manager has the following required parameter

 BuildOutputManager [repositoryURI] [userId] [password] [BuildResultRoot]

The Build Output Manager needs the repository that contains the build results. Currently it will not work with build results that are detected to be in a different repository.

The userID and the password have to be provided and the user has to have the permissions to read and modify the build results.

The Build Output Manager must be run in the context of a user that has permission to read and delete the build output folders.

Here an example for the parameters:

"https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild"

If only these parameters are provided, the tool runs in a default mode. Currently the default mode is internally set to simulation.

The following additional parameters can be added at the end of the parameter list.

  • simulation – the Build Output Manager only simulates operations and does not delete any folders and the result is reported (current default mode)
  • deleteUnreferencedBuildfolders – the Build Output Manager tries to identify build folders that contain the build.properties file; it performs the operations to handle deliverables and deletes the build result folders if they are not a deliverable and a related build result can no longer be found
  • deleteIfSCM – the Build Output Manager performs the operations specified for the mode deleteUnreferencedBuildfolders; in addition all folders that don’t contain a build.properties file but are detected to contain Jazz SCM metadata and exceed the aging limit are deleted
  • forceDelete – the Build Output Manager performs the operations specified for the mode deleteUnreferencedBuildfolders; in addition all folders that don’t contain a build.properties and exceed the aging limit are deleted

Example parameters:

"https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild" simulation
"https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild" deleteUnreferencedBuildfolders
"https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild" deleteIfSCM 
"https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild" forceDelete

An example when calling the RunManager script.

RunManager "https://clm.example.com:9443/ccm/" build build "C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\JKEBuild" deleteUnreferencedBuildfolders

You can run the Build Output Manager from chron jobs if it is deployed on the machine running the build engine or containing the build output folders. Another approach would be to use the JBE and have a specific build definition for each engine to run the tool. In any case the tool needs to be run in a user context that has the required permissions to access and delete the files and folders.

Interesting Parts of the Code

Lets look at some of the interesting parts of the code with respect to the RTC Java API. The details can be found in the article Automated Build Output Management Using the Plain Java Client Libraries.

The code below uses the build result ID from the build.properties file to

  1. Create a build result handle
  2. Query the RTC item manager for the resolved build result using the permission aware API
  3. If there is anything the user is denied to retrieve, the code assumes the result is there, but not accessible so a warning is issued and the folder is skipped
  4. If the result is not found the assumption is, that it has been purged and the build output folder is deleted
  5. If the result can be found the folder is preserved and the result is used to check if the build has been changed to a deliverable
private void manageFolders() throws TeamRepositoryException {
	.
	.
	.

		System.out.println("Process Build Result ID: " + buildResultID
				+ "\t in folder: "
				+ aBuildOutputCandidate.getAbsolutePath());
		IBuildResultHandle resultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE
				.createItemHandle(UUID.valueOf(buildResultID), null);
		ArrayList handleList = new ArrayList();
		handleList.add(resultHandle);
		IFetchResult fetchResult = aTeamRepository.itemManager()
				.fetchCompleteItemsPermissionAware(handleList,
						IItemManager.REFRESH, null);
		if (fetchResult.hasPermissionDeniedItems()) {
			// can't access but there is a build result
			System.out.println("\t " + buildResultID
					+ " access demied, check permissions!");
			continue;
		}
		if (fetchResult.hasNotFoundItems()) {
			System.out.println("\tBuild result no longer in repository: "
					+ buildResultID);
			checkDeleteFolder(aBuildOutputCandidate);
			continue;
		}
		List retrieved = fetchResult.getRetrievedItems();
		for (Iterator iterator = retrieved.iterator(); iterator.hasNext();) {
			Object result = (Object) iterator.next();
			if (result instanceof IBuildResult) {
				IBuildResult buildResult = (IBuildResult) result;
				processBuildResult(aBuildOutputCandidate, buildResult);
			}
		}
	.
	.
	.

}

The code to process the build result is shown below.

/**
 * Process the build result for additional information that might be useful.
 * For instance check if the Build Result is associated with a deliverable.
 * 
 * @param aBuildOutputCandidate
 * @param buildResult
 * @throws TeamRepositoryException
 */
private void processBuildResult(File aBuildOutputCandidate,
		IBuildResult buildResult) throws TeamRepositoryException {
	System.out.println("\tProcessing Build Result"
			+ buildResult.getItemId()
			+ "\t "
			+ (buildResult.isDeleteAllowed() ? "May be deleted"
					: "Deletion not allowed")
			+ "\t "
			+ (buildResult.isPersonalBuild() ? "Personal Build"
					: "Public Build") + "\tStatus:"
			+ buildResult.getState().toString());
	if (!buildResult.getState().equals(BuildState.COMPLETED)) {
		return; // Only handle completed builds
	}
	IWorkItemCommon workItemCommon = (IWorkItemCommon) aTeamRepository
			.getClientLibrary(IWorkItemCommon.class);
	List deliverables = workItemCommon
			.findDeliverablesByArtifact(buildResult.getItemHandle(),
					IDeliverable.FULL_PROFILE, null);
	if (null != deliverables && !deliverables.isEmpty()) {
		System.out.println("\tDETECTED DELIVERABLE: "
				+ aBuildOutputCandidate.getAbsolutePath()
				+ "\t Mark folder and schedule back up of folder!");
		addToBackUp(aBuildOutputCandidate, buildResult);
		markAsDeliverable(aBuildOutputCandidate);
	}
}

The code below deals with a build that is identified as deliverable and needs to be preserved.

/**
 * Stub for calling a backup operation for a release folder.
 * 
 * @param buildResult
 * @throws TeamRepositoryException
 * 
 */
private void addToBackUp(File aBuildOutputCandidate,
		IBuildResult buildResult) throws TeamRepositoryException {
	if (isMarkedAsDeliverable(aBuildOutputCandidate)) {
		return;
	}
	String theURL = publishToIRAM(aBuildOutputCandidate);
	IBuildResultContribution link = BuildItemFactory
			.createBuildResultContribution();
	// Optional set a category
	// link.setComponentName("Build Output Backup for Audits");
	link.setLabel("Download Artifact from IBM Rational Asset Manager");
	link.setExtendedContributionTypeId(IBuildResultContribution.LINK_EXTENDED_CONTRIBUTION_ID);
	link.setExtendedContributionProperty(
			IBuildResultContribution.PROPERTY_NAME_URL, theURL);
	ITeamBuildClient buildClient = (ITeamBuildClient) aTeamRepository
			.getClientLibrary(ITeamBuildClient.class);
	buildClient.addBuildResultContribution(
			(IBuildResultHandle) buildResult.getItemHandle(), link, null);
}

private String publishToIRAM(File aBuildOutputCandidate) {
	return "http://jazz.net/library";
}

The interesting part of this code is the part that adds a link to the artifact repository on the build result, which allows users to navigate there.

The publishing to an artifact repository in the method publishToIRAM() is not implemented, but it would obviously be possible to add the code required for the artifact repository of your choice. It should return an URL to the published artifact.

Interesting Related Posts

Summary

This example was created a long time ago, but I think it is still of interest. Keep in mind this is by no means production code. You might want to do more testing and enhance it to your needs. As always I hope this helps someone out there to get their job done more efficient.

Learning To Fly: Getting Started with the RTC Java API’s


I intend this to become the beginners guide for the RTC java API’s. The reason is, I spend far too much time on the forums trying to answer beginner questions on how to get started over and over again. I intend this post to be my link to answer these questions without having to repeat everything all over.

Where to Start?

You first have to understand what API’s are available and what you can extend. If you don’t understand these basics, you can’t decide on how to proceed. It is also a good idea to understand RTC Process Customization and what you can and cannot do.

Setting up Your Development Environment

If you determined you are interested in one of the Java API’s, you need to set up your environment for Java API development. It is extremely important to perform the steps in that post to have an environment, that has the RTC SDK installed as well as the Plain Java Client Libraries. Do the whole workshop even if you just want to use the Plain Java Client Libraries to code up some automation. This familiarizes you with the concepts of plug-ins and give you some first glimpse on the API.

Even if you intent to develop only Plain Java Client Libraries, you should use a plug-in project to develop your code, because that allows to trick the Eclipse Plugin Development Environment (PDE) into showing you the whole source code of the RTC SDK. It requires to set up your environment as explained in Setting up Rational Team Concert for API Development.

This allows you to

  • See the comments on interfaces, classes and on methods
  • Search interfaces, classes and methods even with using an asterisk if you are not sure about the names and packages
  • Search for references to interfaces, classes, methods, extension points to go looking for inspiration and example code

Search The APIGetting started with the RTC Plain Java Client Libraries

To understand how that works read the post Understanding and Using the RTC Java Client API. Especially read the sections

  • Getting Started With Developing for the Plain Java Client Libraries
  • Debugging Client Code and Finding Information about the API
  • Prepare Your Project to Allow Debugging
  • Finding Information About the API

You should carefully read the whole post Understanding and Using the RTC Java Client API, as it explains how the basic mechanisms in the API work. This is also interesting if you intent to write server extensions.

Getting started with Eclipse Client and Server Extensions

If you are not (yet) interested in this section skip to the next sections and go to “Where can I find Examples and Example Code?“.

The best place to start is following the Rational Team Concert Extensions Workshop. This provides you with a fully set up environment for debugging and guides you through all the important steps that are necessary to develop client and server extensions. You might also want to look at Bartosz Chrabskis post How to create Rational Team Concert – Advisor & Participant Extension (step by step) and the related video for creating a participant and the video for creating an advisor.

RTC provides a lot of ways to extend the Eclipse based clients and the server. The most important in general are

  • Pre-conditions which are also referred to as Advisors, because they drive the process advisor behavior of operations
  • Follow-up action which are also referred to as participants, because they participate in the process behavior of operations
  • Attribute Customization which are also referred to as providers, because most of them provide values for work item attributes; please  find examples here
  • Components which are used to group extensions

The wiki topic Team Process Developer Guide provides a great overview about what you can do and what the rules are.

On the RTC Server side it is also possible to create asynchronous tasks, that can perform operations such as event generation and mail notification. An example with code and explanation can be found in the post Due Date Notifier – an Asynchronous Task Example.

There are additional extension points available on the client and on the server that can be used. Some examples are

  • Eclipse Client UI Extension points
  • RTC Web UI Extension points

There are still more, that I haven’t even looked at yet.

The most important part is to be able to develop and debug your server extensions using Jetty as explained in the Setting up Rational Team Concert for API Development. If you can’t do that, it is just a waste of time and I would consider not trying it out at all. You also always want to have a dedicated server for extensions development, deployment testing and functional testing.

The code used in advisors and participants is very similar except that the interface used is different and the result that is returned is also different.

Advisors and participants always run in the context of the user that calls the operation. This means they can only access data the user is able to access and perform operations the user is permitted to.

In advisors it is not permitted to modify the element for which the advisor is triggered. In participants this is allowed.

Participants that modify or create elements have to perform an additional save for the objects modified. Please note that this can trigger the same or another participant to be executed. This can result in a server crash, if it causes a recursive descent for a recursion that does not stop. To prevent that, it is possible to pass additional arguments in the save that the sub-sequentially called  participant can use to prevent running into a recursion. See for example this post for how that can be used.

The Extension Points and Operation ID’s to assign advisors and participants to the specific operation to work for can be found here.

In general it is the best approach to extend the RTC Server if at all possible. The reason is that this makes deployment easier. Deployment basically has to only happen on the server and deploying on all the clients can be avoided.

Deploying Extensions

If, and only if, your extension works in the Jetty based development and debug environment, you can deploy it on a real server. If you have not successfully tried your extension on Jetty as explained in the Rational Team Concert Extensions Workshop, it does not make any sense to try deploying it on a test server, let alone on a production server.

The Rational Team Concert Extensions Workshop handles deploying extensions very manual. The post A Custom Condition to Make Attributes Required or Read-Only by Role explains how this can be made more automatic in the section Deploying the Extension.

If the extension works on Jetty, the worst that can go wrong deploying it on a real server is that the dependencies in the extension include libraries that are not available on the server.

Other issues could be missing or not satisfiable dependencies, version issues, missing provision profiles or typos in the profile or corrupt files. This would show in the log file during server start up. Check the RTC Server log (i.e. server/logs/ccm.log).

Please note, a deployment error, a typo in the provision profile or missing or corrupted files in the sites folder can lead to the server crashing / not successfully starting up. The server start up can be interrupted and the server may not be reachable. See the server log for hints what might be the problem.

Please refer to the post Is The Extension Deployed? How Can I Redeploy? for checking if the deployment actually worked and for maintaining and upgrading server extensions.

Client extensions are required if the data that it works with is only available on the client, or if the extension is needed on client and server anyway e.g. for Java based attribute customization provider.

Where can I find Examples and Example Code?

There are countless examples out there, here on Jazz.net, on Stackoverflow and on many other blogs and sites. But apparently this is not enough or too hard to find. So I will try to put guidance on how to get started here.

The SDK

As already explained you can search for code that ships with the RTC SDK. This requires a working development environment as described above and here.

This Blog

You can find examples and example code in this blog. The easiest way to do so is to search the blog. This is really easy. Type the interface, method or any other clue you search for in the search window on the top right section underneath the banner and then use the “Search” button.

Search the BlogThen go through the list of posts that appear.

Most posts have working code attached for download as well.

There is also a page with Interesting Links that I maintain and add stuff that I come across. There are many links to examples for API and other interesting sources.

The Internet

We live in the times of search engines. In the past, like 25 years ago, you had to go to a public library and try to find relevant information or books that most likely were not there. Today you can use your preferred search engine and, with very few effort, find all you can ask for.  It is of course always easier to ask someone else to “Google that for me”, but it is way better to skill up to do it yourself in these Darwin times.

A good approach in this case is to just come up with some keywords, interface or class name and run the search engine on it. In my experience some words what you are looking for and RTC usually is enough. I find the interesting information usually on the first two pages. If not, I try to change and refine my query. Examples for typical search paattern are

  • “create work item java API RTC”
  • “access work item attribute java API RTC”
  • “RTC API IIteration”

If the results on page one are just too many and not promising it is possible – for Google at least – to limit the scope of the search to specific sites.

Using Search EnginesAn example for this pattern would be

  • “create work item java API RTC site:jazz.net”

This is most useful if you know that there has to be something there or recall something was there and you can’t remember where. I find this even more effective than using the search engine available on Jazz.net, because it usually shows the information I am looking for higher in the ranking.

Good sites are

Summary

If you want to get started with the RTC Java API’s, work through this blog post and the resources linked to it. I will try to extend this post with more detailed links for specific topics in the future.

As always I hope that helps users out there.

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.

Extending the WorkItem Command Line With New Commands


If one needs a new command to be run in the WorkItem Command Line, what needs to be done? Lets explore this with the background from the post The WorkItem Command Line Explained.

As always, keep in mind, this is not production code, under development and not designed to win a beauty pageant. Testing has not been very thorough, so using this code is at your own risk.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

The New Command

The new command is supposed to help with migrating Multi Select Enumeration  attributes available until RTC 3.x to Enumeration List attributes available in RTC 4.x and later.

As explained in Workaround: Migrate from Rational Team Concert 3.X string attribute used as multi-select lists to RTC 4.X enumeration lists, in RTC 3.x these multi select lists where implemented as a string attribute, that stored the enumeration literal ID’s separated by comma. A special presentation handled input and output. Downside of this approach was, that it was hard to query these attributes and create dashboards and reports. The new way is a special list type for each enumeration.

The workaround explains how to use CSV export and import to migrate the data. This solution uses the API to do so. What this solution does not attempt so far is having a mapping that changes the data.

Related posts

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.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

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 code attached to this post in the development environment you set up in the Rational Team Concert Extensions Workshop and get your own extensions or automation working there as well.

Download

Download the latest version that includes the code from this post.

Requirements

The command is supposed to work on work items of a specific type in a project area.

  • For a source attribute (of type string), read the value, split it into enumeration literal ID’s
  • Find the enumeration literals and build a list from them
  • Store the list of enumeration literals in a target attribute
  • Perform this for one work item of the type (e.g. for testing)
  • Perform this for all work items of the type
  • Be able to tolerate errors and run nonetheless

The image below shows the source attribute as shown in RTC 4.x with a regular string presentation and the new EnumerationList attribute. The values from the source attribute have been migrated over to the target attribute.

Source and target attribute

Source and target attribute

Creating the new Command

The first step is to create a new command class. As explained in The WorkItem Command Line Explained there are some abstract classes available to be used. The AbstractWorkItemModificationCommand seems to be it, but looking closer, it is very specialized in creating or modifying a work item with a list of provided attributes and values in a parameter list. So the choice here is the AbstractTeamRepositoryCommand instead.

So the first step is to create the class MigrateWorkItemAttributeCommand in the package com.ibm.js.team.workitem.commandline. Create a public constructor as required from the abstract class and override the abstract methods. In the method getCommandName() return the name of the command. Also override the method setRequiredParameters() but make sure to add a call to super() to get the parameters it requires added. The initial step would look similar to this:

public class MigrateWorkItemAttributeCommand extends
		AbstractTeamRepositoryCommand {

	public MigrateWorkItemAttributeCommand(ParameterManager parametermanager) {
		super(parametermanager);
	}

	/***
	 * Add required parameters
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.AbstractTeamRepositoryCommand#setRequiredParameters()
	 */
	@Override
	public void setRequiredParameters() {
		super.setRequiredParameters();
	}

	/***
	 * Return the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.IWorkItemCommand#getCommandName()
	 */
	@Override
	public String getCommandName() {
		return "migrateattribute";
	}

	/***
	 * Perform the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.AbstractCommand#process()
	 */
	@Override
	public OperationResult process() throws TeamRepositoryException {
		return getResult();
	}
}

In addition to the repository URL, the user name and the password, the command needs to know the project area, and the the work item type. It also needs the parameter for the source attribute ID and the target attribute ID. Some flags like ignoreErrors might come in handy as well.

Most of this can be harvested from existing commands like the CreateWorkItemCommand.  A few new need to be added. Introducing some constants is a good idea for later. The code added to setRequiredParameters() would finally look like below. The call to super() makes sure the basic parameters for the login process are required as well.

public static final String COMMAND_MIGRATE_ENUMERATION_LIST_ATTRIBUTE = "migrateattribute";
public static final String PARAMETER_SOURCE_ATTRIBUTE_ID = "sourceAttributeID";
public static final String PARAMETER_SOURCE_ATTRIBUTE_ID_EXAMPLE = "com.acme.custom.enum.multiselect";
public static final String PARAMETER_TARGET_ATTRIBUTE_ID = "targetAttributeID";
public static final String PARAMETER_TARGET_ATTRIBUTE_ID_EXAMPLE = "com.acme.custom.enum.list";


/***
 * Add required parameters
 * 
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.AbstractTeamRepositoryCommand#setRequiredParameters()
 */
@Override
public void setRequiredParameters() {
	super.setRequiredParameters();
	// Copied from CreateWorkItemCommand
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY_EXAMPLE);
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY_EXAMPLE);
	getParameterManager().syntaxAddSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS);
	getParameterManager().syntaxAddRequiredParameter(
			PARAMETER_SOURCE_ATTRIBUTE_ID,
			PARAMETER_SOURCE_ATTRIBUTE_ID_EXAMPLE);
	getParameterManager().syntaxAddRequiredParameter(
			PARAMETER_TARGET_ATTRIBUTE_ID,
			PARAMETER_TARGET_ATTRIBUTE_ID_EXAMPLE);
}

The method getCommandName() would look like this now, with the string extracted into a constant:

	/***
	 * Return the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.IWorkItemCommand#getCommandName()
	 */
	@Override
	public String getCommandName() {
		return COMMAND_MIGRATE_ENUMERATION_LIST_ATTRIBUTE;
	}

Add The New Command to the Main Class

The new command needs to be added to the WorkItem Command Line. In the class WorkitemCommandLine add it to the method method addSupportedCommands() like shown below.

/**
 * Add the supported commands. If introducing a new command, add it here.
 * 
 * @param parameterManager
 */
private void addSupportedCommands(ParameterManager parameterManager) {
	addSupportedCommand(new PrintTypeAttributesCommand(
			new ParameterManager(parameterManager.getArguments())));
	addSupportedCommand(new CreateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
	addSupportedCommand(new UpdateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
	addSupportedCommand(new MigrateWorkItemAttributeCommand(new ParameterManager(
			parameterManager.getArguments())));
}

Now it can be used like shown below. It is also possible to start debugging it using Launches.

wcl -migrateattribute /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task sourceAttributeID=custom.medium.string targetAttributeID=custom.enumeration.list

or this

wcl -migrateattribute /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task  id=275 sourceAttributeID=custom.medium.string targetAttributeID=custom.enumeration.list

Implementing the Command Logic

Now the method process() needs to be implemented. It orchestrates the work to be done. This obviously took some iterations. Here is the final result.

/***
 * Perform the command
 * 
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.AbstractCommand#process()
 */
@Override
public OperationResult process() throws TeamRepositoryException {
	// From CreateWorkItemCommand
	// Get the parameters such as project area name and Attribute Type and
	// run the operation
	String projectAreaName = getParameterManager()
			.consumeParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY)
			.trim();
	// Find the project area
	IProjectArea projectArea = ProcessAreaUtil.findProjectArea(
			projectAreaName, getProcessClientService(), getMonitor());
	if (projectArea == null) {
		throw new WorkItemCommandLineException("Project Area not found: "
				+ projectAreaName);
	}

	String workItemTypeID = getParameterManager().consumeParameter(
			IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY)
			.trim();
	// Find the work item type
	IWorkItemType workItemType = WorkItemHelper.findWorkItemType(
			workItemTypeID, projectArea.getProjectArea(),
			getWorkItemCommon(), getMonitor());

	// Get the parameter values - The source attribute
	String sourceAttributeID = getParameterManager().consumeParameter(
			PARAMETER_SOURCE_ATTRIBUTE_ID).trim();
	// check if old attribute ID is string type
	IAttribute sourceIAttribute = getWorkItemCommon().findAttribute(
			projectArea, sourceAttributeID, getMonitor());
	if (sourceIAttribute == null) {
		throw new WorkItemCommandLineException(
				"Source Attribute not found: " + sourceAttributeID);
	}
	if (!AttributeTypes.STRING_TYPES.contains(sourceIAttribute
			.getAttributeType())) {
		throw new WorkItemCommandLineException(
				"Source Attribute is not a String type: "
						+ sourceAttributeID);
	}

	// Get the parameter values - The target attribute
	String targetAttributeID = getParameterManager().consumeParameter(
			PARAMETER_TARGET_ATTRIBUTE_ID).trim();
	// check if new attribute ID is EnumerationList
	IAttribute targetIAttribute = getWorkItemCommon().findAttribute(
			projectArea, targetAttributeID, getMonitor());
	if (targetIAttribute == null) {
		throw new WorkItemCommandLineException(
				"Target Attribute not found: " + targetAttributeID);
	}
	if (!AttributeTypes.isEnumerationListAttributeType(targetIAttribute
			.getAttributeType())) {
		throw new WorkItemCommandLineException(
				"Target Attribute is not an EnumerationList: "
						+ targetAttributeID);
	}
	if (getParameterManager().hasSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS)) {
		setIgnoreErrors();
	}
	String wiID = getParameterManager().consumeParameter(
			IWorkItemCommandLineConstants.PARAMETER_WORKITEM_ID_PROPERTY);
	if (wiID != null) {
		IWorkItem wi = WorkItemHelper.findWorkItemByID(wiID,
				IWorkItem.SMALL_PROFILE, getWorkItemCommon(), getMonitor());
		if (!wi.getWorkItemType().equals(workItemType.getIdentifier())) {
			throw new WorkItemCommandLineException(
					"Work item type mismatch: "
							+ workItemType.getIdentifier() + " specified "
							+ workItemType.getIdentifier());
		}
		migrateSingleWorkItem(wi, sourceIAttribute, targetIAttribute);
	} else {
		// Update all work items of this type.
		migrateAllWorkItems(projectArea, workItemType, sourceIAttribute,
				targetIAttribute);
	}
	// If we got here, we succeeded
	getResult().setSuccess();
	return getResult();
}

What it basically does is to get the parameters needed for this command and work with them. Keep in mind that the abstract class AbstractTeamRepositoryCommand does the login process already. So what needs to be done is to get the project area, the work item type, the source and the target attribute ID and the flags. Most of the code has been harvested in existing commands as well as in the WorkItemHelper class. The methods migrateSingleWorkItem() and migrateAllWorkItems() will be explained in a bit.

Some new fields and getters/setters where introduced in the process. We also need the separator for the literals in the original string later. The code looks as below:

public static final String SEPARATOR_ENUMERATION_LITERAL_ID_LIST = ",";

private boolean fIgnoreErrors = false;

private void setIgnoreErrors() {
	fIgnoreErrors = true;
}

private boolean isIgnoreErrors() {
	return fIgnoreErrors;
}

public MigrateWorkItemAttributeCommand(ParameterManager parametermanager) {
	super(parametermanager);
}

There was also a need to be able to access the ITeamRepository in the MigrateWorkItemAttributeCommand. I introduced a getter into the AbstractTeamRepositoryCommand class for convenience.

Note, it is always possible to get the ITeamRepository from an RTC object using the getOrigin() method and cast it to the ITeamRepository. However, it makes sense to add this to the framework, too.

Implementing the Work Item Modification

The final implementation steps are to implement the missing methods as well as add a inner class extending WorkItemOperation, to do the change to the work item. The code is shown below.

/**
 * Migrate one specific work item - for testing
 * 
 * @param wi
 * @param sourceIAttribute
 * @param targetIAttribute
 * @throws TeamRepositoryException
 */
private void migrateSingleWorkItem(IWorkItem wi,
		IAttribute sourceIAttribute, IAttribute targetIAttribute)
		throws TeamRepositoryException {
	MigrateWorkItem operation = new MigrateWorkItem("Migrate",
			IWorkItem.FULL_PROFILE, sourceIAttribute, targetIAttribute);
	performMigration((IWorkItemHandle) wi.getItemHandle(), operation);
}

/**
 * Migrate all work items of a specific type in a project area
 * 
 * @param projectArea
 * @param workItemType
 * @param sourceIAttribute
 * @param targetIAttribute
 * @throws TeamRepositoryException
 */
private void migrateAllWorkItems(IProjectArea projectArea,
		IWorkItemType workItemType, IAttribute sourceIAttribute,
		IAttribute targetIAttribute) throws TeamRepositoryException {
	// Find all work items of this type.
	// Create an Expression to find them
	IQueryableAttribute attribute = QueryableAttributes.getFactory(
			IWorkItem.ITEM_TYPE).findAttribute(projectArea,
			IWorkItem.PROJECT_AREA_PROPERTY, getAuditableCommon(),
			getMonitor());
	IQueryableAttribute type = QueryableAttributes.getFactory(
			IWorkItem.ITEM_TYPE).findAttribute(projectArea,
			IWorkItem.TYPE_PROPERTY, getAuditableCommon(), getMonitor());
	Expression inProjectArea = new AttributeExpression(attribute,
			AttributeOperation.EQUALS, projectArea);
	Expression isType = new AttributeExpression(type,
			AttributeOperation.EQUALS, workItemType.getIdentifier());
	Term typeinProjectArea = new Term(Term.Operator.AND);
	typeinProjectArea.add(inProjectArea);
	typeinProjectArea.add(isType);

	// Run the Expression
	IQueryClient queryClient = getWorkItemClient().getQueryClient();
	IQueryResult results = queryClient.getExpressionResults(
			projectArea, typeinProjectArea);
	// Override the result set limit so that we get more than 1000 items if
	// there are more
	results.setLimit(Integer.MAX_VALUE);
	MigrateWorkItem operation = new MigrateWorkItem("Migrate",
			IWorkItem.FULL_PROFILE, sourceIAttribute, targetIAttribute);
	// Run the operation for each result
	while (results.hasNext(getMonitor())) {
		IResult result = (IResult) results.next(getMonitor());
		performMigration((IWorkItemHandle) result.getItem(), operation);
	}
}

/**
 * Perform the update and
 * 
 * @param handle
 * @param operation
 * @throws WorkItemCommandLineException
 */
private void performMigration(IWorkItemHandle handle,
		MigrateWorkItem operation) throws WorkItemCommandLineException {
	String workItemID = "undefined";
	try {
		IWorkItem workItem = getAuditableCommon().resolveAuditable(
				(IWorkItemHandle) handle, IWorkItem.SMALL_PROFILE,
				getMonitor());
		workItemID = getWorkItemIDString(workItem);
		operation.run(handle, getMonitor());
		getResult().appendResultString(
				"Migrated work item " + workItemID + ".");
	} catch (TeamRepositoryException e) {
		throw new WorkItemCommandLineException(
				getResult().getResultString()
						+ "TeamRepositoryException: Work item "
						+ workItemID + " attribute not migrated. "
						+ e.getMessage(), e);
	} catch (WorkItemCommandLineException e) {
		String message = "WorkItemCommandLineException Work item " + workItemID
				+ " attribute not migrated. " + e.getMessage();
		if (!isIgnoreErrors()) {
			throw new WorkItemCommandLineException(getResult().getResultString() + message, e);
		} else {
			getResult().appendResultString(message);
		}
	}
}

The method migrateSingleWorkItem() basically runs the migration operation for a single work item. It creates the inner class MigrateWorkItem with the data it needs and then calls the method performMigration() to perform the migration. The method performMigration() calls the provided method and does all the error handling.

The method migrateAllWorkItems() basically creates an expression to get all work items of the specified type from the project area. The expression is run and performMigration() is performed on one result after the other.

Please note that the line

results.setLimit(Integer.MAX_VALUE);

makes sure that the built in query limit of the Eclipse client is overwritten to allow getting all possible results. To work it has to be exactly at the place it is.

The method performMigration() just performs the call and wraps everything up to be able to process exceptions. The information is provided in the result.

The methods above use some additional methods to get the client library IWorkItemClient to be able to perform the query expression.

/**
 * We need this client libraries to run queries
 * 
 * @return
 */
private IWorkItemClient getWorkItemClient() {
	return (IWorkItemClient) getTeamRepository().getClientLibrary(
			IWorkItemClient.class);
}

/**
 * Get the work item ID as string
 * 
 * @param workItem
 * @return
 */
private String getWorkItemIDString(IWorkItem workItem) {
	return new Integer(workItem.getId()).toString();
}

The WorkItemOperation Implementation

Finally lets look at the inner class MigrateWorkItem which extends WorkItemOperation. I use this approach in all the work item manipulation client API usage, because it conveniently handles all possible mishaps.

private class MigrateWorkItem extends WorkItemOperation {
	
	IAttribute fsourceAttribute = null;
	IAttribute fTargetAttribute = null;
	
	/**
	 * Constructor
	 * 
	 * @param The
	 *            title message for the operation
	 * @param message
	 * @param profile
	 * @param sourceAttribute
	 * @param targetAttribute
	 */
	public MigrateWorkItem(String message, ItemProfile profile,
			IAttribute sourceAttribute, IAttribute targetAttribute) {
		super(message, profile);
		fsourceAttribute = sourceAttribute;
		fTargetAttribute = targetAttribute;
	}
	
	/***
	 * This gets called if run() is called
	 * 
	 * @see com.ibm.team.workitem.client.WorkItemOperation#execute(com.ibm.team
	 *      .workitem.client.WorkItemWorkingCopy,
	 *      org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException,
			RuntimeException {
	
		IWorkItem workItem = workingCopy.getWorkItem();
		String thisItemID = getWorkItemIDString(workItem);
		if (!workItem.hasAttribute(fsourceAttribute)) {
			throw new WorkItemCommandLineException(
					"Work Item "
							+ thisItemID
							+ " Source Attribute not available - Synchronize Attributes: "
							+ fsourceAttribute.getIdentifier());
		}
		if (!workItem.hasAttribute(fTargetAttribute)) {
			throw new WorkItemCommandLineException(
					"Work Item "
							+ thisItemID
							+ " Target Attribute not available - Synchronize Attributes: "
							+ fTargetAttribute.getIdentifier());
		}
		// get the old value - a string with literals separated by a comma
		Object ovalue = workItem.getValue(fsourceAttribute);
		// compute the result values
		String sourceValues = "";
		if (null != ovalue && ovalue instanceof String) {
			sourceValues = (String) ovalue;
		}
		if (!sourceValues.equals("")) {
			String[] values = sourceValues
					.split(SEPARATOR_ENUMERATION_LITERAL_ID_LIST);
			IEnumeration enumeration = getWorkItemCommon()
					.resolveEnumeration(fTargetAttribute, monitor);

			List results = new ArrayList();
			for (String literalID : values) {
				if (literalID == "") {
					// Nothing to do
					continue;
				}
				Identifier literal = getLiteralEqualsIDString(
						enumeration, literalID);
				if (null == literal) {
					throw new WorkItemCommandLineException("Work Item "
							+ thisItemID
							+ " Target literal ID not available: "
							+ literalID + " Attribute "
							+ fTargetAttribute.getIdentifier());
				}
				results.add(literal);
			}
			// Set the value
			workItem.setValue(fTargetAttribute, results);
		}
		getResult().appendResultString("Migrated work item " + thisItemID);
	}

	/**
	 * Gets an enumeration literal for an attribute that has the specific
	 * literal ID.
	 * 
	 * @param enumeration
	 *            - the enumeration to look for
	 * @param literalIDString
	 *            - the literal ID name to look for
	 * @return the literal or null
	 * @throws TeamRepositoryException
	 */
	private Identifier getLiteralEqualsIDString(
			final IEnumeration enumeration,
			String literalIDString) throws TeamRepositoryException {
		List literals = enumeration
				.getEnumerationLiterals();
		for (Iterator iterator = literals.iterator(); iterator
				.hasNext();) {
			ILiteral iLiteral = (ILiteral) iterator.next();
			if (iLiteral.getIdentifier2().getStringIdentifier()
					.equals(literalIDString.trim())) {
				return iLiteral.getIdentifier2();
			}
		}
		return null;
	}
}

The interesting method here is execute(). It basically checks if the work item has the source attribute and the target attribute. If one of the attributes is missing, it can not run. You have to synchronize attributes first.

As last step the method gets the string value and splits the string into its values. The list of enumeration literal ID’s is then used to look up the enumeration literals. In case a literal can not be found, this is an error. If the literals are retrieved, they are put into a list and finally set as the value for the new attribute.

The method getLiteralEqualsIDString() is actually mostly harvested from the WorkItemHelper class, where it is used with the literal display name.

Additional Changes to the Framework

Since this command works very different than the others, it became apparent that it would be nice to see progress directly, if we are not working in RMI server mode. this also helps the other commands and potential future commands. To provide a better user experience

    • In WorkitemCommandLine the method isServer() has been made public so that it can be read from the framework classes

 

  • In OperationResult the method appendResultString() was changed to output the information rather than storing it in a result string, if the WorkItemCommandLine does not run in server mode

This change makes the result output much more immediate, especially for long running commands in normal mode. In RMI mode, the data is provided as is, after the call finishes. Some Observations The Parameter handling does not handle optional parameters well. It should be possible to add an optional parameter like the work item ID in this case. However, this is just a minor problem here. In addition at some point it can get so complicated that the automation for printing the command syntax won’t be easily manageable any more. Summary This post explains how to extend the WorkItem Command Line with your own commands. As always, I hope that helps someone out there.