Creating Plans With the Plain Java Client API


Recently a customer approached me with the question how to automate creating plans. The customer had tried to get this done and got stuck. I was pretty convinced I had never tried this. However, I looked into the automation examples I had created over the years and was quite surprised to find an example I created some years back.

As always I thought it might be a good idea to share the code with the community.

Why is this interesting?

Wouldn’t it be nice if you could automate the process of managing timelines as much as possible?

I imagine having some code that:

  • Adds releases and iterations to timelines
    • Automatically setting start and end dates
    • Create ID’s, iteration types and names as expected in a naming convention
  • Creates all the standard plans you need to a new iteration for
    • The project area
    • Each team area
    • Name the plans as expected in a naming convention
  • Extends iterations (if you have to) and aligns the start and end dates of the iterations that come after

I think that would very beneficial and save a lot of time. Ultimately I’d like to have an example. Even bits and pieces could be interesting e.g. creating plans that are missing automatically.

I recently had to manually do this for 12 plans and it was no fun.

*Update*

Please be aware that the code below uses classes that are not shipped in the plain java client libraries. They are part of the client SDK.

The dependencies

import com.ibm.team.apt.common.IIterationPlanRecord;
import com.ibm.team.apt.internal.common.rcp.IIterationPlanService;
import com.ibm.team.apt.internal.common.rcp.dto.DTO_IterationPlanSaveResult;
import com.ibm.team.apt.internal.common.wiki.IWikiPage;

require the library com.ibm.team.apt.common  from the SDK. You can either copy it from the SDK and add it to the classpath or you add the SDK to your classpath.

Please also note that beginning with RTC 6.x the Eclipse client does no longer support browsing plans. So it is unclear if some of the SDK classes mentioned above would be removed from the RTC Client SDK over time.

Please note that the classes in com.ibm.js.team.api.workitem.common.utils are not shown in the code below, but they are part of the download.

Warning License, Further Reading

The code in this post is client API.

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

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!

As always, please note, If you just get started with extending Rational Team Concert, or create API based automation, start reading this and the linked posts to get some guidance on how to set up your environment. Then I would suggest to read the article Extending Rational Team Concert 3.x and follow the Rational Team Concert 4.0 Extensions Workshop at least through the setup and Lab 1. This provides you with a development environment and with a lot of example code and information that is essential to get started. You should be able to use the following code in this environment and get your own extension working.

Just Starting With Extending RTC?

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

You should be able to use the code attached to this post in the development environment you set up in the Rational Team Concert Extensions Workshop and get your own extensions or automation working there as well.

Download the Code

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

How The Code Works

As always lets look at how the code works a bit to understand it and be able to reuse it.

The code is, as most of my code examples based on the code that comes with the snippets in the Plain Java Libraries and the code from examples on Jazz.net.

The code comes in two Eclipse projects. Both are Plug In Development projects, because that allows me to use the Eclipse PDE to see, search and debug with the existing RTC SDK as described in Setting up Rational Team Concert for API Development and Understanding and Using the RTC Java Client API. It can later be run like any other java class.

It is two projects, because the are several helper classes, that I reuse across other examples and I get tired copying the classes around. You can move them into your project if you like. I will take them under SCM and re-use them across solutions in the future, enhancing them over time as well.

The main class is the usual structure that connects to the TeamPlatform and passes the parameters to the method run().

CreatePlan requires the following parameters:

CreatePlan [repositoryURI] [userId] [password] [planName] [planTypeID] [ownigProcessAreaName] [planIterationName]

It needs a repository URI, a user ID and password to log in and do anything. The user must have the required permissions to perform the operation.

The minimal information needed to configure a plan is:

PlanConfiguration

  • A name for the plan.
  • The Plan Type ID
  • The project or team area that is going to be the owner of the plan
  • The iteration that is set for the plan.

An Example:

CreatePlan https://clm.example.com:9443/ccm/ ralph ralph "Ralph's Plan" "com.ibm.team.apt.plantype.crossProject" "JKE Banking (Change Management)/Energy Efficiency Matters" "Main Development/Release 1.0/Sprint 3"

The plan type ID can be found in the process configuration:

PlanIDsThe owning process area name is constructed as path created from the area names beginning with the Project area down to the nested process area we are interested in, separated by ‘/’. If the plan is owned by the project area take only that name. Use ” if the names have spaces.

Example:

“JKE Banking (Change Management)/Energy Efficiency Matters”

From the Team Organization:

PlanConfigurationTeamOrganizationThe iteration path is constructed from the name of the timeline and the parent iterations down to the iteration we are interested in, separated by ‘/’. Use ” if the names have spaces. You can also use the iteration ID’s with the same structure.

For example:

“Main Development/Release 1.0/Sprint 3”

From the Team Artifacts view:

PlanConfigurationIteration

The method run() gets the string representation of the required parameters. It tries to connect to the RTC server. Then it tries to find the owning process area using a small utility. It then tries to find the plan iteration.

If it can find those, it calls a method that performs the plan creation.

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

	if (args.length != 7) {
		System.out
				.println("Usage: CreatePlan [repositoryURI] [userId] [password] [planName] [planTypeID] [ownigProcessAreaName] [planIterationName]");
		return false;
	}

	String repositoryURI = args[0];
	String userId = args[1];
	String password = args[2];
	String planName = args[3];
	String planTypeID = args[4];
	String ownigProcessAreaName = args[5];
	String planIterationName = args[6];
		
		
	IProgressMonitor monitor = new NullProgressMonitor();
	ITeamRepository teamRepository = TeamPlatform
			.getTeamRepositoryService().getTeamRepository(repositoryURI);
	teamRepository.registerLoginHandler(new LoginHandler(userId, password));
	teamRepository.login(monitor);

	System.out.println("Logged in as: "
			+ teamRepository.loggedInContributor().getName());

	IProcessClientService processClient = (IProcessClientService) teamRepository
			.getClientLibrary(IProcessClientService.class);

	// Find the project or team area that is going to be the owner of the plan
	IProcessArea ownigProcessArea = ProcessAreaUtil.findProcessArea(ownigProcessAreaName, processClient, monitor);
	if (ownigProcessArea == null) {
		System.out.println("Process area " + ownigProcessAreaName + " not found.");
		return false;
	}
		
	// Find the plan iteration in the development line 
	List path = Arrays.asList(planIterationName.split("/"));
	DevelopmentLineHelper deveLineHelper= new DevelopmentLineHelper(teamRepository, monitor);
	IIteration planIteration = deveLineHelper.findIteration(ownigProcessArea.getProjectArea(), path, DevelopmentLineHelper.BYLABEL); 
	if (planIteration == null) {
		System.out.println("Iteration " + planIterationName + " not found.");
		return false;
	}
		
	// If we have everything, create the paln
	createPlan(ownigProcessArea, planIteration, planName, planTypeID, teamRepository.loggedInContributor(), monitor);
	teamRepository.logout();

	return true;
}

The plan is created using the method createPlan(). This method creates all the pieces needed for the plan. It uses internal API pretty much everywhere. It has to, the plan API is not supported and not made consumable. You have to be aware that internal API can change without notice and even without documentation how to replace the old approach.

On the other hand, the internal API is also used in various places and it is likely to be needed in the future.

The method gets all the required services. Then it creates the new Plan item and gets the IIterationPlanRecord that represents it.

The next step is to set all the values for the plan configuration, name, owner, iteration and plan type ID.

It is not apparent, but the plan needs also a wiki page to be able to save it. This wiki page is created next.

Finally the plan is saved. If this succeeded, the new plan item interface is returned.

The code looks like below. Please note the internal API.

/**
 * WARNING, this method uses INTERNAL API
 * 
 * Creates a plan based on the process area that the plan is to be set in the 
 * Owner configuration element (can not be null),
 * the iteration that is set as root iteration of the plan configuration element
 * (can not be null), name and plan type Id. The creator user can not be null.
 *   
 * @param planOwner - the process area that owns that plan, can not be null
 * @param planIteration - the root iteration the plan selects, can not be null
 * @param planName - the name the plan will show have
 * @param planTypeID - the ID of the plan type from the plan process administration
 * @param creator - the user that will be set as creator of the plan
 * @param monitor - the progress monitor, can be null
 * @return The IIterationPlanRecord if the plan was created successfull or null
 * 
 * @throws UnsupportedEncodingException
 * @throws TeamRepositoryException
 */
public static IIterationPlanRecord createPlan(IProcessArea planOwner, IIteration planIteration,
		String planName, String planTypeID, IContributor creator, IProgressMonitor monitor)
		throws UnsupportedEncodingException, TeamRepositoryException {

	// Get the Team Repository
	ITeamRepository teamRepository = (ITeamRepository) planOwner.getOrigin();
		
	// get classes for plan creation
	IAuditableCommon auditableCommon = (IAuditableCommon) teamRepository
			.getClientLibrary(IAuditableCommon.class);
	// The IIterationPlanService - this is an internal API class
	IIterationPlanService planService = (IIterationPlanService) ((IClientLibraryContext) teamRepository).getServiceInterface(IIterationPlanService.class);

	// create necessary plan items
	// The IIterationPlanRecord 
	IIterationPlanRecord plan = (IIterationPlanRecord) IIterationPlanRecord.ITEM_TYPE
			.createItem();

	// setup plan values
	plan.setName(planName);
	plan.setIteration(planIteration);
	plan.setPlanType(planTypeID);
	plan.setOwner(planOwner);

	// The IWikiPage - this is an internal API class
	IWikiPage wiki = (IWikiPage) IWikiPage.ITEM_TYPE.createItem();

	// setup wiki page
	String encoding = "UTF8";
	String xmlText = "";
	byte[] bytes = xmlText.getBytes(encoding);
	InputStream inputStream = new ByteArrayInputStream(bytes);

	// The IWikiPage methods - these methods are all internal API
	wiki.setName("");
	wiki.setWikiID(IIterationPlanRecord.OVERVIEW_PAGE_ID);
	wiki.setCreator(creator);
	wiki.setOwner(plan);
	wiki.setContent(auditableCommon.storeContent(IContent.CONTENT_TYPE_TEXT,
			encoding, inputStream, bytes.length, monitor));

	// save plan
	// The these classes and methods are all internal API
	DTO_IterationPlanSaveResult saveResult = planService.save(planOwner,
			plan, wiki);
	// check if the save was successful
	if (saveResult.isSetIterationPlanRecord() == false) {
		System.out.println("Saving failed!");
		return null;
	}
	return plan;
}

Summary

The example above shows how you can automate creation of plans. With some more code to create and maintain timelines, this can make RTC project administration a lot easier and remove repetitive tasks from your plate.

As always I hope this helps someone out there to get their job done more efficient.

Advertisements

The RTC WorkItem Client Link API – Linking to Work Items and Other Elements


It is sometimes interesting to follow links in work items. I have seen and answered several related questions at the jazz.net Forum. This post is supposed to summarize what I have found about linking work items using the Plain Java Client Libraries so far. I will focus on the Client Library in this post because I realized that there are some important differences between the client and the server API with respect to accessing the work item’s references. I will try to address the differences on the server side in a later post. For now RTC Update Parent Duration Estimation and Effort Participant provides an example that shows how to follow work item to work item parent-child links on the server in an Advisor or in a Participant. In this case the information about existing and new references can be retrieved using the ISaveParameter.

*Update* The post A RTC WorkItem Command Line Version 2 contains downloadable code that performs most of the activities required for reading and modifying work items, including the creation of all kinds of links. The interesting code can be found in the com.ibm.js.team.workitem.commandline.helper package in the class WorkItemHelper. All techniques described below are used there. You can familiarize yourself with the concepts in this post and then look into that project for how it is used.

*Update* See the post Creating CLM Links With Back Link Between Work Items for some new information on CLM links betwen work items.

*Update* I figured the server side API and you can find the information in this post.

*Update* I took a deeper look at what to do with URI references.

*Update* See this link for code to get a work item handle from an URI

If you are just starting with extending Rational Team Concert, start reading this and the linked posts to get some guidance on how to set up your environment.

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

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

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

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

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 and Common API.

Creating References Using WorkItemEndPoints

The following code shows the code used to create a reference to another work item. It is based on the WorkitemOperation code used in Uploading Attachments to Work Items. It can however be used also with any kind of WorkItemWorkingCopy saved with the WorkingCopyManager too.

The code shows the most basic way to create a reference in RTC using the client API.

/**
 * Inner class to do the modification
 *
 */
private static class WorkItemReferencesModification extends WorkItemOperation {
	private IWorkItemHandle fOpposite;

	public WorkItemReferencesModification(IWorkItemHandle opposite) {
		super("Modifying Work Item References",IWorkItem.FULL_PROFILE);
		fOpposite = opposite;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy, IProgressMonitor monitor) throws TeamRepositoryException {
		// Create a new reference to the opposite item
		IItemReference reference = IReferenceFactory.INSTANCE.createReferenceToItem(fOpposite);
		// Add the new reference using a specific work item end point
		workingCopy.getReferences().add(WorkItemEndPoints.BLOCKS_WORK_ITEM, reference);
	}
}

This is the easiest way to create a reference from a work item to another work item. The code creates a new reference to a work item handle. In the second step it uses endpoints predefined in com.ibm.team.workitem.common.model.WorkItemEndPoints to create the reference. The endpoints of a reference, sort of, define the type of the reference. A reference is some kind of link between two objects. From the perspective of the objects the relationship might be different. For example a work item could have a parent from its perspective. The parent from its perspective would have a child. The relationship between two work items is defined using the endpoints from com.ibm.team.workitem.common.model.WorkItemEndPoints. Both ends of the link have their own endpoint. By defining the opposite endpoint, the other endpoint is also determined.

When looking at the endpoints defined in WorkItemEndPoints it appears that the available endpoints are only for work item to work item links. Taking a closer look would reveal that even not all CLM work item to work item link types are available. In general there is no endpoint for any URL based relationship. You can only use this code if your link is based on these available WorkItemEndPoints.

Creating References Using WorkItemLinkTypes

If it is necessary to create other types of links for example to some URL this mechanism does not work. The code below shows an alternative approach that creates the endpoint using a more fundamental mechanism provided by using the com.ibm.team.links.common.registry.ILinkTypeRegistry.

/**
 * Inner class to do the modification
 *
 */
private static class WorkItemReferencesModification extends WorkItemOperation {

	private IWorkItemHandle fOpposite;

	public WorkItemReferencesModification(IWorkItemHandle opposite) {
		super("Modifying Work Item References",IWorkItem.FULL_PROFILE);
		fOpposite = opposite;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy, IProgressMonitor monitor) throws TeamRepositoryException {
		// Create a new reference to the opposite item
		IItemReference reference = IReferenceFactory.INSTANCE.createReferenceToItem(fOpposite);
		// Create a new end point
		IEndPointDescriptor endpoint = ILinkTypeRegistry.INSTANCE.getLinkType(WorkItemLinkTypes.BLOCKS_WORK_ITEM).getTargetEndPointDescriptor();
		// Add the new reference using a specific work item end point
		workingCopy.getReferences().add(endpoint, reference);
	}
}

The code uses the ILinkTypeRegistry and the WorkItemLinkTypes to create an endpoint and then creates the reference. This code is more flexible and allows more link types to be created. This includes links to elements using a URL such as OSLC links to elements in other OSLC providers. The code below would create a tested by test case link for a work item.

reference = IReferenceFactory.INSTANCE.createReferenceFromURI(new URI("https://clm.example.com:9447/qm/oslc_qm/contexts/_Lm2UIACBEeGZqMjM3RLKTw/resources/com.ibm.rqm.planning.VersionedTestCase/_dJzNgQCBEeGZqMjM3RLKTw"));
workingCopy.getReferences().add(ILinkTypeRegistry.INSTANCE
	.getLinkType(WorkItemLinkTypes.TESTED_BY_TEST_CASE).getTargetEndPointDescriptor(),reference);

Accessing References

I am aware of two ways to access the references of a work item using the Client Libraries. If you have a WorkItemWorkingCopy you can access the references using this code:

// get all references from the work item workingcopy
IWorkItemReferences references = workingCopy.getReferences();

It is easy to get a working copy from a work item that already is derived from a working copy using

WorkItemWorkingCopy workingCopy = (WorkItemWorkingCopy) workItem.getWorkingCopy()

Another way to access the references, if you have a plain work item, is using the IWorkItemCommon client library. Note, in this case the ITeamRepository is required which can be retrieved using the method .getOrigin(). The IWorkItemClient client library provides this method as well, in case it is already available.

IWorkItemCommon common= (IWorkItemCommon) ((ITeamRepository)workItem.getOrigin()).getClientLibrary(IWorkItemCommon.class);
IWorkItemReferences references = common.resolveWorkItemReferences(workItem, null);

Once we have an IWorkItemReferences object we can analyze the references.

/**
 * Analyze the references of a workitem
 */
private void analyzeReferences(IWorkItemReferences references) {
	List endpoints = references.getTypes();
	for (IEndPointDescriptor iEndPointDescriptor : endpoints) {
		System.out.println("Endpoint: "
			+ iEndPointDescriptor.getDisplayName() + " ID: "
			+ iEndPointDescriptor.getLinkType().getLinkTypeId());
		List typedReferences = references.getReferences(iEndPointDescriptor);
		for (IReference iReference : typedReferences) {
			analyzeReference(iReference);
		}
	}
}

The IWorkItemReferences provides all available link types for the contained references. The code above gets the list and iterates it to look at the references for each type. The code prints some information. The next step is to get all the references for a given endpoint and analyze each reference.

/**
 * Analyze a reference
 */
public void analyzeReference(IReference iReference) {
	if (iReference.isItemReference()) {
		Object resolvedRef = iReference.resolve();
		analyzeItem(resolvedRef);
	}
	if (iReference.isURIReference()){
		analyzeReferenceTarget(iReference);
	}
}

The code above checks each reference if it is a reference to an IItem for example an IWorkItem or an URI reference. For the item reference there is more to analyze so the code resolves it to get the object an passes it on. For an URI reference the code gets the URI and prints it.

The resolved object can now be analyzed in the code below. A cast is used to get to the contained element such as an IWorkItemHandle or a BuildResultHandle. Once the handle is available it is possible to use the ITeamreposiory.itemManager() to get the item from the handle and manipulate it.

/**
 * Analyze an Item
 */
private void analyzeItem(Object resolvedRef) {
	System.out.println(" Resolved item: "
		+ resolvedRef.toString());
	if(resolvedRef instanceof IWorkItemHandle){
		IWorkItemHandle handle = (IWorkItemHandle)resolvedRef;
	}
	if(resolvedRef instanceof BuildResultHandle){
		BuildResultHandle handle = (BuildResultHandle)resolvedRef;
	}
}

*Update* I looked deeper in what the client API provides in terms of accessing the elements referenced by the URI. Here is what I came up with. Please be aware that I am unsure if this works for all cases. It seems to be possible to resolve the element for example if it is in the same repository. If it is a work item you can then use the typical interfaces to access its data.

/**
 * Further analyze an item referenced by an URI
 * @param iReference
 */
public void analyzeReferenceTarget(IReference iReference) {
	URI uri = iReference.createURI();
	try {
		System.out.println("   Resolving URI: " + uri.toString());
		ITeamRepository teamRepo = (ITeamRepository) iReference.getLink().getOrigin();
		IAuditableClient auditableClient = (IAuditableClient) teamRepo.getClientLibrary(IAuditableClient.class);

		// get the location from the URI
		Location location = Location.location(uri);
		// resolve the item by location
		IAuditable referenced = auditableClient.resolveAuditableByLocation(location,
				ItemProfile.createFullProfile(location.getItemType()), null);
		// look for a referenced work item
		if (referenced instanceof IWorkItem) {
			IWorkItem referencedWI = (IWorkItem) referenced;
			System.out.println("   Resolved URI (resolve): "
				+ uri.toString() + " to: " + referencedWI.getId()  
				+ " " + referencedWI.getState2().toString());
		}
	} catch (TeamRepositoryException e) {
		e.printStackTrace();
	}
	System.out.println("   Resolved URI: " + uri.toString());
}

This is pretty much the summary of how links work on the client. I hope the code is useful, and it is easy enough to enhance the code for other purposes. Please remember that there is few error handling at this point. You might want to enhance this.

Common API to create Link URI’s

If you want to provide a URI for elements such as work items, you can use the Location class to do so.

The following code creates two different URI’s for a work item that are used in different types of links.

IWorkItemCommon common = (IWorkItemCommon) teamRepository.getClientLibrary(IWorkItemCommon.class);

int id = new Integer(idString).intValue();
IWorkItem workItem = common.findWorkItemById(id,IWorkItem.SMALL_PROFILE, monitor);
if (workItem == null) {
	System.out.println("Work item: " + idString + " not found.");
	return false;
}

System.out.println("Work item: " + workItem.getId() + ".");
Location location = Location.namedLocation(workItem,((ITeamRepository) workItem.getOrigin()).publicUriRoot());
System.out.println("Named Location URI: " + location.toAbsoluteUri());
		
location = Location.itemLocation(workItem,((ITeamRepository) workItem.getOrigin()).publicUriRoot());
System.out.println("Item Location URI: " + location.toAbsoluteUri());

The named location looks something like

https://server:port/rtc/resource/itemName/com.ibm.team.workitem.WorkItem/45943

The item location looks something like

https://server:port/rtc/resource/itemOid/com.ibm.team.workitem.WorkItem/_cdH5kJ0REean7cO1UYIcNw

Different link types use the different locations for their endpoints. The API is common api and available in the client as well as the server.