The Process Sharing API


RTC process sharing allows to manage a process in one project area and to use the process in multiple other project areas. This post explains the internal API that allows to use  this feature in automation to make operations easier.

The RTC process sharing feature allows to minimize process customization while having a common process in many project areas. This is interesting for all kinds of users, especially with a growing number of project areas while in the need of a common process.

I worked with a team, that needed to create many project areas to contain the information for each project in one project area while limiting access to other project areas. So the idea was to automate the process of creating project areas, which required to be able to use an API. I found hints to this API provided by Sam on Jazz.net. I consolidated the code into an example that I could share with others.

You can find more about how this feature works in the following articles on Jazz.net

The code in this post is client API.

This blog post uses internal API which can be changed at any time.

This blog post uses internal API which can be changed at any time. If the Internal API changes, the code published here will no longer work.

Warning, some of the code uses internal API that might change in the future. If the Internal API changes, the code published here will no longer work.

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

Just Starting With Extending RTC?

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

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

The Code

To provide at least some wrapper around the internal code calls, I created a utility class ProcessProviderUtil. This utility wraps the API to set a project area to provide the process to be used by other project areas, as well as the API to set a project area to use another project area’s process and provides methods to use this API. The methods just delegate to the internal API call at this time.

You can download the code from here.

This is how the code looks like:

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2014. 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.team.js.process.client;

import com.ibm.team.process.common.IProjectArea;
import com.ibm.team.process.common.IProjectAreaHandle;
import com.ibm.team.process.internal.common.ProjectArea;

/**
 * Skeleton for a plain-Java client, see
 * https://jazz.net/wiki/bin/view/Main/ProgrammaticWorkItemCreation.
 */
public class ProcessProviderUtil {

	/**
	 * Set a project area to provide its process for sharing
	 * 
	 * @param projectArea
	 */
	public static void setIsProcessProvider(IProjectArea projectArea, boolean isProcessProvider) {
		((ProjectArea) projectArea).setIsProcessProvider(isProcessProvider);
	}

	/**
	 * Test if the projectArea Provides its process
	 * 
	 * @param projectArea
	 * 
	 *            Determines whether other project areas can get their process
	 *            from this project area.
	 * @return true if this project area allows other project areas
	 *         to use its process, false if not.
	 */
	public static boolean isProcessProvider(IProjectArea projectArea) {
		return ((ProjectArea) projectArea).isProcessProvider();
	}

	/**
	 * Share the process on another project area
	 * 
	 * @param usingProjectArea
	 * @param providingProcessArea
	 */
	public static void setProcessProvider(IProjectArea usingProjectArea,
			IProjectArea providingProcessArea) {
		((ProjectArea) usingProjectArea)
				.setProcessProvider(providingProcessArea);
	}

	/**
	 * Get the project area from which we use the process
	 * 
	 * @param projectArea
	 * 
	 * @return {@link IProjectAreaHandle} or null if this project
	 *         area does not rely on another project area for its process.
	 */
	public static IProjectAreaHandle getProcessProvider(IProjectArea projectArea) {
		return ((ProjectArea) projectArea).getProcessProvider();
	}
}

Example Usage

The methods are so simple that there is no reason to explain them in detail. The code below shows how they can be used. The example needs two project areas to be provided. One project area e.g. based on the Scrum process template. A second project area based on the ‘Unconfigured Process’. The User that runs this automation needs to be member of the JazzAdmins repository group.

The code to set the first project area to provide its process to be used by another projects looks like below.

IProcessItemService processItemService = (IProcessItemService) teamRepository.getClientLibrary(IProcessItemService.class);

System.out.println("\nProcessing project area: "+ providingProjectAreaName);
URI uri = URI.create(providingProjectAreaName.replaceAll(" ", "%20"));
IProjectArea providingProjectArea = (IProjectArea) processItemService.findProcessArea(uri, null, monitor);
if (providingProjectArea == null) {
	System.out.println("....Error: Project area not found: ");
	return false;
}
providingProjectArea = (IProjectArea) providingProjectArea.getWorkingCopy();

System.out.println("...Project area provides its process for usage:"
		+ new Boolean(ProcessProviderUtil
				.isProcessProvider(providingProjectArea)).toString());
// Set to provide the process for sharing
System.out.println("...Set project area process to provided for usage");
ProcessProviderUtil.setIsProcessProvider(providingProjectArea,true);
System.out.println("...Project area provides its process to other project areas:"
		+ new Boolean(ProcessProviderUtil
			.isProcessProvider(providingProjectArea))
			.toString());

// Save project area that uses the provided process
processItemService.save(providingProjectArea, monitor);

The code basically finds the project area somehow (by its name). It then gets a working copy to be able to modify it. It then provides if the project area is already providing its process to others. Regardless of this information it sets the project area to provide its process for usage. It again prints if the process is provided. Finally the changes are saved.

The code to use the process of a project area that provides it looks as below.

System.out.println("\nProcessing project area: " + usingProjectAreaName);
uri = URI.create(usingProjectAreaName.replaceAll(" ", "%20"));
IProjectArea usingProjectArea = (IProjectArea) processItemService.findProcessArea(uri, null, monitor);
if (usingProjectArea == null) {
	System.out.println("....Error: Project area not found: ");
	return false;
}
usingProjectArea = (IProjectArea) usingProjectArea.getWorkingCopy();

System.out.println("...Project area provides its process to other project areas:"
			+ new Boolean(ProcessProviderUtil
				.isProcessProvider(usingProjectArea))
				.toString());
Boolean used = new Boolean(false);
IProjectAreaHandle handle = ProcessProviderUtil
	.getProcessProvider(usingProjectArea);
if (null != handle) {
	used = new Boolean(true);
}
System.out.println("...Project area uses a process provided by another project area:" + used.toString());
// Set to share process
System.out.println("...set to use a process provided by another project area");
ProcessProviderUtil.setProcessProvider(usingProjectArea,providingProjectArea);
used = new Boolean(false);
handle = ProcessProviderUtil.getProcessProvider(usingProjectArea);
if (null != handle) {
	used = new Boolean(true);
}
System.out.println("...Project area uses a process provided by another project area:" + used.toString());
// Save project area that uses the provided process
processItemService.save(usingProjectArea, monitor);

As in the part above the code first finds the project area. It then detects if the project area provides its process for informational reasons. Then it checks if it uses the process of another project area. It then sets the project area to use the process of the first project area, prints a check of the fact and saves the process change.

Please note, there is no real error handling here. e.g. if the project area provides its process and can actually share another process is not tested.

In the inverse case, if providing the process is supposed to be disabled, the save would fail if project areas still share the provided process.

Summary
The code is experimental. I have tested it against a test server on Tomcat and Derby. It is by no means production ready and can be enhanced for various scenarios. However, as always, I hope the code is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

Advertisements

Deploying Templates and Creating Projects Using the Plain Java Clients Library


How can the Rational Team Concert Extensions workshop be changed to be easier to use with newer versions of Rational Team Concert?

This is a question that I was contemplating about since I picked up the maintenance of the workshop. The issue with the workshop is the requirement to import database content using the repotools.

Why is that step required? The main reason is, that the workshop provides a project with SCM data that you use to perform the various labs. It also provides the Launch files needed for launching the Jetty server and the Eclipse clients.  This avoids a lot of work in typing source code and importing projects.

However, it causes a lot of issues.

  •  This used to work with various versions of RTC in the past but since 4.0 it seems to be tied to the version that was used to export the databases. So in order to run the workshop with a newer versions, it is neccessary to set it up with the RTC version it was created with and then upgrade the repository to the desired version.
  • By importing the repository, the public URI is set.
  • When setting the repository up, it is necessary to decide which applications to set up and register. If using a different install method and not installing all applications the JTS will show broken application registrations.
  • It is necessary to run a custom setup, due to the usage of the user ID ADMIN

There are some other issues with this approach that add unnecessary complexity to a workshop that is already challenging enough (for example the usage of user ADMIN and how to provide that user with a license). These can hopefully be addressed somehow.

Solution Approach

The only feasible approach that I could come up with was to provide some automation to perform the steps I have to do to set up the repository to export it for a new workshop version. The automation would run against a RTC Server that has a finalized set up of any kind (Express or Custom) with or without existing projects, create and provide the required data.

The minimal set of steps required would be to:

  1. Deploy the required Template for a Project Area, if it is not already deployed.
  2. Create the required Project Area for the Extension Workshop.
  3. Set the required User Membership and Roles for the Project Area.
  4. Set up the required Stream, Components and the Repository workspace
  5. Share the SCM Data to the Components, deliver it to the Stream and create the required Baselines. This needs to be done multiple times for the workshop code adding new files and modifying files that already exist.
  6. Set the repository workspace components to the baselines needed to start the workshop.

Recently I found some time to explore how this can be achieved. I made considerable process and I think there is a solution to this challenge that I might be able to publish. While I will try to find time to finalize the work and get approval to publish the results, I wanted to publish some of the lessons learned from trying to create the solution.

The solution is described in:

  1. This post
  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. 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.

Preparing the Project

This post will show how the required project can be created. Some of the code used here is from Snippet 3 of the plain Java Client Libraries and from code already published in this blog. The code that follows is wrapped into the code I usually use for my Plain Java Client Library examples, derived from the Programmatic Work Item Creation Wiki entry and the Plain Java Client Libraries Snippets.

The main() method starts the Team Platform, creates the new class object and passes the arguments on to perform the work. I have discussed what this code does so often in other posts, please refer to them if you have questions.

public static void main(String[] args) throws Exception {
	boolean result;
	System.out.println("Starting Team Platform");
	if (!TeamPlatform.isStarted())
		TeamPlatform.startup();
	try {
		System.out.println("Team Platform started");
		ServerSetup setupExtDevServer = new ServerSetup();
		result = setupExtDevServer.run(args);
	} catch (TeamRepositoryException x) {
		x.printStackTrace();
		result = false;
	} finally {
		TeamPlatform.shutdown();
	}
	if (!result)
		System.exit(1);
}

In the run() method the work to do is coordinated and performed. The method checks if the required parameters are available. Then it logs into the repository with the given user name and password. This code is presented at the end of this post, as it is just refactored out and is simply the code I am always using in this blog. The user has to have the JazzAdmin repository role.

In the next step the automation deploys the required process template. It then tries to find if the project area exists and creates a new one if not. It adds the required user with role to the project area. The following steps will be explained in later posts.

private boolean run(String[] args) throws Exception {
	if (args.length != 3) {
		System.out.println("Usage: ServerSetup repositoryURI userId password");
		return false;
	}

	String repositoryURI = args[0];
	String userId = args[1];
	String password = args[2];

	IProgressMonitor monitor = new NullProgressMonitor();
	ITeamRepository teamRepository = logIntoTeamRepository(repositoryURI,
				userId, password, monitor);

	IProcessDefinition scrumProcess = findOrDeployTemplate(teamRepository,
				"scrum2.process.ibm.com", monitor);
	IProjectArea area = findOrCreateProjectArea(teamRepository,
				"RTC Extension Workshop", scrumProcess, monitor);
	addUsersAndRoles(teamRepository, area, monitor);
.
.
.
.
.

In the deployTempate() method the automation tries to find the required process template and tries to deploy it, if it was not already deployed.

private IProcessDefinition findOrDeployTemplate(ITeamRepository teamRepository,
		String processID, IProgressMonitor monitor)
		throws TeamRepositoryException {
	System.out.println("Trying to find or deploy template: " + processID);
	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	IProcessDefinition[] definitions = null;
	// Find the process definition
	IProcessDefinition processDefinition = processItemService
			.findProcessDefinition(processID, null, monitor);
	if (processDefinition == null) {
		// Create the definition if it does not exist.
		definitions = processItemService
				.deployPredefinedProcessDefinitions(
						new String[] { processID }, monitor);
		System.out.println("Template deployed...");
		if (definitions.length != 0) {
			processDefinition = definitions[0];
		} else {
			throw new TeamRepositoryException("Process template "
					+ processID + " does not exist.");
		}
	}
	System.out.println("Template found...");
	return processDefinition;
}

The main task is now to find the project area if it exists and to create a new project area in case it is not available.

private IProjectArea findOrCreateProjectArea(
		ITeamRepository teamRepository, String projectName,
		IProcessDefinition processDefinition, IProgressMonitor monitor)
		throws TeamRepositoryException {

	// Create Project Area based on Scrum
	System.out.println("Trying to find or create Project Area: " + projectName);

	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	IProjectArea area = null;
	List areas = service.findAllProjectAreas(
			IProcessClientService.ALL_PROPERTIES, monitor);
	for (Object anArea : areas) {
		if (anArea instanceof IProjectArea) {
			IProjectArea foundArea = (IProjectArea) anArea;
			if (foundArea.getName().equals(projectName)) {
				area = foundArea;
				System.out.println("Project Area found: " + projectName);
				return area;
			}
		}
	}

	// Could not find the the project area, create one
	if (area == null) {
		System.out.println("Trying to create Project Area: " + projectName);
		area = service.createProjectArea();
		area.setName(projectName);
		area.setProcessDefinition(processDefinition);
		IDescription description = area.getDescription();
		description.setSummary("Project to be used running the RTC Extension Workshop");
		area = (IProjectArea) service.save(area, monitor);
		area = (IProjectArea) service.getMutableCopy(area);
		service.initialize(area, monitor);
		System.out.println("Created and initialized Project Area: " + projectName);
	}
	return area;
}

Now that the project area is available make sure to add the required user with role ID “ScrumMaster”. The method addUsersAndRoles() does this.It first tries to find the role. Once found it adds the currently logged in user, the user that runs this automation, to the administrators section of the project and to the members section with the role obtained.

private IProjectArea addUsersAndRoles(ITeamRepository teamRepository,
		IProjectArea area, IProgressMonitor monitor)
		throws TeamRepositoryException {

	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	area = (IProjectArea) service.getMutableCopy(area);
	System.out.println("Trying to add members with roles: "
			+ "ScrumMaster" + " to Project Area" + area.getName());

	// IProjectArea wArea = (IProjectArea)service.getMutableCopy(area);
	IContributor member = teamRepository.loggedInContributor();

	IRole role = getRole(area, "ScrumMaster", monitor);
	area.addAdministrator(member);
	area.addMember(member);
	area.addRoleAssignments(member, new IRole[] { role });
	IProcessItem[] items = service.save(new IProcessItem[] { area },
				monitor);
	System.out.println("Users with Roles added to " + area.getName());
	return area;
}

To get the role the getRole() method tries to discover the roles in the process that have a matching ID and returns it, if it was found. Please note that IRole objects have no access to the role name. they have just the ID. If you need the role name you need to get the IRole2 interface from the IRole object. The code that does this is commented out below.

	private IRole getRole(IProcessArea area, String roleID,
			IProgressMonitor monitor) throws TeamRepositoryException {
		ITeamRepository repo = (ITeamRepository) area.getOrigin();
		IProcessItemService service = (IProcessItemService) repo
				.getClientLibrary(IProcessItemService.class);
		IClientProcess clientProcess = service.getClientProcess(area, monitor);
		IRole[] availableRoles = clientProcess.getRoles(area, monitor);
		for (int i = 0; i < availableRoles.length; i++) {
			IRole role = availableRoles[i];
			// IRole2 role2 = (IRole2)role;
			// role2.getRoleName();
			if (role.getId().equalsIgnoreCase(roleID))
				return role;
		}
		throw new IllegalArgumentException("Couldn't find roles");
	}

The code left out is for logging into the repository, for completeness it is presented below.

private ITeamRepository logIntoTeamRepository(String repositoryURI,
		String userId, String password, IProgressMonitor monitor)
		throws TeamRepositoryException {
	System.out.println("Trying to log into repository: " + repositoryURI);
	ITeamRepository teamRepository = TeamPlatform
		.getTeamRepositoryService().getTeamRepository(repositoryURI);
	teamRepository.registerLoginHandler(new LoginHandler(userId, password));
	teamRepository.login(monitor);
	System.out.println("Login succeeded.");
	return teamRepository;
}

/**
 * Login Handler implementation
 *
 */
private static class LoginHandler implements ILoginHandler, ILoginInfo {

	private String fUserId;
	private String fPassword;

	private LoginHandler(String userId, String password) {
		fUserId = userId;
		fPassword = password;
	}

	public String getUserId() {
		return fUserId;
	}

	public String getPassword() {
		return fPassword;
	}

	public ILoginInfo challenge(ITeamRepository repository) {
		return this;
	}
}

Summary

The code in this post basically shows how to prepare a project for later usage. It does the steps requires such as deploying a process template in case it is not yet available. It finds or creates the project area and it makes sure to add the user required as administrator and member with the required role.

Now that the project is prepared, the next steps will be all the required preparation in the Projects SCM to be able to upload the code. This is going to be presented in a later post.

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.