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

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

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

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

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

Just starting with extending RTC?

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

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

Advertisement

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.

Using RTC to Work with DevOps Services and With Bluemix

I recently had a look into Bluemix and how to use it with Eclipse to develop cloud applications. The blog post also mentions that there is an integration to DevOps Services that enables to use work items for planning. It also allows to use GIT or Jazz SCM to manage the source code.

Recently I had a look into how that works and I would like to share here what I learned. This post assumes you have performed the first steps to setup your environment following the Getting started With Bluemix post already.

Please note: DevOps Services as well as Bluemix are evolving quickly, adopting for new needs as they arise and what is described here might not be the only possible solution or outdated if you look at it later. It might be a good idea to check with the current documentation of DevOps Services.

Creating a new DevOps Services Project

The first step to get started with DevOps Services, is to create a new project to manage work items and the source code.

After signing into DevOps Services, using the IBM ID created for Bluemix, it is possible to create a project. The screen shot below shows the information needed to do this. Basically it has to have a name, how the source code should be managed, how the project template should look like. There is also a choice to integrate Bluemix with the project.

For the following part of this blog I am assuming that Jazz SCM was chosen.

New DevOps Services ProjectFor the Bluemix integration provide the organization – basically the Bluemix ID and the password.

Clicking the Create button creates a RTC project (which is working under the hood of DevOps Services).

On the overview page, you can select to edit the code, track and plan work with work items, and configure and manage build and deployment.

Configure Eclipse ProjectThere is also a “Configure Eclipse Client” choice available. Clicking at it provides the information of an invitation that can be used in the RTC Eclipse client to set up the connection.

Configure Eclipse ClientJust copy the invitation data and paste it into the ‘Accept Invitation’ action, provide the password and the connection is created. We will look into the next steps done with Eclipse later.

Enabling the Bluemix Integration

Switch to the Build & Deploy section using the button. This page allows to configure the build and deploy mechanism, request a new build and deploy and view the deployment status.

Configure Deploy and BuildThe Build and Deploy has basically two settings. Click Simple to select the Simple setting which are adequate for now (this means I haven’t been able to use the advanced settings). Then click the configure button.

Configure DeploymentThis basically defines the structure needed to deploy an app.

The integration expects the manifest.yml in the root folder in the jazz SCM system. Since there currently is no example code, the first builds&deploys will probably fail.

Jazz SCM in the Project Web UI

Switching to the Edit Code page allows to access the SCM information.

Please note: I had issues with seeing the stream information, versioned files and other data with the latest Version of the Firefox Browser ESR (31.2.0).

Chrome worked for me, so I would suggest to use that browser. It is unclear why, because other users apparently don’t have that problem. It might as well be one of these weird effects we ave to put up with in a browser-based world.

The project creation dialog created a Stream, a repository workspace and a component already. The names are based on the name of the project.

You can browse the repository workspace and create files and folders in the Orion editor in the web UI and deliver your changes to the stream to be deployed.

My task was doing this with the Eclipse client, so there I went first.

Jazz SCM in the Eclipse Client

There is a description for this step that I could find here in the documentation. However, I had problems with performing them. This might be different today, however, if you run into anything, it might have similar reasons.

At this point the assumption is that the invitation from DevOps Services has been used to create a repository connection and the client is logged into the project.

As a first step, a new repository workspace is needed. The easiest way to create one is to find the stream in the Team Artifacts view and create the repository workspace from that. This creates the repository workspace and sets the default and current flow back to the stream. Tip: Name the repository workspace e.g. putting ‘Eclipse’ into the workspace name. This is to not confuse this workspace with the one used by the Web UI in the Orion editor. The reason is that repository workspaces are not designed to support one instance to be loaded and modified multiple times in different places (streams are designed for this).

Next step would be to load the repository workspace. Before attempting this, keep in mind that the Build&Deploy step assumes the manifest.yml file to be in the root folder. To achieve that using the Eclipse client and RTC Jazz SCM, there is only one option: Load the component as root folder as shown below. Trying this however, failed for me the first time around. The reason for that is that the default name of the component is derived from the project name and has a pipe ‘|’ symbol in the name. This is not allowed as name in a file or folder on the filesystem (Windows at least). Best approach is to rename the component to some useful mane. At least replace the pipe symbol by a valid one, for example a dash.

After this has been done the component can be loaded.

Load Repo Workspace ComponentIn the second step of the load wizard select the component to be loaded and press finish.

Select Load Repo Workspace Component As FolderWhile loading the data to disk, the RTC Eclipse client creates an artificial project file to mark this folder as an Eclipse project. dependent on the scenarios one wants to perform later, one might or might not want this file to be checked into version control. If one would like to have Eclipse projects on a deeper level, the file could get into the way.

Since the file is always created if the data is loaded this way, I added the file to the Jazz ignore file.

It is now possible to add the files for the application. For example the files from the example from Bluemix from my last post can be used as shown below. This would for example look like below:

Example File Structure

Why this structure? The project.json file is from configuring the project. It contains the property for the project name. I left it there.

The manifest.yml file is needed for the boilerplate/runtime our sample is using. It need to be in the root folder. It is specific to how Bluemix builds and deploys. In the example above I basically moved the original the manifest.yml from the enclosed eclipse project rsjazz01 into the root folder. Then I changed the path to pint into my Eclipse project/folder rsjazz01.  The content is changed to reflect the path to the Node.js project in the sub folder rsjazz01.

Manifest FileIf the path set above, would be just the root folder, the package.json file would be required also in the root folder. As it is above the file is needed in the sub folder.

The way it is now, would allow to load the repository workspace to find the rsjazz01 folder as node.js project and do local debugging on it.

Working with the Code

Once the general structure is set up, it is possible to edit the code in the Web UI as well as in the Eclipse client. Once you deliver the code to the stream it gets automatically built and deployed. Delivery would usually require a work item connected to the code change for traceability.

Build And DeployThe application is also accessible for testing and, of course monitoring in Bluemix.

Pro and Con’s

Looking at this post and the Bluemix post, there are obviously several valid approaches. The approach described here allows to have one application developed with one DevOps Services RTC project and have a continuous build and deploy for free.

The approach described in the Bluemix post, would allow to use Eclipse to work on several projects and actually manage the work and code in one or more DevOps Services RTC projects, as best fits. If I want to manage multiple applications in one RTC project, the automatic build and deployment would not be available. That, however can easily be scripted into continuous integration build scripts as well.

Summary

I hope this and the Bluemix post, provide you with some insight about how the DevOps Services and Bluemix work together and how you can user Eclipse and RTC to develop your applications.

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

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

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

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

Other posts in this blog about using the SCM API

Extracting an Archive Into Jazz SCM Using the Plain Java Client Libraries

How can one share or extract data from an archive file directly into Jazz SCM system? This is just one question that has a lot of potential. If one could do that, any source for information could also be used to extract data and store it in Jazz SCM. Basically it would be possible to write your own SCM migration tool.

This post shows some of the Client API used to do this kind of operations.

Evan’s SCM Lounge blog post Getting your stuff – using the RTC SDK to zip a repository workspace explains the reverse direction, in case you are interested. His post Committing content to RTC SCM with the SDK is also a great explanation of the Jazz SCM API.

As described in the articles

  1. Deploying Templates and Creating Projects using the Plain Java Clients Library
  2. Managing Workspaces, Streams and Components using the Plain Java Client Libraries
  3. Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries
  4. This post

I want to automatically set up a project, manage the streams and components and seed the SCM system with SCM data.

The last post explains how the SCM operations on the repository workspace and stream work. Specifically, how to deliver outgoing change sets from a repository workspace to a stream, baseline the state of a component in the repository workspace, deliver the baseline to the stream and set a component of the repository workspace to a specific baseline.

This post explains how to

  • Extract data from an archive file
  • Find or create files and folders in the Jazz SCM system
  • Compare the content of files in the Jazz SCM with content in another source
  • Check in/commit changes to files and folders into the Jazz SCM

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

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

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.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client API.

The ArchiveToSCMExtractor Class

The extraction process is wrapped into the class ArchiveToSCMExtractor. The code below shows the imports, fields and the basic constructor. The Archive file is expected to be in a format that is created when compressing a folder or exporting projects from an Eclipse client as archive files: folders at root level that contain files and folders.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 * 
 * ArchiveToSCMExtractor
 *
 * 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.rtcext.serversetup;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

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

import com.ibm.team.filesystem.client.FileSystemCore;
import com.ibm.team.filesystem.client.IFileContentManager;
import com.ibm.team.filesystem.common.FileLineDelimiter;
import com.ibm.team.filesystem.common.IFileContent;
import com.ibm.team.filesystem.common.IFileItem;
import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.scm.client.IConfiguration;
import com.ibm.team.scm.client.IWorkspaceConnection;
import com.ibm.team.scm.client.content.util.VersionedContentManagerByteArrayInputStreamPovider;
import com.ibm.team.scm.common.IChangeSetHandle;
import com.ibm.team.scm.common.IComponentHandle;
import com.ibm.team.scm.common.IFolder;
import com.ibm.team.scm.common.IFolderHandle;
import com.ibm.team.scm.common.IVersionable;
import com.ibm.team.scm.common.IVersionableHandle;

/**
 * Extracts data from a compressed archive file into the RTC Jazz SCM.
 * The data is extracted directly into the component, provided in the call.
 * The assumption is, that the data in the archive is inside of one or more 
 * folders, which can be loaded with the component.
 * 
 * @see com.ibm.team.repository.service.tests.migration.ZipUtils
 * 
 */
public class ArchiveToSCMExtractor {

	// The ZipInputStream
	private ZipInputStream fZipInStream = null;
	// The team repository
	private ITeamRepository fTeamRepository = null;
	// The workspace connection
	private IWorkspaceConnection fWorkspace = null;
	// The change set
	private IChangeSetHandle fChangeSet = null;
	// The configuration to access the SCM data
	private IConfiguration fConfiguration=null;
	// The progress monitor we are using
	private IProgressMonitor fMonitor=new NullProgressMonitor();
	/**
	 * Simple constructor
	 * 
	 */
	public ArchiveToSCMExtractor() {
		super();
	}

Some of the data used during the extraction process is kept in fields. This makes the method calls simple. The constructor in this example is really simple. This could be enhanced if you have other needs.

The required data is passed in the public method extractFileToComponent(), which is the entry point, available to start the extraction process. This method is described below.

The method extractFileToComponent() requires the archive file name, the ITeamRepository, the workspace connection representing the repository workspace, the component that the data will be sent to, a comment for the change set that will contain the changes and the monitor.

Note, the ITeamRepository could be gathered from the component using getOrigin() or from the workspace connection getting the owner and then getting its origin.

The method stores the information passed in the fields, so that other methods can consume the data. It creates a new change set that is used to contain all changes created during the process. Then it creates a new ZipInputStream for the file represented by the archive. If all succeeds it starts the extraction process by calling the method extract() and returns its value.

/**
 * Extract the archive. This is basically the entry point 
 * for the extraction.
 * 
 * @param zipFile
 * @param teamRepository
 * @param targetWorkspace
 * @param component
 * @param changeSetComment
 * @param monitor
 * @return
 * @throws Exception
 */
public boolean extractFileToComponent(String archiveFileName,
		ITeamRepository teamRepository, IWorkspaceConnection targetWorkspace,
		IComponentHandle component, String changeSetComment,
		IProgressMonitor monitor) throws Exception {

	fMonitor=monitor;
	fTeamRepository = teamRepository;
	fWorkspace = targetWorkspace;
	fChangeSet = fWorkspace.createChangeSet(component, changeSetComment,
			true, monitor);
	fConfiguration = fWorkspace.configuration(component);
	File archiveFile = new File(archiveFileName);
	System.out.println("Extract: " + archiveFile.getPath());
	try {
		FileInputStream fileInputStream = new FileInputStream(archiveFile);
		fZipInStream = new ZipInputStream(fileInputStream);
		try {
			return extract();
		} finally {
			fileInputStream.close();
		}
	} catch (Exception e) {
		System.out.println("Extract Exception" + e.getMessage());
		e.printStackTrace();
		return false;
	}
}

The code for extract() is presented below. The method basically iterates all entries in the ZipInputStream.

For each of the entries it checks if this is a directory. If this is the case it uses the method findOrCreateFolderWithParents() to find the directory folder in the Jazz SCM system which also creates the desired folder, including missing parent folders that don’t yet exist.

In case the entry represents a file the work of extracting the file is delegated to the method extractFile().

Please note that archive files don’t necessarily contain all directory folders, some only store the files and the folder structure needs to be recreated from the file path. This method should handle also archives that provide folder information, even if the folders are empty.

/**
 * Extract the content of an archive to a component.
 * This assumes that the archive contains folders on top level. 
 * These folders can act as projects when loading,
 * 
 * @return
 * @throws IOException
 * @throws TeamRepositoryException
 */
private boolean extract() throws IOException,
		TeamRepositoryException {
	ZipEntry entry = fZipInStream.getNextEntry();
	boolean result = true;
	while (entry != null) {
		File targetEntry = new File(entry.toString());
		try {
			if (entry.isDirectory()) {
				System.out.println("Extracting Folder: "
						+ targetEntry.getPath());
				findOrCreateFolderWithParents(targetEntry);
			} else {
				System.out.print("Extracting File: "
						+ targetEntry.getPath());
				extractFile(targetEntry, entry);
				System.out.println(" OK");
			}
		} catch (Exception e) {
			System.out.println("Extract Exception: " + e.getMessage());
			e.printStackTrace();
		result = false;
		} finally {
			fZipInStream.closeEntry();
		}
		entry = fZipInStream.getNextEntry();
	}
	return result;
}

The method extractFile() below works as follows. First, it tries to find the parent folder for the file in the Jazz SCM system, using findOrCreateFolderWithParents().

The method findOrCreateFolderWithParents() tries to find the parent folder of the file in the Jazz SCM system and creates the folder and all its parents, if necessary. The IFolder returned represents the directory folder object in the SCM system, that is supposed to contain the file.

Now the method uses getFile() to try to find the file represented as a IFileItem object  entry contained in the parent IFolder in the Jazz SCM system. If the file does not exists the method returns null and createFileItem() is used to create the IFileItem in the parent IFolder. Now the IFileItem should be available and we can access its content, regardless if it already existed or was just created.

In the next step the file content is copied from the zip entry into a ByteArrayOutputStream. This step is necessary, because the VersionedContentManagerByteArrayInputStreamPovider closes the stream provided with the data. This is usually not a problem, but in our case we need the ZipInputStream to stay open to process the next entries. It might be possible to write your own VersionedContentManagerStreamProvider that does not close the file. I tried to do that but had issues with certain files. I am not sure what the reason was, maybe some issue with converting the content. So I decided to use an existing provider instead.

The next step is to get a IFileContentManager that is needed to compare the content of the file we just copied with the content in the stream. This interface is used to create IFileContent that contains the data of the ZipEntry. This process requires to provide the file encoding as well as the line delimiter used in the content. In our case we want to only handle text files and can pick the right values easily. In more complex scenarios with files with different encoding and file delimiters, it would be necessary to determine the values somehow. One strategy would be using the file extension. The VersionedContentManagerByteArrayInputStreamPovider is used to convert the data we copied before and provide it for storage.

The next step is to compare the content of the file in the Jazz SCM and the content from the archive file we just found or created. If the contents are different, it is necessary to modify the object in the Jazz SCM system.

In case the file existed before it is necessary to get a working copy of the file to do so. After retrieving the working copy the content is set to the new value and the change is committed to the workspace. If a new file was created, there was no content. The new content is always set and therefore the file creation is also committed.

After committing the change, or doing nothing, if there was no change, this method is done.

/**
 * Extract a file from a ZipEntry to the Jazz SCM system. Currently only
 * Text/UTL8 files are supported. 
 * 
 * Commit the file if there are changes because
 * the file did not exist or there are changes in the new content compared
 * to the existing file.
 * 
 * @param targetFile
 * @param zipEntry
 * @throws FileNotFoundException
 * @throws IOException
 * @throws TeamRepositoryException
 * @throws InterruptedException
 */
private void extractFile(File targetFile, ZipEntry zipEntry)
		throws FileNotFoundException, IOException, TeamRepositoryException,
		InterruptedException {
	IFolder parentFolder = findOrCreateFolderWithParents(
			targetFile.getParentFile());

	IFileItem aFile = getFile(targetFile, parentFolder);
	if (aFile == null) {
		aFile = createFileItem(targetFile.getName(), zipEntry, parentFolder);
		System.out.print(" ... Created");
	}

	ByteArrayOutputStream contents = copyFileData(fZipInStream);
	try {
		IFileContentManager contentManager = FileSystemCore
				.getContentManager(fTeamRepository);
		IFileContent storedzipContent = contentManager.storeContent(
				IFileContent.ENCODING_UTF_8,
				FileLineDelimiter.LINE_DELIMITER_PLATFORM,
				new VersionedContentManagerByteArrayInputStreamPovider(
						contents.toByteArray()), null, fMonitor);

		// Compare the files. If there is a difference, set the new 
		// content and commit the change
		if (!storedzipContent.sameContent(aFile.getContent())) 
		{
			IFileItem fileWorkingCopy = (IFileItem) aFile.getWorkingCopy();
			fileWorkingCopy.setContent(storedzipContent);
			fWorkspace.commit(fChangeSet, Collections
					.singletonList(fWorkspace.configurationOpFactory()
							.save(fileWorkingCopy)), fMonitor);
			System.out.print(" ... Content");
		}
	} finally {
		contents.close();
	}
}

How are folders found and created? This is dealt with in the method findOrCreateFolderWithParents().

If there is no parent, the folder is in the root and the method can return the completeRootFolder() of the component. Otherwise the method tries to recursively discover the parent of the current folder. Once a valid parent is found, the method uses getFolder() to find the IFolder relative to the parent found. If none can be found, a new folder is created with createFolder(). The folder is then returned.

The recursive call allows to find a folder, the root parent folder of the component , and then find the other folders in the hierarchy. The recursive descent simply makes iterating the path easier to use. Another approach would be to split the path and iterate the segments and find the next deeper level beginning with the root. Remembering a stack of folders would be another optimization option. Evan’s post Committing content to RTC SCM with the SDK shows another way to resolve a path, that I had overlooked, using IConfiguration.resolvePath() might be an option too.

/**
 * Find a folder in the Jazz SCM system. Create the folder if required. Also
 * finds and, if necessary, creates the required parent folders. Could be
 * optimized by keeping the folder stack.
 * 
 * @param folder
 * @return
 * @throws TeamRepositoryException
 */
private IFolder findOrCreateFolderWithParents(File folder)
		throws TeamRepositoryException {

	IFolder parent = null;
	String folderName = folder.getName();
	String parentName = folder.getParent();
	if (parentName == null) {
		parent = fConfiguration.completeRootFolder(fMonitor);
	} else {
		// Recursively find the parent folders
		parent = findOrCreateFolderWithParents(new File(parentName));
	}
	IFolder found = getFolder(folderName, parent);
	if (found == null) {
		found = createFolder(folderName, parent);
	}
	return found;
}

The method getFolder() is used to check if a folder with a specific name has an entry in the found parent folder. The method basically uses getVersionable() to get a IVersionable item with the name and the parent. It checks if the item is an IFolder and returns the item if that is the case or null, if not.

/**
 * Find a folder in an existing parent folder.
 * 
 * @param folderName
 * @param parentFolder
 * @return
 * @throws TeamRepositoryException
 */
@SuppressWarnings("unchecked")
private IFolder getFolder(String folderName, IFolderHandle parentFolder)
		throws TeamRepositoryException {

	IVersionable foundItem = getVersionable(folderName, parentFolder);
	if(null!=foundItem){
		if (foundItem instanceof IFolder) {
			return (IFolder) foundItem;
		}
	}
	return null;
}

The method getVersionable() basically gets all the child entries of the parent first. The returned map has the names of the entries as well as the IVersionableHandles.  The method tries to get the handle of an item using the name. If there is a handle, it gets the complete item and then returns this item. Otherwise null is returned to show that no qualified element was found.

/**
 * Gets a versionable with a specific name from a parent folder.
 * 
 * @param name
 * @param parentFolder
 * @return
 * @throws TeamRepositoryException
 */
private IVersionable getVersionable(String name, IFolderHandle parentFolder)
		throws TeamRepositoryException {
	// get all the child entries
	@SuppressWarnings("unchecked")
	Map<String, IVersionableHandle> handles = fConfiguration.childEntries(
			parentFolder, fMonitor);
	// try to find an entry with the name
	IVersionableHandle foundHandle = handles.get(name);
	if(null!=foundHandle){
		return fConfiguration.fetchCompleteItem(foundHandle, fMonitor);
	}
	return null;
}

If no folder with the correct name can be found, a new folder must be created. This is basically done in the code of the method createFolder() shown below. The method basically creates a new IFolder, sets the parent folder and the name and then commits it to the change set into the Jazz SCM.

/**
 * Create a folder and commit it to SCM.
 * 
 * @param folderName
 * @param parent
 * @return
 * @throws TeamRepositoryException
 */
private IFolder createFolder(String folderName, IFolder parent)
		throws TeamRepositoryException {
	IFolder newFolder = (IFolder) IFolder.ITEM_TYPE.createItem();
	newFolder.setParent(parent);
	newFolder.setName(folderName);
	fWorkspace.commit(fChangeSet, Collections.singletonList(fWorkspace
			.configurationOpFactory().save(newFolder)), fMonitor);
	return newFolder;
}

Similar to finding the parent folder, it is necessary to find existing files in extractFile(). The method getFile() does this analogue to the method getFolder() above, again using getVersionable().

/**
 * Tries to find a IFileItem node in a given IFolder. Returns the IFileItem
 * found or null if none was found.
 * 
 * @param file
 * @param parentFolder
 * @return
 * @throws TeamRepositoryException
 */
private IFileItem getFile(File file, IFolderHandle parentFolder)
		throws TeamRepositoryException {
	IVersionable foundItem = getVersionable(file.getName(), parentFolder);
	if(null!=foundItem){
		if (foundItem instanceof IFileItem) {
			return (IFileItem) foundItem;
		}
	}
	return null;
}

Again, if a matching file can not be found in the SCM system, it is necessary to create one. This is done in createFileItem(). This works similar to the method createFolder() above. A new Item is created and the necessary properties are set. Other than in createFolder() the new item is not committed to Jazz SCM, because this is done after setting the file content. If you try to commit the file without setting the content, the operation would fail.

/**
 * Tries to create a IFileItem node in a given IFolder. Returns the
 * IFileItem.
 * 
 * @param string
 * @param zipEntry
 * @param parentFolder
 * 
 * @return
 * @throws TeamRepositoryException 
 */
private IFileItem createFileItem(String name, ZipEntry zipEntry,
		IFolder parentFolder) throws TeamRepositoryException {
	IFileItem aFile = (IFileItem) IFileItem.ITEM_TYPE.createItem();
	aFile.setParent(parentFolder);
	aFile.setName(name);
	aFile.setContentType(IFileItem.CONTENT_TYPE_TEXT);
	aFile.setFileTimestamp(new Date(zipEntry.getTime()));
	return aFile;
}

The last thing missing is the method to copy the file data so that the content can be created for the SCM compare and store operations. The method copyFileData() below does just this. It copies the data from one stream to another. This prevents our ZipInputStream from being closed as well as provides us with the interface needed to get the data for storing it.

/**
 * Copy the data from an input stream to an output stream. This is done to
 * avoid the Jazz SCM closing the stream that contains the original data.
 * 
 * @param zipInStream
 * @return
 * @throws IOException
 */
private ByteArrayOutputStream copyFileData(InputStream zipInStream)
		throws IOException {
	ByteArrayOutputStream contents = new ByteArrayOutputStream();
	byte[] buf = new byte[2048];
	int read;
	while ((read = zipInStream.read(buf)) != -1) {
		contents.write(buf, 0, read);
	}
	contents.flush();
	return contents;
}

A closing bracket finalizes the class.

The class is used in the method exctractToComponentBaseline() from the post Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries as shown below.

	// Extract the archive file to the component in the repository
	// workspace
	System.out.println("Extracting...");
	ArchiveToSCMExtractor extract = new ArchiveToSCMExtractor();
	if(!extract.extractFileToComponent(archiveFileName, teamRepository,
			repoWorkspace, component, changeSetComment, monitor)){
		throw new Exception("Exception extracting " + archiveFileName);
	}

The code above could be enhanced by managing a stack of the folders that have already been found, because the archive files usually do a recursive descent through the file system, this would reduce the number of lookups for the parent folder considerably. The code to manage this would be too complex for the post and for the scenario this code was designed, the performance is acceptable,

Summary

The code in this post basically shows how to work with data against repository workspaces. In the example the data is taken from an archive using ZipInputStream. However, this is just the example and the data could come from a file system, some application, like another SCM system or anywhere else.

The posts Managing Workspaces, Streams and Components Using the Plain Java Client Libraries and Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries basically provide you with all you need to know to write your own migration tooling, too.

As always, I hope that sharing this code helps users out there, with a need to use the API’s to do their work more efficient.

Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries

How does delivering change sets from a repository workspace work and how can I create baselines and deliver them?

This post shows some of the Client API used to do this kind of operations.

As described in the articles

  1. Deploying Templates and Creating Projects using the Plain Java Clients Library
  2. Managing Workspaces, Streams and Components using the Plain Java Client Libraries
  3. This post
  4. Extracting an Archive Into Jazz SCM Using the Plain Java Client Libraries

I want to automatically set up a project, manage the streams and components and seed the SCM system with SCM data.

The last posts show how to create and manage the project area and how to manage the components, streams and repository workspaces. What is left is to extract some kind of archive file and share the files with a component in a repository workspace using Jazz SCM. Then it is necessary to do some SCM operations on the repository workspace.

This post explains how the SCM operations on the repository workspace and stream work. Specifically, how to

  1. Deliver outgoing change sets from a repository workspace to a stream
  2. Baseline the state of a component, in the repository workspace
  3. Deliver the baseline to the stream
  4. Set a component of the repository workspace to a specific baseline

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

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

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.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client API.

Deliver Change Sets and Baselines

Continuing in the run method of Deploying Templates and Creating Projects using the Plain Java Clients Library we now want to extract some data from an archive file, add the code Jazz SCM and manage the changes and baselines.

This happens in the code below. The code only shows changes to one component. The same mechanism can be used for other components however and we grab the component we want from the map components we created in the last post.

As you can see the code below basically delegates the work to another method exctractToComponentBaseline(). For the first step it remembers the baseline that was created. It uses this baseline at the end, to set the current baseline for the component in the repository workspace to the first baseline created. This allows the user to load the repository workspace and begin with the first version of the projects. Later the user can accept incoming baselines, that are already delivered to the stream, to discover the code changes made for the next labs.

public static void main(String[] args) throws Exception {
.
.
.
.

	IBaselineConnection lab2Baseline = exctractToComponentBaseline(
			teamRepository, workspace, extensionStream,
			components.get("Component 2"),
			"./LabCode/Lab2Code.zip", "Share Projects - Lab 2 Code",
			"Lab 2 Code", "Lab 2 Code", monitor);

	exctractToComponentBaseline(teamRepository, workspace, extensionStream,
			components.get("Component 2"),
			"./LabCode/Lab3Code.zip", "Share Projects - Lab 3 Code",
			"Lab 3 Code", "Lab 3 Code", monitor);

	exctractToComponentBaseline(teamRepository, workspace, extensionStream,
			components.get("Component 2"), 
			"./LabCode/Lab4Code.zip", "Share Projects - Lab 4 Code",
			"Lab 4 Code", "Lab 4 Code", monitor);

	exctractToComponentBaseline(teamRepository, workspace, extensionStream,
			components.get("Component 2"),
			"./LabCode/Lab5Code.zip", "Share Projects - Lab 5 Code",
			"Lab 5 Code", "Lab 5 Code", monitor);

	setActiveBaseline(workspace,
			components.get("Component 2"),
			lab2Baseline, monitor);
	return result;
}

The interesting code is used in exctractToComponentBaseline() below. The first step is to extract an archive file into a component in the repository workspace. The extraction process creates a change set with all the folder and file creations and changes. How that works is going to be discussed in the final post of this series to keep the tension up, and because it is quite a lot of code.

The next lines of code look quite innocent, but I had a lot of trouble to get to the point that it was working for me.

In the first step after extracting the archive, the code uses the IWorkspaceConnection.compareTo() API to compare the repository workspace to the target stream. It uses the flag WorkspaceComparisonFlags.CHANGE_SET_COMPARISON_ONLY to only compare the change stets. The 3rd parameter could be used to exclude components on the workspace from the compare operation. This is not used in this example and an Collections.EMPTY_LIST is passed instead. The effort to create the exclusion list would increase the code complexity but does not really save that much time in this example. If this is used with a lot of components with a lot of files, it would be worth spending the effort.

The result of the compare operation is a IChangeHistorySyncReport which is used in the next step to deliver the change sets.

To deliver the changes the code uses the IWorkspaceConnection.deliver() API of the repository workspace. The call provides the deliver target stream, the IChangeHistorySyncReport from the compare, an empty list of baselines and the outgoing change sets from the IChangeHistorySyncReport.

This operation also adds the target stream (workspace connection) as a flow target. You can access the flow targets using IWorkspaceConnection.getFlowTable() and use this information to get a target in other scenarios.

The next step is to create a new baseline on the repository workspace. This is done using the IWorkspaceConnection.createBaseline() API call. This call can be used with exactly on component, which is what we want in this example. The name of the baseline and a comment for the baseline are also provided in the call.

If we wanted to create a snapshot IWorkspaceConnection.createBaselineSet() would have been the AI of our choice. This creates a baseline set across several components (and can exclude components).

The next steps are similar to the delivery of the change sets. First, do a compare() of the repository connection and the target, then do a deliver() with the change set sync report and the outgoing baselines and change sets from the sync report.

It would have been possible to do the baseline creation and the deliver in one step, in our case. I choose to keep it separate, because it shows that you have to provide outgoing change sets as well as outgoing baselines from the sync report to be able to deliver a baseline. I contemplated over many IllegalArgumentExceptions to get to this insight.

private IBaselineConnection exctractToComponentBaseline(
		ITeamRepository teamRepository, 
		IWorkspaceConnection repoWorkspace,
		IWorkspaceConnection targetStream,
		IComponentHandle component, 
		String archiveFileName,
		String changeSetComment, 
		String baselineName,
		String baselineComment, 
		IProgressMonitor monitor) throws Exception, TeamRepositoryException {

	// Extract the archive file to the component in the repository workspace
	System.out.println("Extracting...");
	ArchiveToSCMExtractor extract = new ArchiveToSCMExtractor();
	extract.extractFileToComponent(archiveFileName, teamRepository, 
			repoWorkspace, component, changeSetComment, monitor);

	// Compare the repository workspace with the stream to find the changes
	// Deliver the change sets
	System.out.println("Comparing Change Sets...");
	IChangeHistorySyncReport changeSetSync = repoWorkspace.compareTo(
			targetStream,
			WorkspaceComparisonFlags.CHANGE_SET_COMPARISON_ONLY,
			Collections.EMPTY_LIST, monitor);
	System.out.println("Deliver Change Sets...");
	repoWorkspace.deliver(targetStream, changeSetSync,
				Collections.EMPTY_LIST,
				changeSetSync.outgoingChangeSets(component), monitor);

	// Create a baseline and compare the repository workspace with the
	// stream to find the changes and deliver the baselines
	System.out.println("Create Baseline...");
	IBaselineConnection baseline = repoWorkspace.createBaseline(
			component, baselineName, baselineComment, monitor);
	System.out.println("Comparing Baselines...");
	IChangeHistorySyncReport baselineSync = repoWorkspace.compareTo(
			targetStream,
			WorkspaceComparisonFlags.INCLUDE_BASELINE_INFO,
			Collections.EMPTY_LIST, monitor);

	// Deliver the baselines
	System.out.println("Deliver Baselines...");
	repoWorkspace.deliver(targetStream, baselineSync,
			baselineSync.outgoingBaselines(component),
			baselineSync.outgoingChangeSets(component), monitor);
	System.out.println("Extracting successful...");

	return baseline;
}

After extracting, sharing, creating baselines and delivering to setup the scenario right, the repository workspace needs to be set to a specific older baseline. This is done in setActiveBaseline().

The interesting part here is that it is necessary to provide a list of component operations (implementing IComponentOp). The component operations can be created using the IFlowNodeConnection.componentOpFactory() API which is inherited by IWorkspaceConnection.

The code replaces the baseline by an older one, created earlier in the process. The replaceComponent() operation takes the component, the baseline connection and a parameter that allows to calculate detailed item update reports. We don’t need this, because we don’t want to actually do anything with the baseline in our case.

private void setActiveBaseline(IWorkspaceConnection workspace,
		IComponentHandle component, IBaselineConnection lab2Baseline,
		IProgressMonitor monitor) throws TeamRepositoryException {
	workspace.applyComponentOperations(Collections.singletonList(workspace
			.componentOpFactory().replaceComponent(component, 
					lab2Baseline, false)), monitor);
}

Summary

The code in this post basically shows how to work with change sets and baselines, to manage workspace connections, deliver change sets and baselines and to replace a component with a specific baseline.

The last step needed is to understand how it is possible to extract an archive (or any other data source) into Jazz SCM. This is described in the post Extracting an Archive Into Jazz SCM Using the Plain Java Client Libraries.

As always, I hope that sharing this code helps users out there, with a need to use the API’s to do their work more efficient.

Managing Workspaces, Streams and Components Using the Plain Java Client Libraries

How can I manage the streams, repository workspaces and components in a RTC project area using the Plain Java Clients Library?

As described in the last post I want to automatically set up a project, manage the streams and components and seed the SCM system with SCM data. The last post shows how to create and manage the project area.

This post will talk about managing the components and the stream and repository workspaces.

The related posts are:

  1. Deploying Templates and Creating Projects using the Plain Java Clients Library
  2. This post
  3. Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries
  4. Extracting an Archive Into Jazz SCM Using the Plain Java Client Libraries

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

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

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.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client API.

Managing Components, Streams and Workspaces

Continuing in the run method of Deploying Templates and Creating Projects using the Plain Java Clients Library we now want to create some required components and then set up the stream and repository workspace with these components.

The requirement is to have three components and to have them on the repository workspace and the stream. If there is a default component, we want to rename it to what we need.

The code below coordinates that. First it creates a list of components we want. the method manageComponents() takes care of renaming or creating the required components and provides with a map we can use later to get the one we want. Then, the code tries to find and rename or create a stream that will later be used, and add the required components.

The method manageWorkspaces() does the same for a repository workspace. After this has been performed the whole SCM structure is set up.

.
public static void main(String[] args) throws Exception {
.
.
.
.	List<String> requiredComponents = new ArrayList<String>();
	requiredComponents.add("Component 1");
	requiredComponents.add("Component 2");
	requiredComponents.add("Component 3");
	HashMap<String, IComponentHandle> components = manageComponents(
			teamRepository, area, requiredComponents, monitor);

	IWorkspaceConnection myStream = manageStream(teamRepository,
			area, "Stream Name", "Stream Description", components, monitor);
	if (extensionStream == null) {
		System.out.println("Could not create or retrieve a stream.");
		return false;
	}
	IWorkspaceConnection workspace = manageWorkspace(teamRepository, area,
			myStream, "Workspace name", "Workspace Description", components,
			monitor);
	if (workspace == null) {
		System.out.println("Could not create or retrieve a workspace.");
		return false;
	}
.
.
.
.
.
}

Manage Components

The method manageComponents() creates a map with the components that exist in the project area. Using the list of required components, it looks if there is a component with the correct name already. If there isn’t it tries to find any component that is already available and rename it. If there is no component for reuse, a new component is created.

private HashMap<String, IComponentHandle> manageComponents(
		ITeamRepository teamRepository, IProjectArea area, List requiredComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {

	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	HashMap<String, IComponentHandle< allComponents = getComponentMap(wm, monitor);

	HashMap<String, IComponentHandle> components = new HashMap<String, IComponentHandle>();
	// process all required components.
	// Look up if we have the component already.
	// If not, grab an existing component we have found and reuse it by
	// renaming.
	// If there is none, create a new component.
	Set componentsRemaining = allComponents.keySet();
	Iterator remainingComponentsIterator = componentsRemaining
			.iterator();
	for (String requiredComponent : requiredComponents) {
		IComponentHandle required = allComponents.get(requiredComponent);
		if (required != null) {
			// Reuse Component
			components.put(requiredComponent, required);
		} else {
			// We don't have it yet.
			if (remainingComponentsIterator.hasNext()) {
				String compName = remainingComponentsIterator.next();
				IComponentHandle renameMe = allComponents.get(compName);
				// Create Component
				IComponentHandle component = renameOrCreateComponent(
						teamRepository, wm, area, renameMe,
						requiredComponent, monitor);
				extComponents.put(requiredComponent, component);
			} else {
				// Create Component
				IComponentHandle component = renameOrCreateComponent(
						teamRepository, wm, area, null, requiredComponent,
						monitor);
				components.put(requiredComponent, component);
			}
		}
	}
	return components;
}

The method getComponentMap() that gets all available components and places the handles into a map looks as below. It uses the IWorkspaceManager to get all component names. Then it uses IComponentSearchCriteria with the retrieved names to look up the handle for each component. Currently the situation that components have the same name is ignored, only a warning is issued. It would be possible to rename components with the same name.

Both name and handle are stored in a map for later usage and the map is returned.

private HashMap<String, IComponentHandle> getComponentMap(
		IWorkspaceManager wm, IProgressMonitor monitor)
		throws TeamRepositoryException {
	HashMap<String, IComponentHandle> allComponents = new HashMap<String, IComponentHandle>();
	// These are the components I need

	// Try to find all components
	Set<String> components = wm.findAllComponentNames(monitor);
	for (String compName : components) {
		IComponentSearchCriteria criteria = IComponentSearchCriteria.FACTORY
				.newInstance();
		criteria.setExactName(compName);
		List<IComponentHandle> found = wm.findComponents(criteria,
				Integer.MAX_VALUE, monitor);

		if (found.size() > 1) {
			System.out.println("Ambiguous Component Names");
		}

		for (IComponentHandle iComponentHandle : found) {
			allComponents.put(compName, iComponentHandle);
		}
	}
	return allComponents;
}

The code for renameOrCreateComponent() to create or rename a component is shown below. Again IWorkspaceManager is used to do the work.

If the component handle passed is not null , then there is an existing component, and the name of this component is changed.

If the parameter for the component handle is null a new component is created and named as desired. For learning purposes the component is created as private component of the automation user first.

In any case the ownership of the component is set to the project area.

private IComponentHandle renameOrCreateComponent(
		ITeamRepository teamRepository, IWorkspaceManager wm,
		IProjectArea area, IComponentHandle componentHandle,
		String componentName, IProgressMonitor monitor)
		throws TeamRepositoryException {
	if (componentHandle == null) {
		// Rename component
		componentHandle = wm.createComponent(componentName,
				teamRepository.loggedInContributor(), monitor);
		wm.setComponentOwner(componentHandle, area, monitor);
	} else {
		// Rename component
		wm.renameComponent(componentHandle, componentName, monitor);
		wm.setComponentOwner(componentHandle, area, monitor);
	}
	return componentHandle;
}

Now we have the components that we need and they are returned in a convenient way in a map for easier access.

Manage Streams

We have the components that we need. Now we need a stream and the stream should have the components. This is managed in the method manageStream().

The method tries to find or create a IWorkspaceConnection (a Stream in this case). The IWorkspaceConnection is the abstraction of a repository workspace or a stream.

The code uses the method findAndRenameConnection() which is explained later. The code tries to fist find a WorkspaceConnection (a stream or workspace) by name. If that does not work it tries to find any workspace.  If there is one found, it gets renamed, otherwise a new one is created.

This method also removes all components from the IWorkspcaceConnection and adds the required components.

private IWorkspaceConnection manageStream(ITeamRepository teamRepository,
		IProjectArea area, String streamName, String description,
		HashMap<String, IComponentHandle>components,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceConnection connection = findAndRenameConnection(
			teamRepository, IWorkspaceSearchCriteria.STREAMS, streamName,
			description, monitor);
	if (connection == null) {
		IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
		connection = wm.createStream(area, streamName, description, monitor);
	}
	// Remove all components
	removeAllCompoentsFormWorkspaceConnection(connection, monitor);
	addCompoentsToWorkspaceConnection(connection, components, monitor);
	return connection;
}

The method findAndRenameConnection() as shown below finds a workspace or stream, selectable by kind with a certain name if given. Again, the IWorkspaceManager is the main interface used. The method tries to find the connection by name and kind first. If that does not succeed. It uses findConnectionByName() to find a connection by name and kind,if it already exists.

The code could be enhanced to delete any additional workspaces that are not needed.

private IWorkspaceConnection findAndRenameConnection(
		ITeamRepository teamRepository, int kind, String name,
		String description, IProgressMonitor monitor)
		throws TeamRepositoryException {
	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	// Find the exact name if available use that.
	List<IWorkspaceHandle> workspaces = findConnectionByName(teamRepository,
			name, kind, monitor);
	IWorkspaceHandle firstFound = null;
	IWorkspaceConnection connection = null;
	if (!workspaces.isEmpty()) {
		firstFound = workspaces.get(0);
		connection = wm.getWorkspaceConnection(firstFound, monitor);
	} else {
		// Find an existing stream or workspace (providing a null name) and rename it
		workspaces = findConnectionByName(teamRepository, null, kind,
				monitor);
		for (IWorkspaceHandle workspaceHandle : workspaces) {
			IWorkspace ws = (IWorkspace) teamRepository.itemManager()
					.fetchCompleteItem(workspaceHandle,
					IItemManager.DEFAULT, monitor);
			if (firstFound == null) {
				firstFound = workspaceHandle;
			} else {
				// wm.deleteWorkspace(workspaceHandle, monitor);
				System.out.println("Should be deleted.... ");
			}
		}
		if (firstFound != null) {
			connection = wm.getWorkspaceConnection(firstFound, monitor);
			System.out.println("Renaming: " + connection.getName());
			connection.setName(name, monitor);
			connection.setDescription(description, monitor);
		}
	}
	return connection;
}

The method findConnectionByName() finds a workspace or stream, selectable by kind with a certain name. If the name is not specified, it just look for any object of the kind. The method uses IWorkspaceSearchCriteria to create the criteria to search for.

private List findConnectionByName(
		ITeamRepository teamRepository, String name, int kind,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	IWorkspaceSearchCriteria criteria = IWorkspaceSearchCriteria.FACTORY
			.newInstance().setKind(kind);
	if (name != null) {
		criteria.setExactName(name);
	}
	List<IWorkspaceHandle>workspaces= wm.findWorkspaces(criteria,
			Integer.MAX_VALUE, monitor);
	return workspaces;
}

The code that is missing is to remove and add components to the stream.

The code of the method removeAllCompoentsFormWorkspaceConnection() below removes all components from a workspace connection, which can be a stream or repository workspace. It uses an IComponentOp retrieved from the IComponentOpFactory to remove the components. Please note, that the Plain Java Client Library snippets currently refer to the deprecated API IWorkspaceConnection.addComponent(). The code below is the way the API works since 4.x.

private void removeAllCompoentsFormWorkspaceConnection(
		IWorkspaceConnection workspaceConnection, IProgressMonitor monitor)
		throws TeamRepositoryException {
	// Remove all components
	List wsComponents = workspaceConnection.getComponents();
	for (Object comp : wsComponents) {
		IComponentHandle cHandle = (IComponentHandle) comp;
		workspaceConnection.applyComponentOperations(Collections
				.singletonList(workspaceConnection.componentOpFactory()
						.removeComponent(cHandle, false)), true, monitor);
	}
}

The code below shows the method addCompoentsToWorkspaceConnection() which adds the components from our component map to the workspace connection. Again, an IComponentOp retrieved from the IComponentOpFactory is used.

private IWorkspaceConnection addCompoentsToWorkspaceConnection(
		IWorkspaceConnection workspaceConnection,
		HashMap<String, IComponentHandle> extComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {

	// Add new components
	Collection components = extComponents.values();
	for (Object comp : components) {
		IComponentHandle cHandle = (IComponentHandle) comp;
		workspaceConnection.applyComponentOperations(Collections
				.singletonList(workspaceConnection.componentOpFactory()
						.addComponent(cHandle, false)), true, monitor);
	}
	return workspaceConnection;
}

Manage Workspaces

The last method to describe is manageWorkspace(). This method does exactly what manageStream() does for a stream, but only for a repository workspace. It tries to find a repository workspace. If it finds one it renames it. Otherwise it creates a new workspace. Then it makes sure the correct components are in the workspace. It uses the same helper methods described above to do its task.

private IWorkspaceConnection manageWorkspace(
		ITeamRepository teamRepository, IProjectArea area,
		IWorkspaceConnection extensionStream, String workspaceName, String description,
		HashMap<String, IComponentHandle> extComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceConnection connection = findAndRenameConnection(
			teamRepository, IWorkspaceSearchCriteria.WORKSPACES,
			workspaceName, description,
			monitor);
	if (connection == null) {
		IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
		connection = wm.createWorkspace(
				teamRepository.loggedInContributor(), workspaceName,
				description, monitor);	
	}
	removeAllCompoentsFormWorkspaceConnection(connection, monitor);
	addCompoentsToWorkspaceConnection(connection, extComponents, monitor);
	return connection;
}

Summary

The code in this post basically shows how to manage connections, streams and workspaces to prepare Jazz SCM for later usage. Please be aware that, so far, there is no connection between the workspace and the stream. The Flow Target will be set later, once we begin delivering from the workspace to the stream.

Please note, there might be other ways of dealing with this, especially getting components from a stream and the methods used here might not apply for all use case, especially reusing components,streams and workspaces. You can always pick the parts you need.

Now that the project is prepared with the required SCM, the next post will show how it is possible to upload code from compressed files into the components, share and baseline the code.

As always, I hope that sharing this code helps users out there, with a need to use the API’s to do their work more efficient.