The Work Item Command Line is now Open Source


To allow customers to use and share the WorkItem Command Line freely, it has now been released under the MIT License.

Access the Source Code

The code is available in the Jazz Community.

OpenSourceGitHub

License

Released under the MIT License. See the License.txt and the license headers in the individual files.

 

Changes

The current version uploaded there contains the capabilities described in The RTC Work Item Command Line on Bluemix.

Additional Download

You can also download the latest version 4.0 here:

Please note, there might be restrictions to access Dropbox and therefore the code in your company or download location.

Usage and install

Please see the posts A RTC WorkItem Command Line Version 3.0.

For the general setup follow the description in A RTC WorkItem Command Line Version 2.

For usage follow the description in A RTC WorkItem Command Line Version 2 and in A RTC WorkItem Command Line Version 2.1. Check the README.txt which is included in the downloads.

Summary

The work item command line is now available on IBM Bluemix Dev Ops Services and can be accessed and worked on there.

Advertisements

Only Owner Can Close WorkItem Advisor


I always wanted to do a Server Work Item Save advisor, so here is a simple example. This advisor will prevent closing work items for any user that is not the owner of the work item. Since the code turned out to be very simple, I will try to emphasize some other useful details about creating it. Please note, that this code can be easily changed to do more complex things e.g. only work for certain work item types or workflows, look at roles of the user that does the save to act on that and much more. There are various code examples in this blog that could be used to extend the code below to achieve those goals. The API code in this post is Server and Common API.

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.

The example in this blog post shows RTC Server and Common API.

Creating the Plug-in(s)

The first step is, as usual, to create a plug-in. This is easily enough using the New Project wizard and choosing the Plug-in Project. All you need to provide is a name for the project and some settings.

Using a name like “OnlyOwnerCanSaveAdvisor” looks great and a lot of people do this. Its a trap!

When choosing the name of the project I have learned over the years, it is a good idea to have a certain naming convention. The most important point here is to be able to easily find your plug-ins and features again, once they are deployed. If you deploy into the Eclipse Client, your extension might end up between 1500 other extensions. If you don’t know an easy way to locate your code, you have a problem. It can take a lot of time to find it. If you don’t know where stuff is going to end on disk and if something is deployed or not, being able to search for a file name helps a lot.

Tip: Name your extensions in a way that allows you to easily search and identify them. Create a namespace pattern to support this.

I use a Java namespace structure to name my projects. I always use com.ibm.js as prefix in the name. So I can easily search for files named com.ibm.js. The rest of the name usually has something to do with the purpose. In this case I chose com.ibm.js.team.workitem.extension.advisor.statechange.service as name for the plug-in project. I chose service, because this plug-in runs on the server.

If I have to develop more complex extensions, I often end up having several plug-in projects that belong together. To be able to easily locate them in my workspace I ended up to have a common name part and a special suffix for each of the projects. The common name here would be com.ibm.js.team.workitem.extension.advisor.statechange and suffixes would be service, component, feature, updatesite and potentially others. This makes it so much easier to find around in the Eclipse UI.

The other choices here are trivial. We don’t need an activator class for this. Keep the other defaults. There is no template to choose from, so finish the wizard.

Once the plug-in project is created, give it a useful name and leave the other information as it is. Especially leave the .qualifier suffix in the version. This allows Eclipse to create a unique version extension. Your plug-in overview tab should look like below.

Plug-in Overview

Tip: Keep the Version number structure with the .qualifier suffix as provided by the default. This allows Eclipse to create a unique version extension.

The next step is typically adding the dependencies. I usually start with some I have from other extensions. org.eclipse.core.runtime is almost always needed.

Tip: Start with dependencies used in other extensions. It is easy to remove dependencies later.

You can add dependencies easily by following the namespace pattern used in RTC. The pattern starts with com.ibm.team then there is a domain such as workitem and the suffix is typically service, common, client where

  • service is API that is only available on the server
  • common is API that is available to the server and (Java/Eclipse) clients
  • client is API only available to (Java/Eclipse) clients

The domains are

  • process for API related to the process specification for process areas (project areas/team areas)
  • repository for API related to accessing data in the repository
  • workitem for work item related API
  • filesystem for the SCM API
  • interop for API to develop work item synchronizers

amongst others.

Tip:Use the namespace pattern provided by the RTC SDK to find the API plug-ins you have dependencies to.

To add dependencies, use the add button and the namespace pattern to find interesting plug-ins.

Search and add dependencies

Please be aware that there is a domain reports that continues with the usual pattern, including the domain names in the suffix as subdomains. Avoid to accidentally pick one of those if you don’t work in the reports API. If you want to use API and the classes can not be found, although the dependency was meant to be added, check if you accidentally picked the reports domain and fix the dependency. This happened to me many, many times.

Tip: The reporting API can sneak in because its namespace space includes the other domains as subdomain. Make sure to pick the right plug-ins.

It is a good idea to start with adding the usual suspects as dependencies. If you need additional API later, you can always add it on the fly and save the plugin.xml to be able to access the API.

This is a typical first iteration of dependencies:

Typical dependencies When adding the dependency, there is compatibility information added. This information would require at least a certain version of the dependency to be available. It is possible to remove this information to make the extension more compatible e.g. to earlier versions of RTC. In the dependencies above, I removed the minimal version. Since this extension was developed with RTC 4.0.5, but would work with other, earlier versions, as well this would now be deployable e.g. in RTC 4.0 or even 3.0.

Tip: Manage the required versions in the dependencies, if you want to be able to deploy in RTC versions with lower version numbers than the version you use to develop your plug-in.

The next step is to add the extension you want to hook up to. In our case we want an operationAdvisor. It can be found the same way we found the dependencies. If you can’t find your extension point, please uncheck the option Show only extension points from the required plug-ins, to make sure you can see all the extension points, even if you have not yet added the required dependency.

The full ID of the extension point is com.ibm.team.process.service.operationAdvisors  from the list of Extension Points and Operation ID’s.

Tip: Make sure to pick all extension points and use the namespace patterns already described to find extension points.

Tip: Look in the list of Extension Points and Operation ID’s to learn more about what is available.

Find and add extension pointsSelect the extension point and add it.

Once the extension point is added, provide the required information. You need to provide data for the operation advisor.

Specify basic informationThere are several mandatory fields here. I stick to my namespace pattern and provide the following information:

  • id – com.ibm.js.team.workitem.extension.advisor.statechange.RestrictClosingToOwner
  • class – com.ibm.js.team.workitem.extension.advisor.statechange.service.RestrictClosingToOwner
  • name – Restrict Closing Work Item to Owner
  • operationId – we want to react on work item save so choose com.ibm.team.workitem.operation.workItemSave from the list of available Extension Points and Operation ID’s.

Clicking in the field name class* in front of the class definition allows to create a class. It also shows you what the class is required to provide to be able to conform to the specification of the extension point. When you create the class make sure to use com.ibm.team.repository.service.AbstractService as superclass and choose the com.ibm.team.process.common.advice.runtime.IOperationAdvisor interface as implemented. The class definition should look like:

public class RestrictClosingToOwner extends AbstractService implements IOperationAdvisor {

The code of the class is going to be presented later below. For now, just quickfix and let it add the methods to implement. In order for the code to run later, you need to specify an extension service to provide the component ID this extension belongs to and specify the implementation class for this service. The interface is required in the operationAdvisor specification. The AbstractService comes in in the next step.

Tip: Make sure to create the extending class with the right interface by looking at the editor.

Click on the operationAdvisor node and add the extension service. You can also add a description.

Specify Extension Service

The extension service needs to be specified.  Stay with the namespace pattern and provide a component ID. As implementationClass, select the one that was just created.

  • componentId – com.ibm.js.team.workitem.extension.advisor.statechange.common.component
  • implementationClass – com.ibm.js.team.workitem.extension.advisor.statechange.service.RestrictClosingToOwner

Tip: Server extensions usually extend AbstractService which provides capabilities needed later e.g. to get services.

This class needs to be based on AbstractService, which was already dealt with.

The Jazz compnent extension with the ID that was just chosen still needs to be defined. There is a special extension point for this. The extension point to specify the Jazz component is com.ibm.team.repository.common.components.

Tip: Create the Extension providing a jazz component in a different plug-in. This allows to use the component later if server as well as client extensions are needed, e.g. to provide an aspect editor to configure the extension in the UI.

It would be possible to define the component in the current plug-in. However, if the component is needed in server as well as client extensions, it is necessary to bundle it with both. In this case it is better to create a special plug-in for the component. Provide the same ID that was used in the definition of the extension service and provide a name for the component.

Specify a component extensionTip: To get more information about the extension point look at the description, the schema, declaration and references. You can find all kinds of information easily, including plug-ins that extend this point and the classes that implement the extension.

There are several means that allow you to find out more about the extension point and implementations. Just be curious and look at it. The image beow shows where to access this information.

Extension Point DetailsAnother place is where you add new extensions to the point.

Add more advisorsIt will be necessary later, to declare the services that are used by the extension. Unfortunately the schema does not contain the node to do so. This is a manual that needs to be done in the plugin.xml.

Implement the Extension

The class that is to be called by the extension point has already been created. However, the implementation is still missing and needs to be provided. It is easy to open the class from the plug-in editor.

The entry point into the class called by the extension point is the run() method. The implementation code is provided below. As all advisors (preconditions)  and participants (follow-up actions) the run() method gets the information about the work item and other information about the context it is running.

This information is checked first and the work item is extracted. The advisor code contains a section that is commented out right now.This code could later be used to limit the restriction only to specific work item types.

The advisor shall not limit saving the work item in closed stats, it should only preventing to change the state to a state in the closed group. So the next check is looking at the workflow action to determine if there is a state change. If not, the advisor does not prevent the operation.  So a user could still update attributes, while it is closed,however, only the owner can change states to states in the closed group.

The last section checks if the new state of the work item is in the closed group. If not, nothing needs to be done.

If there is a workflow action, the next step is to check the state the work item will enter. If the new state is not in the closed group, nothing needs to be done.

If the new state is in the close groups, the final check is comparing the owner and the current user. If the ID’s match, nothing needs to be done. Note, this is also the case if the work item is already closed and someone wants to move it to another closed state. Only the owner will be able to do this, provided the advisor is configured for all roles.

Finally, this is a state change into a closed state and the current user is not the owner. The advisor provides a problem info and returns it. This will block the save. Please note, this is also true if the owner is unassigned.

Here is the code:

/*******************************************************************************
 * Licensed Materials - Property of IBM (c) Copyright IBM Corporation 2005-20014.
 * 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.team.workitem.extension.advisor.statechange.service;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.IAuditable;
import com.ibm.team.repository.common.IContributorHandle;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.service.AbstractService;
import com.ibm.team.workitem.common.ISaveParameter;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.workflow.IWorkflowInfo;
import com.ibm.team.workitem.service.IWorkItemServer;

public class RestrictClosingToOwner extends AbstractService implements IOperationAdvisor {

	@Override
	public void run(AdvisableOperation operation,
			IProcessConfigurationElement advisorConfiguration,
			IAdvisorInfoCollector collector, IProgressMonitor monitor)
			throws TeamRepositoryException  {
		Object data = operation.getOperationData();
		if (data instanceof ISaveParameter) {
			IAuditable auditable = ((ISaveParameter) data).getNewState();
			if (auditable instanceof IWorkItem) {
				IWorkItem workItem = (IWorkItem) auditable;

//				// If this needs to be limited to a special type
//				if (workItem.getWorkItemType() != "Enter Type ID Here")
//					return;
				
				// We want to allow saving the work item, if there is no state change happening.				
				String action = ((ISaveParameter) data).getWorkflowAction();
				if(action==null)
					return;
				
				// Get the workflow info and check if the new state is in the closed group.
				IWorkItemServer iWorkItemServer = getService(IWorkItemServer.class);
				IWorkflowInfo workflowInfo = iWorkItemServer.findWorkflowInfo(workItem,
						monitor);
				if (!(workflowInfo.getStateGroup(workItem.getState2()) == IWorkflowInfo.CLOSED_STATES)) {
					return; // nothing to check if the new state is not closed.
				}

				// work item is going to a state in the closed group.
				// Check if the current user is owner of the work item.
				IContributorHandle loggedIn = this
						.getAuthenticatedContributor();
				IContributorHandle owner = workItem.getOwner();
				if ((owner != null && owner.getItemId().equals(
						loggedIn.getItemId())))
					return;
				
				IAdvisorInfo info = collector.createProblemInfo(
						"The work item can only closed by its owner!",
						"The work item can only closed by its owner! If the owner is unassigned and it can also not be closed.",
						"error");
				collector.addInfo(info);
			}
		}
	}
}

Before we can do the debugging the last thing we need to do is to require the service IWorkItemServer we use to be available to the server in the plugin.xml. The plugin.xml needs to be changed to reflect that.

Add the prerequisite section with the service(s) required as presented below.

Prerequisite for the required serviceTip: Since the extension point schema does not have the prerequisite added, this is something you simply have to know how to do it.

Please also see Creating Custom Link Types for Rational Team Concert, especially the sections Prepare to Deploy and the following 2 sections describing the additional project to make deployment a little easier.

Download

You can download the final code here.

Summary

The advisor presented above will prevent anyone, except the owner, to use any action that changes the state of a work item to a closed state. Users, other than the owner can still update work item attributes, but if it is configured for a user, state changes to a closed state are only possible for the owner of a work item.

As always, I hope the code above helps someone out there with extending RTC.

I will blog about the next steps, like debugging and deploying the advisor in one of the next posts.

Working with Work Item Attributes


When I started with customizing RTC, the biggest challenge was how to get started on work item attributes. Since getting work item attributes is also one of the most popular search terms on this blog, I think a small summary would be useful for new users of the RTC Java API’s.

*Update* The post The RTC Work Item Command Line on Bluemix contains downloadable code that performs most of the activities required for reading and modifying work items, their attributes, and all kinds of links. This includes reading and writing work item attribute of all kinds, including list attribute types. The interesting code can be found in the com.ibm.js.team.workitem.commandline.helper package in the class WorkItemHelper. For reading values look at the class ExportWorkItemsCommand and look at ExportWorkItemsCommand.getStringRepresentation(IWorkItem, ParameterValue) and the subsequently called methods. 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.

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

Download Example Code

Download Examples from Dropbox here. Please note, that Dropbox might be blocked and unavailable in some companies.

Update: the The RTC Work Item Command Line on Bluemix has example code for an almost complete set of attribute types.

To keep it simple this example is again based on the Jazz Team Wiki entry on Programmatic Work Item Creation. 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.

Which API?

If you look at the examples below, you will note that the code below uses the interfaces

com.ibm.team.workitem.common.IWorkItemCommon

or

com.ibm.team.workitem.client.IWorkItemClient

to access work item data. Always look at the interface com.ibm.team.workitem.common.IWorkItemCommon first and use it if the method you need is available. The common API is usable in the client and the server API and is the preferred API to use. Only use specific client or server API if the common API does not have this API(the namespace is like com.ibm.*.domain.client for client API or  com.ibm.*.domain.server for server API).

Most of the usage of the methods available on IWorkItemClient are inherited from IWorkItemCommon and it is better to replace usage of IWorkItemClient with usage of IWorkItemCommon whereever possible.

Why did I use IWorkItemClient? I was inexperienced and found the client API first in examples and It only later occurred to me that it is better to search the common API first.

Getting the Work Item’s Attribute ID’s in the UI

You can find the work item attributes and their ID’s in the project area administration in the section Work Items, subsection Types and Attributes. select the work item type you are interested in. The attribute IDs for the built in and the custom attributes for the selected work item type can be found in the ID column.

AttributeIDs

RTC Web Administration UI shows valid attribute ID’s

Please note, do not use the attribute ID’s you find in the project area administration in the RTC Eclipse client. The RTC Eclipse client shows attribute ID’s with a prefix com.ibm.team.workitem.attribute such as such as com.ibm.team.workitem.attribute.category. This attribute ID is not the one that works with the work item API. I am not sure what they really do, but they don’t work to get attribute values. See the Enhancement Request 219100 RTC Eclipse Client shows wrong IDs for internal Work Item Attributes for Java API for details.

WrongAttributeID's

RTC Eclipse Client shows wrong attribute ID’s for internal attributes

Getting the Work Item’s Attributes in the API

You can also find the attribute ID’s in the API. The Work Item Command Line uses this for the command printtypeattributes.

If you have a work item, an object of type IWorkItem in the API, you will realize that you have only a very limited set of getters and setters to access information for the work item. You have access to the Summary, Owner, and some other built in attributes of the work item. If you look at a RTC work item, you realize there is much more information in it. The question is how to access this information?

The RTC data meta-model provides a special interface that is used to access arbitrary attributes of a work item. The interface is com.ibm.team.workitem.common.model.IAttribute. To access an arbitrary Attribute at a work item, you need to get the IAttribute first.

There are several ways how this can be done.

If you have the Attribute ID available as a string, you can use this code to get the attribute.

IWorkItemClient workItemClient = (IWorkItemClient) fTeamRepository.getClientLibrary(IWorkItemClient.class);
IAttribute someAttribute= workItemClient.findAttribute(fProjectArea, "some_attribute_ID", monitor);

Another example if you know the work item ID, it is possible to find the work item form the string value.

IWorkItemCommon workItemCommon = (IWorkItemCommon ) fTeamRepository.getClientLibrary(IWorkItemCommon.class);
IAttribute attribute = workItemCommon.findAttribute(projectArea, attributeID,
				getMonitor());
if(attribute != null){
	// Do something with it.
}

RTC defines some built-in attributes that are typical for all kinds of work items. You can get the ID’s for these attributes using the interface com.ibm.team.workitem.common.model.IWorkItem. The interface defines several properties containing the ID’s for built-in the work item attributes.

This code for example gets the attribute for the built-in duration.

IAttribute built_in_duration = workItemClient.findAttribute(fProjectArea, IWorkItem.DURATION_PROPERTY, monitor);

If this is not feasible, because you do some automation and don’t want to hard code it, the code below gets all the built in attributes of a project area.

List builtInAttributeHandles = workItemClient.findBuiltInAttributes(fProjectArea, monitor);

If you are looking for all attributes, this method below retrieves the list.

List allAttributeHandles = workItemClient.findAttributes(fProjectArea, monitor);

There is no method that returns only the custom attributes from the project area. You can however get the custom attributes directly from a work item using the code below:

List custAttributeHandles = workItem.getCustomAttributes();

The code to get the attributes, returns only a handle. You have to resolve the handle to the full object. The code below shows how that can be done.

for (Iterator iterator = builtInAttributeHandles .iterator(); iterator.hasNext();) {
    IAttributeHandle iAttributeHandle = (IAttributeHandle) iterator.next();
    IAttribute iAttribute = (IAttribute) fTeamRepository
        .itemManager().fetchCompleteItem(
        iAttributeHandle, IItemManager.DEFAULT ,monitor);
}

This code uses the permission aware API instead, to get the built in attributes.

List builtInAttributeHandles = workItemClient.findBuiltInAttributes(fProjectArea, monitor);
IFetchResult builtIn = getTeamRepository().itemManager().fetchCompleteItemsPermissionAware(built_in_attribs,
    IItemManager.DEFAULT, monitor);

Get a Work Item Attribute

Once you have the IAttribute, you can get the value of the attribute using the method IWorkItem.getValue(). However, as described in the post Using an Expression to Synchronize Attributes for Work Items of a Specific Type, work items do not necessarily have all attributes already. This especially holds true for custom attributes. You either need to check for a null value, or you can check if the work item actually has the attribute using code like below.

if (workItem.hasAttribute(iAttribute)) {
	Object value = workItem.getValue(iAttribute);
}

The call returns a plain object. You have to cast the returned value to the expected type, to use it. Examples for basic objects are String, Integer, Boolean. You can use these to access the data. The API has many more attribute types that you can also use to cast and access the data.

Here an example how to access a attribute of type String:

if (workItem.hasCustomAttribute(customString)){
	Object value = workItem.getValue(customString);
	if (value instanceof String) {
		String attributeValue = (String) value;
		// do something with the value
	}
}

It is necessary to understand the type that is returned. The information is can easily be picked up during debugging.

Typical Attribute Types

The available attribute types can be found in the process configuration in the Web UI (or the Eclipse UI). To get the valid information for built in and custom attributes, the Web UI works best. The Type column shows the type, the ID column shows the ID of the attribute. Note, the Eclipse Client shows an external ID for built in attributes, that does not work.

AttributeTypes_2017-05-15_10-40-15

Please find below some examples how to access work item attributes of different types. The examples are taken from the Work Item Command Line which has examples for almost every attribute type.You can download the code from this post. All the examples expect the object returned by

Object value = workItem.getValue(customString);

as parameter. Some need the IAttribute in addition to that.

For a complete list of attribute values and their conversion to text see the WorkItem Command Line and the class ExportWorkItemsCommand and look at ExportWorkItemsCommand.getStringRepresentation(IWorkItem, ParameterValue) and the subsequently called methods to convert the attributes to strings.

The most important attribute types are shown below.

Category

The attribute “Filed Against” returns a category. The conversion to user readable text looks like this:

/**
 * Compute the string representation for a category
 * 
 * @param value
 * @return
 * @throws TeamRepositoryException
 */
private String calculateCategoryAsString(Object value)
		throws TeamRepositoryException {
	if (value != null) {
		if (value instanceof ICategoryHandle) {
			return getWorkItemCommon().resolveHierarchicalName(
				(ICategoryHandle) value, getMonitor());
		}
		throw new WorkItemCommandLineException(
				"Convert Category - Incompatible Type Exception: "
						+ value.toString());
	}
	return CONSTANT_NO_VALUE;
}

Number Types

There are several number types supported. Here the conversion to text values.

/**
 * Compute the string representation for a number
 * 
 * @param value
 * @param format
 * @return
 */
private String calculateNumberAsString(Object value, String format) {
	if (value == null) {
		return CONSTANT_NO_VALUE;
	}
	if (value instanceof Integer) {
		return ((Integer) value).toString();
	}
	if (value instanceof Long) {
		return ((Long) value).toString();
	}
	if (value instanceof Float) {
		return ((Float) value).toString();
	}
	if (value instanceof BigDecimal) {
		return ((BigDecimal) value).toString();
	}
	throw new WorkItemCommandLineException(
			"Calculate number - Incompatible Type Exception: "
					+ value.toString());
}

Enumeration Type Attributes

Typical custom attribute types are enumerations. This code shows how to access the values. Also see the post Manipulating Work Item Enumeration Values.

/**
 * Compute a string representation for one enumeration literal
 * 
 * @param value
 * @param attribute
 * @return
 * @throws TeamRepositoryException
 */
private String calculateEnumerationLiteralAsString(Object value,
		IAttribute attribute) throws TeamRepositoryException {
	if (value == null) {
		return CONSTANT_NO_VALUE;
	}
	if (!(value instanceof Identifier)) {
		return "Value not an enumeration literal";
	}
	IEnumeration enumeration = getWorkItemCommon()
			.resolveEnumeration(attribute, getMonitor());
	@SuppressWarnings("unchecked")
	Identifier currentIdentifier = (Identifier) value;
	ILiteral literal = enumeration
			.findEnumerationLiteral(currentIdentifier);
	return literal.getName();
}

Comments

Comments are not an attribute, but a collection that can be accessed. Here an example how to get at the information.

IComments comments = workItem.getComments();
IComment[] theComments = comments.getContents();
List commentText = new ArrayList(theComments.length);
int i = 1;
for (IComment aComment : theComments) {
	if (i > 1) {
		commentText.add("\r");
	}
	commentText.add(i + ". " + aComment.getHTMLContent().getPlainText());
	i++;
}

Contributor Type Attributes

User references are stored as com.ibm.team.repository.common.IContributorHandle as handles for IContributors. To access the contributor information it is necessary to get the handle and resolve the contributor. This is shown below.

/**
 * Compute the string representation for a contributor/user
 * 
 * @param value
 * @return
 * @throws TeamRepositoryException
 */
private String calculateContributorAsString(Object value)
		throws TeamRepositoryException {
	if (value == null) {
		return CONSTANT_NO_VALUE;
	}
	if ((value instanceof IContributorHandle)) {
		IContributor contributor = (IContributor) getTeamRepository()
				.itemManager().fetchCompleteItem(
						(IContributorHandle) value, IItemManager.DEFAULT,
						getMonitor());
		return contributor.getName();
	}
	throw new WorkItemCommandLineException(
			"Convert Contributor - Incompatible Type Exception: "
					+ value.toString());
}

Timestamps

Timestamps  are stored as java.sql.Timestamp. The code below shows how to get the data.

/**
 * Compute the string representation for a timestamp
 * 
 * 
 * @param value
 * @return
 */
private String calculateTimestampAsString(Object value) {
	if (value != null) {
		if (value instanceof Timestamp) {
			Timestamp timestamp = (Timestamp) value;
			return SimpleDateFormatUtil.getDate(timestamp,
					getSimpleDateTimeFormatPattern());
		}
		throw new WorkItemCommandLineException(
				"Convert timestamp - Incompatible Type Exception: "
						+ value.toString());
	}
	return CONSTANT_NO_VALUE;
}

The class com.ibm.js.team.workitem.commandline.utils.SimpleDateFormatUtil is part of the WorkItem Command Line and uses java.text.SimpleDateFormat to convert the value to a string.

Durations

Durations such as “Time Spent” are stored as long. The code below converts the value.

/**
 * Compute the string representation for a duration
 * 
 * @param value
 * @param attribType
 * @return
 */
private String calculateDurationAsString(Object value, String attribType) {
	if (value != null) {
		if (value instanceof Long) {
			Long milliseconds = (Long) value;
			return SimpleDateFormatUtil.convertToTimeSpent(milliseconds);
		}
		throw new WorkItemCommandLineException(
				"Calculate Duration - Incompatible Type Exception: "
						+ value.toString());
	}
	return CONSTANT_NO_VALUE;
}

The class com.ibm.js.team.workitem.commandline.utils.SimpleDateFormatUtil is part of the WorkItem Command Line and uses java.text.SimpleDateFormat to convert the value to a string.

Iterations

Iterations are returned as IIterations. To resolve the iteration see the code below. The WorkItem Command Line contains the class com.ibm.js.team.workitem.commandline.helper.DevelopmentLineHelper to convert Iterations and Timelines to user readable text.

/**
 * Get the IIteration object from a handle
 * 
 * @param handle
 * @return
 * @throws TeamRepositoryException
 */
public IIteration resolveIteration(IIterationHandle handle)
		throws TeamRepositoryException {
	if (handle instanceof IIteration) {
		return (IIteration) handle;
	}
	IIteration iteration = (IIteration) fTeamRepository.itemManager()
			.fetchCompleteItem((IIterationHandle) handle,
					IItemManager.DEFAULT, fMonitor);
	return iteration;
}

Work Item State

To get the work item state see the code blow.

/**
 * Convert a work item state to a string
 * 
 * @param workItem
 * @return
 * @throws TeamRepositoryException
 */
private String calculateStateAsString(IWorkItem workItem)
		throws TeamRepositoryException {
	Identifier state = workItem.getState2();
	IWorkflowInfo wfInfo = getWorkItemCommon().findWorkflowInfo(workItem,getMonitor());
	String stateName = wfInfo.getStateName(state);
	if (stateName == null) {
		return "";
	}
	return stateName;
}

Subscriptions

Subscriptions is another pseudo attribute that really is a collection.

ISubscriptions subscriptions = workItem.getSubscriptions();
IContributorHandle[] contributors = subscriptions.getContents();
List subscribers = Arrays.asList(contributors);

Compute the contributors from the list as shown above. Also see Subscribing To a Work Item Using the Java API.

Approvals

Approvals is also a pseudo attribute and is accessed using a collection. The conversion is quite sophisticated as the approval information is composed of several descriptors and approval records. Please see the WorkItem Command Line and the ExportWorkItemsCommand.calculateApprovalsAsString(IWorkItem) for the code.

Also see Adding Approvals to Work Items Using the Plain Java Client Libraries.

Other Attribute Types or Pseudo Attributes

Please see the WorkItem Command Line especially the class ExportWorkItemsCommand for how to access the values. Also see

List Attributes

Work Item Attributes have a type for example smallString, mediumString, myCustomEnumeration and many more. Since RTC 4.x it is possible to have attributes defined that can contain a list of attribute values. The attributes are defined by picking the attribute type with the post-fix list in the attribute type selection for example string list, myCustomEnumeration list, Work Item list, Team Area list etc.

Basically the returned object for attribute types is a list object an instance of:

List

to get at the contained values, cast to a list. Then iterate the list and work on the returned object.

/**
 * Compute the string representation for a string list object
 * 
 * @param value
 * @return
 */
private String calculateStringListAsString(Object value) {
	if (value == null) {
		return CONSTANT_NO_VALUE;
	}
	List resultList = new ArrayList();
	if (value instanceof List) {
		List items = (List) value;
		for (Object object : items) {
			resultList.add(calculateString(object));
		}
	}
	return StringUtil.listToString(resultList, SEPERATOR_NEWLINE);
}

For the objects returned cast them to the correct instance and then use the values as fits. Her an example for a string type.

/**
 * Compute the string representation for a string object
 * 
 * @param value
 * @return
 */
private String calculateString(Object value) {
	if (value != null) {
		if (value instanceof String) {
			return (String) value;
		}
		throw new WorkItemCommandLineException(
				"Convert string - Incompatible Type Exception: "
						+ value.toString());
	}
	return CONSTANT_NO_VALUE;
}

See the WorkItem Command Line and ExportWorkItemsCommand.getStringRepresentation(IWorkItem, ParameterValue) for how to analyze the attribute types and call the conversions.

Set the Work Item Attribute

Very similar to getting the work item Attribute, you can set the value of an attribute. You need to have a WorkItemWorkingCopy of the work item to be able to set an work item attribute value. See the Jazz Team Wiki or for example this post for how to retrieve a working copy. This post shows some of the code for the server. The code below shows this for a custom integer attribute. First it checks if the work item attribute is available and then sets the value. The IWorkItem.setValue() method assumes it is able to cast the value in order to be able to set it. You need to provide a value of a matching type to set the attribute.

IWorkItem workItem = IWorkItem workItem = workingCopy.getWorkItem();
Integer value = new Integer("4");
if (workItem.hasCustomAttribute(customInteger))
	workItem.setValue(customInteger, value);
}

The various types that can be set are explained above. The data has to be provided as retrieved above. Typical types are String, Integer, Long, BigDecimal, enumerations, Timestamps.

For List attributes you have to construct a list of attributes and set the list. e.g. A very simple example would be for a custom enumeration. Construct a list of attribute values (e.g. the Identifier for the enumeration literal) and use the list to set the values:

List values = new ArrayList();
values.add(getLiteralEqualsString("foo", customEnumerationList));
values.add(getLiteralEqualsString("bar", customEnumerationList));
workItem.setValue(customEnumerationList,values);

With getLiteralEqualsString returning the enumeration value identifier as explained in Manipulating Work Item Enumeration Values.

private static Identifier getLiteralEqualsString(String name, IAttributeHandle ia) throws TeamRepositoryException {
.
.
.
}

See the work item commandline especially the WorkItemUpdateHelper for methods ending with List for example:

com.ibm.js.team.workitem.commandline.helper.WorkItemUpdateHelper.calculateEnumerationLiteralList(ParameterValue, List)

ClassCast Exceptions

Regardless whether you read a value or write a value, you usually can’t just use the value. As described in Understanding and Using the RTC Java Client API you need to cast the object you get to something you can use. A String, an Int, an enumeration literal, or for more complex itens to an ItemHandle and resolve the handle to be able to access the data.If you want to set a value, you have to make sure that the Object you pass conforms to the attribute type as well.If your data does not conform to the value that is returned or expected, your code will throw a ClassCastException.My experience so far is, that such exceptions usually were my fault and not the fault of the API. So in case of exceptions first look at your code before posting the API is broken in the forum.

Examples for setting work item attributes

The Work Item Command Line has examples for a complete set of work item attribute types. The class   com.ibm.js.team.workitem.commandline.helper.WorkItemUpdateHelper provides examples for how to find the work item attributes from an ID, get the attribute type and set a value from a string representation. The entry points are

  • com.ibm.js.team.workitem.commandline.helper.WorkItemUpdateHelper.updateProperty() – the method tries to find the attribute and calculate the attribute type
  • com.ibm.js.team.workitem.commandline.helper.WorkItemUpdateHelper.getRepresentation() – the method takes the calculated attribute type IAttribute and the string representation and tries to get an object that represents the value in the repository; the code tries to find a contributor from the ID, tries to find an iteration or a category from a text description.

Various methods with the prefix calculate, find, get, create, update are provided that do the work to convert or lookup the data.

Related posts for setting attributes

Saving the work item

As described in Understanding and Using the RTC Java Client API you can not just do a setValue() and expect the work item to change. You need a WorkItemWorkingCopy to actually change the value. The best way to do this in the client API is described in Understanding and Using the RTC Java Client API. Use a WorkItemOperation to wrap your code. This post has examples for how to work in the server API.

Summary

This is a short overview about the basics you need to know to access and modify work item attribute values. Other posts in this blog and several other examples on Jazz.net and IBM Developerworks provide more information on the API.

Related Posts

The RTC Work Item Command Line on BluemixA RTC WorkItem Command Line Version 3RTC Update Parent Duration Estimation and Effort ParticipantManipulating Work Item Enumeration ValuesHandling Iterations – Automation for the “Planned For” AttributeUsing Work Item Queries for AutomationUsing Expressions for AutomationUsing an Expression to Synchronize Attributes for Work Items of a Specific TypeManipulating Work Item Workflow StatesResolve Parent If All Children Are Resolved ParticipantDo Not Modify The Triggering Element In An OperationAdvisor

A Create Approval Work Item Save Participant


This post is about a Participant that creates an approval when saving a work item. I was interested in posting this, because I was interested on how to get at project member and role information in a server extension. I had already helped a partner with a similar effort in the past, where the approver information was supposed to be read in an external system. Back then I couldn’t find how to access the project area information and to find the roles.

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.

The example in this blog post shows RTC Server and Common API.

Download

You can download the code here. The API code in this post is Server and Common API.

Solution Overview

The code provides you with several classes. The interesting one is com.ibm.js.team.workitem.createapproval.participant.CreateApprovalParticipant. This class implements the participant that creates an approval if a workitem changes into the state "com.ibm.team.workitem.common.model.IState:com.ibm.team.apt.story.tested".

In case this state change is detected, the participant runs the following code to get approvers by their role Product Owner.

/**
 * @param workItem
 * @param collector
 * @param role
 * @param monitor
 * @throws TeamRepositoryException
 */
private void createApprovalByRole(IWorkItem workItem,
		IParticipantInfoCollector collector, String role, IProgressMonitor monitor) throws TeamRepositoryException {

	IContributorHandle[] approvers=findApproversByRole(workItem, "Product Owner", monitor);
	createApproval(workItem, collector, approvers, monitor);
}

The method called to find the approvers looks like the following code. The code gets the process area that governs the work item. and tries to get contributors with matching roles.

If there are no contributors that could be found with a matching role, it tries the same with the project area. The contributors are returned to create the approval.

Please note, this strategy could be changed into recursively start at the project area an find the enclosed team area hierarchy and then try all team areas in the hierarchy from the one that owns the work item up to the project area. This is left as a good example for the you to implement.

/**
 * Finds Approvers by role. Looks in the process area that owns the work item first, 
 * then looks at the project area if it was not already looking at it.
 * 
 * @param newState
 * @param roleName
 * @param monitor
 * @return
 * @throws TeamRepositoryException
 */
private IContributorHandle[] findApproversByRole(IWorkItem newState,
		String roleName, IProgressMonitor monitor) throws TeamRepositoryException {
	IProcessAreaHandle processAreaHandle = fWorkItemServer.findProcessArea(
		newState, monitor);
	IProcessArea processArea = (IProcessArea) fAuditableCommon.resolveAuditable(processAreaHandle,
		ItemProfile.createFullProfile(IProcessArea.ITEM_TYPE), monitor);
	IContributorHandle[] contributors = findContributorByRole(processArea, roleName, monitor);

	if(contributors.length==0){
		IProjectAreaHandle projectAreaHandle = processArea.getProjectArea();
		if(!projectAreaHandle.getItemId().equals(processAreaHandle.getItemId())){
			IProcessArea projectArea = (IProcessArea) fAuditableCommon.resolveAuditable(projectAreaHandle,
				ItemProfile.createFullProfile(IProcessArea.ITEM_TYPE), monitor);
			return findContributorByRole(projectArea, roleName, monitor);
		}
	}
	return contributors;
}

The code to find the approvers by role gets the members of the process area, then gets the contributors with the role name provided and returns the result. The code can be seen below.

/**
 * Find contributors by role on a process area.
 * 
 * @param processArea
 * @param roleName
 * @param monitor
 * @return
 * @throws TeamRepositoryException
 */
public IContributorHandle[] findContributorByRole(
		IProcessArea processArea, String roleName,
		IProgressMonitor monitor) throws TeamRepositoryException {
	fProcessServerService = getService(IProcessServerService.class);
	IContributorHandle[] members = processArea.getMembers();
	IContributorHandle[] matchingContributors = fProcessServerService
		.getContributorsWithRole(members, processArea,
		new String[] { roleName });
	return matchingContributors;
}

Finally the code below creates the approval if there are approvers that are passed. It gets the full state of the work item. Then it gets the approvals and creates a new descriptor for the new approval. For each approver it creates an approval with the new descriptor and then adds it to the approvals. Finally it saves the work item.

In case there are no approvers or the save is prevented, an error info is generated.

/**
 * Creates an approval and adds all approvers from an array
 * 
 * @param workItem
 * @param collector
 * @param monitor
 * @throws TeamRepositoryException
 */
private void createApproval(IWorkItem workItem,
		IParticipantInfoCollector collector, IContributorHandle[] approvers, 
		IProgressMonitor monitor) throws TeamRepositoryException {

	if (approvers.length==0) {
		String description = NLS.bind("Unable to create the Approval",
			"Unable to find an approver for the work item ''{0}''.",
			workItem.getItemId());
		IReportInfo info = collector.createInfo(
			"Unable to create approval.", description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
		return;
	}
	// Get the full state of the parent work item so we can edit it
	IWorkItem workingCopy = (IWorkItem) fWorkItemServer.getAuditableCommon()
		.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
		.getWorkingCopy();

	IApprovals approvals = workingCopy.getApprovals();
	IApprovalDescriptor descriptor = approvals.createDescriptor(
		WorkItemApprovals.REVIEW_TYPE.getIdentifier(), APPROVAL_NAME);
	for (IContributorHandle approver : approvers) {
		IApproval approval = approvals.createApproval(descriptor, approver);
		approvals.add(approval);
	}
	IStatus saveStatus = fWorkItemServer.saveWorkItem2(workingCopy, null, null);
	if (!saveStatus.isOK()) {
		String description = NLS.bind("Unable to create the Approval",
			"Unable to save the work item ''{0}''.",
			workItem.getItemId());
		IReportInfo info = collector.createInfo(
			"Unable to create approval.", description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
	}
}

The code to download contains other examples for how to get approvers.

Summary
The code is experimental. I have tested it in a Jetty based test server using the Scrum template. 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.

Resolve Parent If All Children Are Resolved Participant


This post is about a Participant that resolves the parent work item, if all children are resolved. It is the example I used to understand the server API for manipulating work item states. I would probably not use it on a production system, because I strongly believe that a human being should do a conscious decision in cases like that, but it is a nice example about the RTC Work Item Server API.

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.

The example in this blog shows RTC Server and Common API.

Download

You can download the code here.

The download contains the (slightly enhanced) UpdateParentDuration Participant described in this post, the UpdateParentStateParticipant and the WorkFlowPathfinder from this post as Eclipse projects. In Eclipse use File>Import and select Existing Projects into Workspace. Select the downloaded archive file and do the import. This version of the WorkFlowPathFinder has the Plain Java Client Libraries dependency removed and is treated as real plug in, to provide its services to the other plug ins.

Please note, If you just get started with extending RTC, I would suggest to start reading this and the linked posts to get some guidance on how to set up your environment. Then 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.

UpdateParentSateParticipant

The project com.ibm.js.team.workitem.extension.updateparent.participant contains the class UpdateParentState. This implements the Participant that

  1. Checks if there was a state change
  2. Checks if the new state is a closed state
  3. Checks if there is a parent
  4. If there is a parent, checks if all children are closed
  5. Resolves the parent If all children are closed

The UpdateParentStateParticipant can use different strategies to do the state change. The code below shows some of the available options.

if (!resolveBF(parentHandle, monitor)) {
// if (!resolveDF(parentHandle, monitor)) {
// if (!resolveWithResolveActionID(parentHandle, monitor)) {
// if (!gotoStateBF(parentHandle,"4",true, monitor)) {

The methods resolveBF() and resolveDF() use the Breadth First and the Depth First strategy against the resolve action. The method gotoStateBF() uses the Breadth First strategy but uses a target state instead. The method resolveWithResolveActionID() only tries to apply the action ID. You can play around with the different strategies and pick the one you like best.

The resolve Action needs to be defined in the RTC work Item workflow as shown below, otherwise the state change can’t be done, because no path can be found.

Define teh Resolve Action The WorkflowPathFinder class in the download provides two other ways to find a target state, instead of a target action. The methods are called findPathToState_DF() and findPathToState_BF().

UpdateParentDurationParticipant

The UpdateParentDurationParticipant is slightly enhanced compared to the state in RTC Update Parent Duration Estimation and Effort Participant post. It checks if there are changes to the duration and estimate values before doing anything else.

The code also contains several methods to analyze the work item links on the server as described in The RTC WorkItem Server Link API post.

Summary
The code is experimental. I have tested it in a Jetty based test server using the Scrum template. 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.