Adding custom commands to the SCMUtils

The SCMUtils is based on a framework that I developed over time. The framework provides easy to reuse mechanisms to create custom commands that handle most of the essential requirements automatically. It handles calling a command based on a name string, the parameters the command needs and optional parameters. The command can define its own help content that is printed when either the command was called with missing parameters or if the command name is not supported. This blog post explains the steps needed.

What are the SCMUtils?

Please see SCM Utils – SCM data secure sharing, statistics and more for what they are and how to get them.

Enhancing the tool with own commands

It is easy to implement your own commands. The framework project provides abstract classes that can be used to implement own commands.

The framework allows easy implementation of new custom commands.

The class SampleCommandCmd is a simple example that can be used as a starting point. It implements all the basic capabilities that are needed.

The class AbstractCommand can be extended to a simple command that can execute your own code.

The class AbstractTeamrepositoryCommand is an abstract class that can be used to implement custom commands that interact with a Jazz application. It manages the parameter and behavior required for connecting to a Jazz server. In addition it automatically implements scenario logging. It can be extended to implement commands that work against a Jazz server.

It is not necessary to use one of the classes above. It is only required to implement the interface ICommand.

Tips and tricks for implementing custom commands

The framework comes with a sample command, that you can look to understand how the implementation works. If you want to create your own command, you should not do that in the project com.ibm.js.team.supporttools.framework. You should add it in a project area that refers to the com.ibm.js.team.supporttools.framework project. E.g. you can add the command in the project com.ibm.js.team.supporttools.scmutils.

Open the command /com.ibm.js.team.supporttools.framework.SampleCommandCmd and study the class.

Sample class part 1

The class SampleCommandCmd extends AbstractCommand, so it does not inherit logging into a jazz server. It also implements ICommand (which it inherits from AbstractCommand).

The next part is to define a logger. The framework uses Simple Logging Facade for Java (SLF4J) to provide logging. The log file will be written to disc. Use the logger to add custom logging.

The command must implement the default constructor and pass the command id to the framework. The super class implements this, so the command name is passed to the super class.

In addCommandOptions(), the class adds additional options (parameters). The first parameter in addOption() is the option/parameter name, the second defines if the option requires a value. The third parameter is the description of the option.

Sample class part 2

The command has to make sure it gets the options it needs. This is checked in the next step. The method checkParameters() is used to do just this. Test if the required options are available.

The method printSyntax() is called if there is some kind of issue with calling the SCMUtils. Implement your documentation here. The logger by default prints log level info.

Sample class part 3

The entry point for the custom code is the execute() method. This usually gets all the mandatory options and their values. The method getCmd().getOptionValue() is used to get the parameter or option value if the option has a value. The method getCmd().hasOption() is usually if the option does not have a value and is more like a flag.

Then it usually executes the payload code that does whatever the command is supposed to do. The return value should be true if the command succeeded and false otherwise.

Adding the custom command to the CommandFactory

After implementing a new command, it has to be added to the ScmSupportToolsFactory in the project com.ibm.js.team.supporttools.scmutils to make it available to be called..

The command factory

This is where the command needs to be added. See the available commands.

Add a command

Remove the comments before the put(new SampleCommandCmd()), to enable the sample command. You would add a put for your own command here. The SampleCommand is a good learning execise if you want to learn debugging.

Summary

It is really simple to add new commands, there is a set of commands already available, copy one of those if it looks similar to what you want to do.

Like always, I hope this helps someone out there.

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.

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 Work Item Attributes to Display RTC Components and other Item Data

The RTC Work Item shall guide the user to the component, where the fix needs to be done. Is it possible to implement this requirement in RTC?

This was basically a requirement that I was asked for how to implement this with RTC. I also remember it being asked in the Jazz.net forum some time ago. I remember this, because my immediate thought was, this is only going to work using customization or extending RTC. I was quite surprised when a colleague monitoring the forum came up with a simple solution. I tried it out and it is really simple. However, it is quite hard to spot how this can be done. Therefore this post summarizes how you can easily do this yourself.

If you are just starting with customizing RTC Work Items, have a look at Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management 2012 especially Lab 3-5.

This was done with RTC 4.0.5 but it should work with earlier versions of RTC 4.x.

Create a New Attribute of Type Item

First you need to create a new attribute that will contain the information you want. There are a lot of attribute types you can choose from. There are special types that allow to select RTC Data such as users, team areas and the like. All these are for data related to the work item model. There is a special type for RTC data that is not work Item data from the RTC process model. It is easy to overlook. The type can be used as single selection or as a list. When creating the Attribute use

  • Item
  • ItemList

dependent upon your needs to select one or multiple elements.

Attribute Type Item
Attribute Type Item

If you select a ItemList, the work item UI supports selecting multiple items.

Configure the Editor Presentation

Once you have added the attribute, you need to configure the editor presentations to show the attribute as usual. You select

  • The Attribute
  • The Kind of the presentation

The available presentation kinds depend on the attribute type you selected. For the type ItemList, select Item List as kind.

Configure Editor PresentationFinally you can select which kinds of items the user can select. There are several choices that match types where RTC also provides a specialized attribute type, such as WorkItem or TeamArea.

The type Item or ItemList is more flexible and allows you to select any of these kinds and additional types that are not available in special attribute types such as SCM Component.

In the ItemList, you could have various objects that are interesting in this context. For example if you want to express what is affected by a work item.

You can select

  • Let the user choose
  • A specific item type

for the item type type be selectable in the presentation.

Save your changes, to make them available in the process.

Create a Work Item

Now you can create a new work item of the type you modified and select the new attribute. The presentation allows you to choose or add an element. The Select Component Dialog provides you with search and filter mechanisms to find what you need.

Select a ComponentSummary

You can use RTC work items to express relationships of a work item to other objects using special attribute types.

You can then use these new attributes and their values to drive new automation, e.g. check if a change set is actually for the component the related work item claims it to be. I shall see, if I can publish an article about such an advisor soon.

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.