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.